
The order you test an API in decides what you find, and most checklists get it wrong by organizing around tools instead of phases. You cannot test authorization before you have an endpoint inventory, and you cannot tell a real BOLA from an expired session before you understand the auth model. So this checklist runs in six dependent phases, each handing structured output to the next: Discovery, Authentication, Authorization, Input, Business logic, and Rate limiting.
Every item maps to the OWASP API Security Top 10 (2023), the taxonomy that ranks Broken Object Level Authorization as the number-one API risk. Below you get the full checklist as a working document, then the requests and command output that prove each phase, a report-style findings table, and the configuration changes that actually close the gaps.
Six phases, each targeting a different slice of the OWASP API Top 10, run in strict dependency order. Discovery builds the attack surface. The two auth phases catch the most damaging access-control bugs. Input and logic catch injection and abuse. Rate limiting catches resource exhaustion.
The sequence is not cosmetic. Discovery hands Authorization a list of object-bearing endpoints to attack; Authentication hands it the tokens to attack them with. Reverse the order and you waste a day fuzzing inputs on a route that turns out to be unauthenticated anyway. The checklist below is the short form, where each item is something you actively send a request to verify, never a box you tick from documentation. Pair it with the fundamentals of API pentesting and the OWASP API methodology for the reasoning behind each test.
Enumerate every endpoint, version, and parameter the backend accepts, from documented and undocumented sources alike, because old versions (API9) are a frequent foothold. Record three things per endpoint: the HTTP method, whether it takes an object ID in the path or body, and whether it requires authentication. Object-bearing endpoints are your BOLA candidates, admin-looking 403s are your BFLA worklist, and unauthenticated state-changers are immediate findings.
The fastest way to surface the undocumented surface is to fuzz with an API-aware tool that fires the correct method, then diff against the spec:
$ kr scan https://api.target.com -w routes-large.kite
GET 200 /api/v2/users/me
GET 403 /api/v2/admin/users # <- exists, gated: BFLA target
POST 200 /api/v2/internal/users # <- undocumented, writable: chase this
GET 401 /api/v1/accounts/{id} # <- old version still live: API9The 403 and the undocumented 200 are the lines that matter. A path-only fuzzer firing GETs would mark that POST route as 405 and discard it; Kiterunner finds it because it replays Assetnote's route database with the right verb. The most common mistake in this phase is testing only what the client documents, so always diff live mobile traffic against the OpenAPI file and chase every delta.
Attack the token before you attack the data, because a token the server mis-validates is a master key. JWTs fail in a handful of repeatable ways, each with a direct test: alg:none acceptance, an unverified signature (flip a byte, look for 200), kid injection for key confusion, and a weak HMAC secret. The last one is mechanical once you capture a token:
$ hashcat -a 0 -m 16500 token.jwt rockyou.txt # -m 16500 = JWT
eyJhbGciOiJIUzI1NiJ9...<snip>:Summer2021! # <- secret cracked
Status...........: CrackedWith the secret you re-sign the token carrying "role":"admin" and replay it. Then test the boring failures that matter just as much: no account lockout after repeated failures, tokens reused across services, and tokens that never expire. jwt_tool automates the alg and kid checks so you spend your time on the claims worth tampering with.
This is the core of the engagement, and it needs at least two accounts, ideally at two privilege levels. Authorization is where BOLA (API1) and BFLA (API5) live, the bugs behind the largest API breaches. The technique is simple: replay the same request as a different principal and watch the status code.
PUT /api/v1/users/501/email HTTP/1.1
Authorization: Bearer <low-priv-user-token> # token for user 88, not 501
Content-Type: application/json
{ "email": "attacker@evil.com" }
--- response ---
HTTP/1.1 200 OK # <- expected 403: you just took over user 501
The telltale is a 200 or 204 where a 403 belongs. To run this across hundreds of requests without hand-replaying each one, Burp's Autorize extension does the two-account diff for you:
[Autorize] replaying with second account's token...
GET /api/v1/orders/1043 Bypassed! # <- BOLA confirmed
GET /api/v1/users/501 Enforced # 403 as it should be
DELETE /api/v1/admin/users/77 Bypassed! # <- BFLA confirmedEvery "Bypassed!" row is a finding. Always test both directions: low-to-high (a standard user reaching an admin function) and lateral (user A reaching user B's object).
Test every parameter for injection and mass assignment, then attack the workflow itself. APIs trust the request body, so the property level (BOPLA, API3) is rich ground. For breadth, drive the spec through Schemathesis to flush out 500s and contract breaks before you go hunting by hand:
$ schemathesis run https://api.target.com/openapi.json \
--checks all -H "Authorization: Bearer <token>"
FAILED GET /api/v1/search status_code_conformance # 500 on empty q
FAILED POST /api/v1/orders response_schema # leaks internal_cost field
2 passed, 2 failed # <- both worth manual follow-upThen go manual where judgment is required:
role, isAdmin, tenantId, verified, credit. If the server echoes your value, it bound it. See mass assignment attacks./checkout/confirm); race concurrent requests against one balance using Repeater's parallel send or Turbo Intruder's single-packet attack.A coupon that survives ten simultaneous redemptions is a logic bug worth real money, and no scanner will ever construct that test.
The last phase confirms that sensitive and expensive endpoints enforce throttling, pagination caps, and payload limits, because missing limits (API4) enable credential stuffing, enumeration, and denial of service. Hammer login, OTP, password-reset, and search for any 429 or lockout; request oversized pages with ?limit=1000000; send deeply nested or aliased GraphQL queries to probe cost limits. The catch every scanner misses: a per-IP limit looks fine to DAST but falls to a rotating X-Forwarded-For or a GraphQL alias batch, so test the limit by identity.
On a recent assessment of a logistics SaaS, the API looked tidy until the rate-limit phase: their password-reset endpoint had no per-identity throttle, so we enumerated valid accounts at thousands of requests per minute, then chained that into a BOLA on shipment records to map their entire customer list. Each gap below pairs with a config fix, not advice. The table is what a report excerpt looks like.
For teams that want this running continuously instead of once a year, our agentic pentesting approach automates the repetitive discovery and rate-limit checks so testers focus on logic.