September 11, 2026
API Security in 2026: The Vulnerabilities Everyoneβs Missing.
Most companies still test APIs like they test web apps. They run a scanner, check for SQLi and XSS, tick a box, and ship it. Then sixβ¦
By Bugitrix
8 min read
Most companies still test APIs like they test web apps. They run a scanner, check for SQLi and XSS, tick a box, and ship it. Then six months later someone finds a way to pull every user's private data by changing an ID in the URL, and nobody understands how a "secure" API let that happen.
Here's the uncomfortable truth: the OWASP Top 10 for web apps was never built for APIs. APIs don't have a rendered DOM to attack. They don't rely on a browser to enforce anything. Every check that assumes "the client will stop the user from doing that" is dead on arrival, because the client is Postman, curl, or a script β not a browser respecting your UI restrictions.
This article breaks down the three vulnerability classes that dominate real API breaches right now, why standard checklists walk right past them, and exactly how to test for them yourself.
Why the Old Checklist Fails on APIs
A traditional web security checklist assumes:
- The user interacts through a browser UI.
- The UI hides options the user shouldn't access.
- Input validation on the client is a meaningful control.
- One "session" maps cleanly to one user's intended actions.
None of this holds for APIs. An API endpoint doesn't know or care whether the request came from your official mobile app or from an attacker replaying a captured request with a different user_id. The API's only real defense is server-side logic β and that's exactly where these bug classes live.
This is why a clean Burp Suite scan or a passing Nessus report means almost nothing for API security. Automated scanners test for known signatures. BOLA, mass assignment, and GraphQL abuse are logic flaws. There's no signature for "this endpoint forgot to check ownership." You have to think like the developer who wrote the authorization code, find the place they got lazy, and prove it.
1. BOLA β Broken Object Level Authorization
BOLA (also called IDOR when talking about direct object references) is consistently the #1 API vulnerability in real-world bug bounty and pentest data. It's simple to explain and brutal in impact.
The core idea
An API endpoint fetches or modifies a resource based on an ID, but never verifies that the authenticated user actually owns or has permission to access that specific object.
GET /api/v1/invoices/8825GET /api/v1/invoices/8825If changing 8825 to 8826 returns someone else's invoice β even though you're logged in as yourself β that's BOLA.
Why it slips past standard testing
- Authentication passes fine. The token is valid. The scanner sees a 200 OK and moves on.
- It requires two accounts to actually prove β you need Account A's session hitting Account B's resource. Most automated tools only test with one session.
- It often only shows up on secondary or "forgotten" endpoints: export features, PDF generation, internal admin APIs, webhook replay endpoints β not the primary CRUD flows everyone tests first.
Practical methodology to find it
- Create two test accounts (Account A and Account B) with different privilege levels if possible.
- Map every endpoint that accepts an identifier β numeric IDs, UUIDs, slugs, order references, ticket numbers. Don't just check
/users/{id}; check nested resources too:/users/{id}/orders/{order_id}/items/{item_id}. - Swap the ID, keep the token. Log in as A, capture the request, replace A's object ID with one belonging to B, replay it using A's auth token.
- Test all HTTP verbs, not just GET. BOLA on
GETleaks data. BOLA onPUT/PATCH/DELETElets you modify or destroy someone else's data β far more severe, and far more commonly missed because testers stop after confirming the read case. - Don't trust "impossible to guess" IDs as a defense. UUIDs feel safe, but if you can enumerate them from another endpoint (a public listing, a search feature, an export, even error messages), the "unguessable" ID stops being a control.
- Check indirect object references too β filenames in an
/uploads/{filename}pattern, S3 keys returned in API responses, internal file paths leaked in JSON error bodies.
The fix (what to tell your dev team)
Authorization checks must happen on every single request, server-side, based on the authenticated identity β never inferred from the object ID itself. The pattern should be: "does this authenticated user have a relationship to this specific object?" not "does this object exist?" This has to be enforced at the data access layer, not scattered across individual route handlers where it's easy to forget on the tenth endpoint someone adds six months later.
2. Mass Assignment
Mass assignment happens when an API automatically binds incoming JSON fields directly to internal data models or database columns β without an explicit allow-list of which fields the client is actually permitted to set.
The core idea
Say a "create user" endpoint expects:
json
{ "name": "Aman", "email": "aman@example.com" }{ "name": "Aman", "email": "aman@example.com" }But the underlying User model also has fields like role, is_verified, account_balance, or is_admin. If the framework blindly maps whatever JSON keys arrive onto the model, an attacker can just add fields the UI never exposes:
json
{
"name": "Aman",
"email": "aman@example.com",
"role": "admin",
"is_verified": true
}{
"name": "Aman",
"email": "aman@example.com",
"role": "admin",
"is_verified": true
}If the backend doesn't explicitly reject or ignore unrecognized/unauthorized fields, congratulations β you just self-promoted to admin during signup.
Why it slips past standard testing
- The official app never sends these fields, so the visible attack surface looks clean. You only find it by sending fields the app doesn't send.
- It's invisible in API documentation. Swagger/OpenAPI specs usually only document the intended request shape, not the full underlying data model.
- Frameworks that auto-generate CRUD endpoints (many low-code/ORM-based backends) are especially prone to this because the convenience feature β "just map the JSON to the model" β is the vulnerability.
Practical methodology to find it
- Get the full object shape first. Look at GET responses for the same resource β a
GET /api/v1/users/meresponse often reveals internal field names (role,permissions,tier,discount_percent,is_staff) that the create/update forms never show you. - Replay creation/update requests with extra fields injected. Take a legitimate signup or profile-update request and append every internal field name you found, testing common privilege-related keys:
role,isAdmin,admin,permissions,verified,plan,credits,balance,status. - Test nested objects too β mass assignment isn't just top-level.
{"user": {"name": "x", "role": "admin"}}and array-of-object bodies both need the same treatment. - Try both PATCH and full-object PUT. PATCH endpoints are often more permissive because developers assume "partial update" means "low risk."
- Check price/quantity manipulation in checkout/order APIs. This is mass assignment's financially dangerous cousin: if a checkout endpoint accepts a client-supplied
priceordiscountfield instead of recalculating server-side, you can buy a $500 item for $1.
The fix
Use strict allow-lists (DTOs / serializers with explicit permitted fields) on every write endpoint. Never bind request bodies directly to ORM models. Anything not on the allow-list gets silently dropped, not silently accepted.
3. GraphQL Introspection Abuse & Query-Based Attacks
GraphQL fixes a lot of REST's over-fetching problems β and introduces a completely different attack surface that REST-trained testers routinely miss.
The core idea: introspection
GraphQL APIs can expose a special __schema query that returns the entire data model β every type, every field, every mutation, every argument. It's meant for development tooling (like GraphQL Playground or Apollo Studio), but if left enabled in production, it hands an attacker a complete map of your backend with zero effort:
graphql
query {
__schema {
types {
name
fields {
name
type { name }
}
}
}
}query {
__schema {
types {
name
fields {
name
type { name }
}
}
}
}This single query can reveal internal mutation names like deleteUserAccount, impersonateUser, or adjustAccountBalance that were never meant to be public-facing β no source code access required.
Beyond introspection: the query-shape attacks
Even with introspection disabled, GraphQL has its own bug class that REST checklists have no concept of:
- Batching attacks β sending an array of queries/mutations in a single request to bypass rate limiting. If your rate limiter counts HTTP requests instead of GraphQL operations, an attacker can brute-force a login mutation 500 times in one POST.
- Deeply nested queries (query depth abuse) β GraphQL lets you request relationships within relationships within relationships. A malicious query nesting
user { friends { friends { friends { posts { comments { author { friends... }}}}}}}can force the server to do exponential work, causing denial-of-service with a single request. - Field-level authorization gaps β REST developers are used to authorizing endpoints. In GraphQL there's often one endpoint (
/graphql) for everything, so authorization has to happen per field/resolver. It's extremely common to find that a sensitive field (likeuser.ssnoruser.internalNotes) is reachable through an unexpected query path that the developers didn't think to lock down, even though the "obvious" path is protected. - Alias-based rate-limit bypass β GraphQL aliases let you request the same field multiple times under different names in one call, which can be abused similarly to batching.
Practical methodology
- Always try introspection first, even if you expect it to be disabled:
{"query": "{__schema{types{name}}}"}. Many teams disable it only in the primary environment and forget staging/internal APIs. - If introspection is off, brute-force the schema anyway using tools like
clairvoyanceor wordlist-based query guessing β disabled introspection is not the same as an unknown schema. - Test batching against auth endpoints. Send a login mutation 50 times in a single array-based request and see if the rate limiter catches it.
- Test query depth limits. Build a deliberately nested query against a relational field and watch server response time β if it climbs non-linearly, there's no depth/complexity limit in place.
- Map field-level authorization separately from operation-level. For every sensitive field, try reaching it through at least two different query paths (direct query vs. nested through a relation) and confirm authorization holds on both.
- Check mutation naming for hints of unintended functionality. Introspection results often surface admin/internal mutations that were never meant for the public schema β these are frequently under-protected because they weren't part of the intended "public API surface" in anyone's mental model.
The fix
Disable introspection in every non-development environment. Enforce query cost analysis and maximum query depth server-side (most GraphQL frameworks support this natively β it's usually just not turned on). Authorize at the resolver/field level, not just at the top-level query. Rate-limit by GraphQL operation count, not raw HTTP request count.
The Methodology That Actually Works
If you take one thing from this article, take this: API security testing is fundamentally about broken business logic, not injection payloads. Here's the repeatable process we use:
- Get two accounts minimum, ideally three tiers (admin, regular user, unauthenticated). Almost every serious API bug requires comparing behavior across identities.
- Build a complete request map first. Use the app normally through a proxy (Burp/OWASP ZAP) and capture every request β including the ones triggered by rare UI paths like account deletion, data export, or admin panels. You can't test what you haven't mapped.
- Diff the API surface against the UI surface. Anything the API allows that the UI doesn't expose is your highest-priority target β it usually means it was tested less.
- For every endpoint, ask three questions specifically:
- Can I access this as a different authenticated user? (BOLA)
- Can I set fields the client interface never sends? (Mass Assignment)
- If this is GraphQL, can I reach this data through a path the developers didn't anticipate? (Field-level authz / introspection)
- Automate the boring parts, manually verify the logic. Tools can help you enumerate parameters and replay requests at scale, but the actual "is this authorization broken" judgment call is still a human task. Don't outsource that to a scanner.
- Document impact in business terms, not just technical terms. "BOLA on
/invoices/{id}" gets ignored. "Any authenticated user can view any other customer's unpaid invoices, including full billing addresses and payment history" gets fixed by Monday.
Final Thought
APIs are where the actual money, data, and business logic of modern applications live β yet most security programs still spend the bulk of their budget on web app checklists written for a threat model from a decade ago. BOLA, mass assignment, and GraphQL-specific abuse aren't exotic edge cases. They're the default outcome when API authorization is treated as an afterthought instead of a first-class design requirement.
If you're building or securing APIs in 2026, the checklist isn't "did we run a scanner." It's "did we prove, with two different identities, that every object-level and field-level boundary actually holds."
Want to go deeper?
I run Bugitrix, where we help people build real, practical offensive security skills β not just theory.
- 1:1 Mentorship β personalized guidance if you're serious about breaking into API security / bug bounty / pentesting: Apply here
- Resume, LinkedIn & Portfolio Building β get your profile actually noticed by hiring managers in security: Get started
- Free daily tips & resources on Telegram β real, no-fluff content, no spam: t.me/bugitrix
- More at bugitrix.com
If this breakdown helped you look at APIs differently, follow for more deep-dives like this β no recycled OWASP lists, just what actually shows up when you're testing real targets.