I have spent the better part of a year intentionally breaking APIs, and every time, I was reaching for the same list in my head: the OWASP API Security Top 10. And every time, I wished I had one page that told me precisely what to check for each of the ten, both as the person attacking the API and as the person trying to prove theirs is safe.
So this is that page.
For every item in the OWASP API Security Top 10 2023, you get two tracks. 'How I test for it' is the attacker's view: the requests to send, the things to swap, the tools to reach for, and what a positive finding looks like. 'How to prove you are covered' is the defender's view: the controls to verify so you can say, with evidence, that this class of flaw does not apply to you.
Working through this checklist does not guarantee your API is secure. Nothing does. A checklist is a floor, not a ceiling. It catches the ten most common classes of failure, but a determined attacker chains findings together in ways no list anticipates, and business logic flaws in particular live in the gaps between categories. Treat this as a strong starting point and a way to make sure you have not missed the obvious, not as a certificate of safety.
With that said, let's get into it.
Two things are worth setting up before you start, because almost every test below depends on them.
Here is the whole list at a glance before we go deep on each
| # | Category | The one-line question | Primary tools |
|---|---|---|---|
| API1 | Broken Object Level Authorization | Can I reach another user's resources?? | Burp Repeater, Autorize, ffuf |
| API2 | Broken Authentication | Can I break, forge, or brute force my way into an account? | xjwt.io, jwt_tool, Burp Intruder, hashcat |
| API3 | Broken Object Property Level Authorization | Does it leak fields I should not see, or accept fields I should not set? | Burp Repeater, Param Miner |
| API4 | Unrestricted Resource Consumption | Can I make it do expensive things without limit? | Burp Turbo Intruder, ffuf |
| API5 | Broken Function Level Authorization | Can a normal user reach an admin function? | Burp Repeater, Autorize |
| API6 | Unrestricted Access to Sensitive Business Flows | Can I automate a business flow past its intended limit? | Burp Intruder, custom scripts |
| API7 | Server Side Request Forgery | Can I make the server fetch a URL I control? | Burp Collaborator, interactsh |
| API8 | Security Misconfiguration | Are the headers, methods, errors, and TLS locked down? | nikto, nuclei, testssl.sh |
| API9 | Improper Inventory Management | Are there old versions, staging hosts, or shadow endpoints live? | ffuf, subfinder, kiterunner |
| API10 | Unsafe Consumption of APIs | Does it blindly trust data from the APIs it consumes? | mitmproxy, code review |
This is number one for a reason. It is the flaw I have found in almost every single lab I have touched, and it is the simplest to understand. The API receives an object ID from the client and uses it to fetch a record, but it never checks whether the requester is authorized to access that record. The ID is trusted as if possessing it were equivalent to being authorized to use it.
Ask these, straight from the OWASP criteria:
The core move is the two-account swap
Beyond the manual swap:
In the Vulnerable Bank API article I wrote, I swapped an account number in GET /transactions/{account_number} while sending my attacker token, expected a 403, and got the victim's entire transaction ledger
In Zero-Health, the same GET /api/lab-results/:id endpoint returned another patient's medical results.
In VAmPI, I pulled another user's private book by name
Reference: API1:2023 Broken Object Level Authorization
Authentication is exposed to everyone, making it the most poked part of any API. When it breaks, the attacker does not read one user's data; they become that user.
Authentication has the widest attack surface of the ten, and JWTs are where I spend most of my time. My go-to for the manipulation itself is xjwt.io, where I decode a token, run the dictionary attack against the secret, edit the claims, and re-sign, all in one place.
I documented the full scope of JWT attacks and their fixes in "JSON Web Tokens: Anatomy of a Break and Fix
Reference: API2:2023 Broken Authentication
This one merges two older flaws, so it has two sides. The read side is 'excessive data exposure': the API hands back object fields the user should never see. The write side is 'mass assignment': the API accepts fields the user should never be able to set.
Read side, hunt for exposure:
Write side, inject properties:
I found the full range of this across labs:
In VAmPI, I added "admin": true to the register call
In vAPI, I discovered a hidden credit field via GET /user/me and then set it to 50000 on registration.
In the Vulnerable Bank, I injected "is_admin": true at register and watched my new token come back with the admin claim set.
On the read side, VAmPI's /users/v1/_debug endpoint exposed every user's plaintext passwords, and the Vulnerable Bank's GET /api/virtual-cards endpoint returned full, unmasked card numbers, expiry dates, and CVV codes because the masking lived only in the frontend.
Reference: API3:2023 Broken Object Property Level Authorization
Every request costs something: CPU, memory, bandwidth, or literal money when an endpoint sends an SMS or calls a paid third party. When there is no limit on how often you can trigger that cost, you have this flaw. It shows up as a denial-of-service attack and as a surprise five-figure cloud bill.
Any of these limits missing or set wrong makes it vulnerable: execution timeouts, max memory, max upload size, number of operations per request (GraphQL batching), records returned per page, and third-party spending limits.
In the Vulnerable Bank Part 2, I hit the password-reset endpoint 100 times with Burp Intruder and got a 200 every single time, never a 429.
Reference: API4:2023 Unrestricted Resource Consumption
Where API1 is about reaching another user's data, this is about reaching another role's functions. A regular user calling an admin-only endpoint. The classic tell is an admin action that works with a normal token.
OWASP is blunt about one thing here: do not assume an endpoint is admin-only or regular based on the URL path alone.
In VAmPI, I reset the admin's password as a normal user with PUT /users/v1/Mel/password, got a 204, and logged in as the admin.
In vAPI, I changed /user/{id} to the collection endpoint /users and got the full user list with a non-admin token.
And a note on negative results, because they matter just as much.
In the Vulnerable Bank, I tried POST /admin/create_admin and POST /admin/delete_account as a standard user and got a clean 403 Forbidden both times. I documented it as not vulnerable. Proving a control works is a finding too.
Reference: API5:2023 Broken Function Level Authorization
This one is different from the rest, because the individual request is not a bug. The flaw is that a flow that is fine at human speed becomes harmful at machine speed, and nothing stops the automation. One purchase, one reservation, one sign-up is normal. Thousands of them, driven by a script, in seconds, is the exploit.
An endpoint is vulnerable if it exposes a sensitive business flow without restricting excessive access. Think buying stock (scalping), posting (spam), reserving slots (denial of availability), or creating accounts for referral credit. The risk is business-specific, so you need to understand what the business stands to lose.
In the Vulnerable Bank, I replayed the virtual-card creation request 100 times with Burp Intruder, and the server happily minted 100 distinct cards, with no per-account limit on a sensitive financial flow.
Reference: API6:2023 Unrestricted Access to Sensitive Business Flows
SSRF occurs when an API fetches a URL you provide without validating where it points. You hand it an internal address, and it reaches inside its own network and brings the response back to you.
I have hit both shapes of this. In vAPI's Server Surfer, I swapped a normal URL for file:///etc/passwd and decoded the Base64 the server returned. In the Vulnerable Bank Part 2, an external IP was blocked with a 403, but http://ip-address/internal/config.json sailed through the loopback and returned the database password and app secret key.
Reference: API7:2023 Server Side Request Forgery
This is the broad one. It spans the whole stack, from a missing TLS setting to a verbose error that leaks a stack trace to a CORS policy that trusts any origin. Injection lost its own spot in the 2023 list, and I tend to test for it here, since in practice it usually rides in on a misconfigured or unvalidated input path. That is my own placement, not an official OWASP mapping.
In the Vulnerable Bank, sending Origin: http://meli.traleor.com got it reflected straight back in the CORS header, and the config file I pulled via SSRF was itself a misconfiguration.
Reference: API8:2023 Security Misconfiguration
You cannot protect what you do not know you are running. This is the flaw of the forgotten staging host, the old v1 that never got the fix, the undocumented endpoint. The current production API might be locked down tight, while a beta host sitting next to it, wired to the same database, has none of the protections.
This is a pattern I keep exploiting:
In vAPI, /api9/v2/user/login returned a rate-limit header while the legacy /api9/v1/user/login had none, so I brute-forced the PIN on v1.
In the Vulnerable Bank, /api/v3/forgot-password masked the reset PIN while the legacy /api/v1/forgot-password returned it in plain JSON. The old version is almost always the weak one.
Reference: API9:2023 Improper Inventory Management
The newest item, and a shift in perspective. The other nine are about incoming requests to your API. This one is about the requests your API makes out to the third-party services it consumes. Developers trust data from a well-known API more than they trust user input, and that trust is the vulnerability.
This one often requires some grey-box knowledge of the integrations, but there are also black-box approaches.
The closest demonstration I did is the Vulnerable Bank Part 2 AI chatbot. Its GET /api/ai/system-info reported "database_access": true, and by feeding it natural-language prompts, I got it to retrieve data that bypassed the app's own BOLA and RBAC checks. The backend consumed the AI's output with no guardrails, which is the shape of unsafe consumption.
Reference: API10:2023 Unsafe Consumption of APIs
Reading a checklist and working through one are different things. I built an interactive companion to this article where you can tick each check off, filter by severity or status, expand any item to view the payloads and commands, watch your progress fill up, and export your findings when you are done. It is meant to sit open on a second screen while you test.
There is a printable version too, for when you want the checklist attached to an engagement report rather than open in a browser.
If I could tattoo one lesson from all of this onto the back of my hand, it would be that eight of these ten come down to two questions: who are you, and are you allowed to do this? BOLA, BOPLA, BFLA, business flows, authentication, they are all authorization requests at different layers. Get authorization right, on every object, every property, every function, every version, and most of this list closes itself.
And remember the disclaimer at the top. Once these boxes are ticked, the real work of finding the flaw nobody else thought of begins.
See you in the next one!