Library · Summary & review

Pentesting APIs

By Maurício Harley (Packt, 2024), a video course. The attacker's moves against an API, from recon to bypass, with demos you must reread before trusting them.

FR EN
Pentesting APIs book cover, Maurício Harley

Pentesting APIs

Pentesting APIs: A practical guide to discovering, fingerprinting, and exploiting APIs

5.6 /10

« An attack course honest about its failures, fooled by its own demos, to read for the moves and not for the code. »

  • AuthorMaurício Harley
  • OriginalPackt, 2024 · video course, 4 h 50
  • LanguageEnglish only
  • This page~13 min read
  • Read it Read on ExpertEdge Code : 50% off the yearly plan. Affiliate link: I earn a commission if you subscribe with this code. My ratings do not depend on it.
Book rating across 5 dimensionsIdeas5/10Practical5/10Readability6/10Aged well7/10Examples5/10

The attacker's moves against an API, from recon to bypass, with demos you must reread before trusting them.

Why this course

You wrote an API (a door that lets a program talk to your server) and your tests pass. Each address on that door, like /orders, is called a route (or endpoint). One question remains: what would someone trying to break it do? This course answers with the moves. A penetration test (pentest: attacking your own system, with permission, to find the flaws before others do) runs step by step on a practice target.

The target is crAPI, a deliberately vulnerable API. The tools are the trade's own: Burp Suite (a proxy that intercepts and replays requests), Hydra (tries passwords in bursts), Radamsa (makes malformed inputs). The thread is one idea: the attack works when the server settles for what the client claims. The course says it in so many words: that is the quote in idea 1.

A few precisions before we start. This is a transcribed video course, not a book. I read in full only the lessons where the attack is new to this library: 21 out of 69. The targets are labs, so attacks succeed more easily than elsewhere. Several demos are wrong or unusable, and I say which. Finally, this library already covers defense, in the Defending APIs note and the article BOLA: the A/B test in Go and PHP.

The six ideas form three families. What the API believes on trust: ideas 1 and 5. What it lets slip: ideas 2 and 3. What it thinks it can stop or copy: ideas 4 and 6.

The ideas that stay

1A role read from the header is a role offered

To answer a request, the server must know two things: who is asking, and what they may do. A request header (a line of metadata, like Authorization) is written by the client. Anything in it can therefore be made up.

The course shows Flask servers (a small Python web framework) that forget this rule. I keep two of its three scenarios. The first follows RBAC (role-based access control: each user has a role, and the role opens doors). The code reads the role from the request header, nothing more: "The code simply fetches the role provided by the requestor from the headers without further checking whether such a claim is legitimate." (lesson 29). In plain words: the attacker writes admin and the door opens.

The second follows ABAC (attribute-based access control: the right depends on account type, amount, time of day). A "standard" account is capped at 1,000 per transaction, but the account type comes from the request: the attacker declares another type and exceeds the cap. Same verdict: "the validation code is simply relying on what is claimed by the requestor" (lesson 29).

The course treats BOLA (Broken Object Level Authorization: reaching someone else's object) separately. The code trusts an identifier supplied by the user. It does not check that this user may ask for it. On a GET /user?id= route, changing the number is enough (lesson 29). Its cousin, BFLA (Broken Function Level Authorization), means calling a function reserved for administrators: the course shows a /admin/create_user route that any user can call.

The author's conclusion fits in one sentence: "All mentioned AuthZ mechanisms are strong, but the way they were implemented on an API endpoint may make them useless" (lesson 29).

A JWT (a signed token that proves identity: it carries its role and its expiry date) is the counterpoint. It also travels in a header, Authorization, but the client cannot rewrite its role without breaking the signature. The course tests it two ways. First way, a token scanner (jwt_tool, a tool that tries known attacks against a token) finds nothing: "No vulnerabilities were found." (lesson 24).

Second way, key confusion. The server signs its tokens with a private key that only it holds, and publishes the matching public key, which anyone can read. The attack signs a fake token with that public key, hoping the server takes it for a plain signing password. On the tested route, the server answers "Invalid JWT Token". On another route, the forged token is accepted, but the server returns the original role, user, not admin. The course concludes: "This probably means the JWT implementation of crAPI is not vulnerable to the key confusion vulnerability." (lesson 24). A signed role cannot be rewritten, while a role read from the header is there for the taking.

My reading

Concrete case, my own. In PHP, the difference is two lines.

// ✗ the client picks its role
$role = $_SERVER['HTTP_X_ROLE'] ?? 'user';
// ✓ the server reads it from its own session
$role = $session->user()->role;

Same rule against BOLA: filter the object by its owner, WHERE id = :id AND user_id = :moi (:moi is the logged-in user), never WHERE id = :id alone.

2Before touching the API, the attacker reads your repositories

The attack does not start with the API. It starts by searching for what you left lying around. The course calls this OSINT (open-source intelligence: collecting public information) and defines it: "OSINT comprises a series of techniques for collecting and analyzing information that is publicly available." (lesson 18).

The tools are simple. Shodan is a search engine for machines connected to the Internet: the query content-type:application/json returns the servers that answer in JSON, the format of most APIs. A dork is a search query bent to find what should not have been public, like intitle:"Index of /api/", which lists open API directories. A typical target is the secrets file pushed by mistake: "An inattentive developer might have pushed them to a public repository." (lesson 18).

Next comes the default account. Hydra, run against crAPI's login page with a list of common usernames and passwords, finds the pair admin@example.com and Admin!123. The origin is mundane: "the developer forgets to delete a credential pair used just for testing" (lesson 25). The variant is called password spraying: trying a single password, or very few, against many accounts (lesson 26).

The course also shows a dead end. With the single username admin, Hydra announces "0 valid password found". The author then switches to a credentials file, without explaining the failure: "We have found nothing so far." (lesson 25).

For the defender, the log is a clue. Every request carries a User-Agent header announcing the client software, and Hydra signs itself Mozilla/4.0 (Hydra) there (lesson 25). Another clue is volume. Thousands of requests are measurable, and a protection that limits requests per client can block them (idea 4 details it). To avoid that, the course advises running several copies of Hydra from different addresses (lesson 25).

My reading

Practical, outside the course. Hydra can announce another name in its User-Agent, with a simple setting. That name stays a clue, not a defense. The two moves that count are free: search your own repositories for .env, .yml and keys before someone else does, and delete test accounts before going live.

3A chatty error is a floor plan of the building

When a request crashes the server, what does it send back? Many APIs return the exception as is. For the attacker, it is a map: the language, the framework, the function names.

Fuzzing means sending malformed inputs in bulk to provoke errors. The course uses Radamsa to make 1,000 damaged CSV files. A small Bash script sends them to /api/upload/csv and replays everything that does not answer 200. Honest result: 2 errors out of 1,000 files, then 41 in total with 5,000. The errors shown are 500 errors that spit out the Python exception text ('utf-8' codec can't decode byte). The course writes "2%" for 2 out of 1,000: it is 0.2% (lesson 41). The yield is thin, and the author owns it: "you must have patience while dealing with fuzzing" (lesson 41).

The next lesson lists what errors reveal. An error page gives the .NET version (Microsoft's web platform). A message names Flask and Werkzeug (the web library under Flask). A stack trace (the list of calls at the moment of the crash) gives function names and line numbers. Each detail narrows the search field for a targeted attack.

The defense advice is short and good: "Generic error messages with minimal technical details are a good first step." (lesson 42).

My reading

The same advice translated into PHP by me. The chatty error is fixed in one place, the exception handler, which PHP calls when an exception is caught nowhere. The detail goes to the server log, the client gets a neutral message.

set_exception_handler(function (Throwable $e) {
    error_log((string) $e);                  // the detail stays with us
    http_response_code(500);
    echo json_encode(['error' => 'internal error']);   // the client learns nothing
});

4The rate-limit bypass that bypassed nothing

A rate limit is a doorman who counts each client's requests and refuses those above the cap. To recognize a client, it reads its IP address (its number on the network, which the server sees on every connection). The course gives the image of the leaky bucket: "Imagine a bucket with a small hole at the bottom, constantly leaking out a controlled amount of water." (lesson 49). Requests fill the bucket, the hole sets the exit speed, and whatever overflows is refused.

The lab puts NGINX (a web server acting here as the doorman) in front of Mockoon (a fake API server). The rule: one request per second, with a burst of five (how many requests ahead are tolerated). The ab tool (Apache Bench, a load generator) sends 100 requests, 10 at a time: 94 fail with a 503 code. The doorman works, and the author notes it: "Well, we can attest that our rate-limiting mechanism is doing its job." (lesson 49).

The next lesson announces the bypass: "To bypass such a restriction, we commonly apply a rotation of the source IP address." (lesson 50). The script simulates five users, each with their own address, and a one-second wait between two requests. Result: all 50 requests get through.

But the script sends none of the simulated addresses. The variable simulated_ip = f"10.0.0.{user_id+1}" appears only in a print. The request goes out with requests.get(url), with no header, no proxy, no source address. The five "users" share the same address: the server reads the connection's, and nothing in the request changes it. The script sends one request at a time, at the pace of the limit: one per second. It respects the limit, it does not bypass it. The author credits the pause between requests, though: "The key point here is the delay we are putting between every request." (lesson 50). Yet his summary concludes that the delays and the address change successfully bypassed the limit (lesson 50).

Precision: I read this code, I did not rerun it.

My reading

I tested the course's rule, not its script: with NGINX 1.29 and the same rule (rate=1r/s, burst=5), eight requests sent one after the other, with no pause at all, all pass. Without nodelay, NGINX queues instead of refusing. The course's refusals come from simultaneous sends (ab -c 10, Burp Intruder). What I infer, the course stops short. The course says NGINX counts by source address ($binary_remote_addr). Varying the real address would therefore have changed the request count. An X-Forwarded-For header, which the client writes itself, would have changed nothing, barring a special setup. To defend, limit by account or API key on top of the address: address rotation is the obvious bypass.

5Logic flaws are found by hand

A scanner finds what looks like a known flaw. It ignores business logic, meaning the rules specific to the application: a discount code should not be worth 100%. The course devotes six lessons to these abuses, with this conclusion: "Unveiling business logic vulnerabilities hinges on manual testing." (lesson 64).

First move, credential stuffing: replaying real username and password pairs, stolen elsewhere, betting that people reuse their passwords. Unlike the spraying of idea 2, each attempt is a real pair. The tool is OpenBullet2: it replays a five-line user:password file against the login page of a small Flask API. That API limits to five failures per minute and per username. The point of the demo: this limit does not hinder an attack that changes the name on every attempt, since each name suffers one failure at most (lesson 59). The author admits: "I wasted a reasonable amount of time trying to understand why my attacks were not working." (lesson 60). The fix goes through an OpenBullet2 settings file, and the author cannot say whether it was a bug in his version (lesson 60).

Second move, parameter tampering: the course puts it this way: "The core of the attack lies in exploiting the trust the API endpoint has in the parameters provided as part of the requests." (lesson 63). On a small demo API, the course turns Alice, a fictional account, from user into admin. A transaction goes from pending to completed. Each request carries the new value, and also the administration password (admin_secret) that the server requires.

Third move, business logic proper, on a small demo shop. An invented discount code is not applied. The attacker then calls the administration route, with the administration password hard-coded in the code. There he creates a 100% code. He then places the order again with that code, and this time the order goes through.

These two demos prove little, because they already assume the administration password is known. The author concedes it would have to be obtained through social engineering (convincing someone to hand it over), resource exhaustion or data leaks (lesson 63). The move stays useful, the proof does not.

6The course's "secure" code does not always protect

The end of the course gives "bad / good" pairs in Python, Java, JavaScript and Go: password hashing, parameterized queries, error handling, encryption. Its conclusion: "Never reinvent the wheel." (lesson 68) and "No single technique or principle will protect the whole API." (lesson 68).

Only, several of these snippets are wrong. I checked two by running them.

The first is in Go. The "strong" example writes the key straight into the code ("hard-coded"): []byte("strongkey12345678901234567890"). It is 29 bytes long. But AES (the standard encryption algorithm) only accepts 16, 24 or 32 bytes. Run in Go 1.26.10, the aes.NewCipher function answers crypto/aes: invalid key size 29, and the panic in the same snippet (the abrupt stop of the Go program) fires. The "weak" example from the same course, a 15-byte key, fails the same way.

The second is in Python, with bcrypt (a password hashing algorithm). The code computes hashpw(password, gensalt()), then passes that hash to an authenticate function that the course does not show. But bcrypt makes a fresh salt on every call: a random string mixed into the password so that two identical passwords give two different hashes. Measured: two calls on the same password give two different hashes, so this hash can never equal a hash already stored. To check a password, you needed checkpw, which rereads the salt from the stored hash.

I read three other flaws in the course code, without running them. The GraphQL "encryption" is an HMAC (a one-way fingerprint, impossible to decrypt). The Fernet key (an encryption key) is regenerated on every run. In Java, the initialization vector iv (a random value the cipher requires) is never set. The lesson thus contradicts its own moral: it recommends proven libraries, then uses them wrongly.

My reading

Measured on my side. The two results above come from small programs I ran, with Go 1.26.10 and the bcrypt library of Python 3.12. The course flags none of these flaws: checkpw, the valid AES key lengths and the initialization vector have 0 occurrences across the 69 extracted lessons.

My honest take

A course that shows the attack well and its own proofs badly. Its strength: it also shows the failures, unvarnished. The token scanner finds nothing. Hydra, with the single username admin, finds nothing. An invented discount code is not applied. You learn the real rhythm of a test, full of dead ends.

Two reservations. First, several demos do not prove what they announce, including the address rotation of idea 4. Part of the secure code in idea 6 is wrong. Copy nothing without replaying it. Second, the targets are labs, and two demos in idea 5 start from the administration password already known. You take away moves, not a method for a real API.

My advice: if you defend an API, read ideas 1, 3 and 4 and skip the lessons where tools get installed. If you only have time for one note, take Defending APIs. I am a web developer, not a pentester: the above says what the course taught me, not what an attack professional would think of it.

Odilon

Still valid in 2026?

Yes for the moves. The course cites the 2023 edition of the OWASP API Top 10, the reference list of API risks (lesson 66). It ranks BOLA among the two most dangerous threats (lesson 8).

What ages: the exact syntax of Shodan and GitHub search filters, which I did not recheck. And crypto.createCipher in JavaScript is no longer recommended.

Who is it for?

Read it if

  • you develop an API in PHP or Go and have never seen a penetration test unfold
  • you want to understand why a header or identifier from the client is never believed
  • you look for test moves to replay on your own API, with permission, in a lab
  • you have half a day and like seeing dead ends as much as successes

Skip it if

  • you want a complete defense guide: take Defending APIs
  • you aim for a pentester career: the level is an introduction, on lab targets
  • you plan to copy the course's code snippets into your project
Read it on ExpertEdge

This video course is part of ExpertEdge, Packt's subscription for technical books and video courses. The code ODILON50 takes 50% off the yearly plan: the course on ExpertEdge. Affiliate link: I earn a commission if you subscribe with this code, and my ratings don't depend on it.

Going further

Note based on : 21 lessons read in full out of the 69 extracted (77 announced, quizzes excluded), plus two passages found by search (lessons 8 and 66). The "My reading" passages are mine, not the course's.

Comments (0)

On the same shelf

Networking and security

See the whole shelf (8 notes) →

Browse the whole library

More book notes coming: one book at a time, the marrow only.