DEV Community

Cover image for How I Find Excessive Data Exposure in APIs
Abhinav Singwal
Abhinav Singwal

Posted on

How I Find Excessive Data Exposure in APIs

Excessive Data Exposure in APIs

I was testing a fintech app's "View Profile" feature. The mobile app showed my name and avatar. Burp Suite showed something else entirely:

{
  "name": "John Doe",
  "avatar": "/images/avatar.jpg",
  "email": "john@example.com",
  "phone": "+1234567890",
  "password_hash": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
  "reset_token": "a7b3f9e2c4d1g6h8j9k0l",
  "internal_risk_score": 42,
  "admin_notes": "Flagged for unusual activity",
  "last_login_ip": "192.168.1.100"
}
Enter fullscreen mode Exit fullscreen mode

The app only showed name and avatar. The API returned everything about me - including my password hash and an active reset token.

That was my first Excessive Data Exposure bounty. $1,500 for reading my own data.

Let me show you how to find these everywhere.


What Even Is Excessive Data Exposure?

Simple definition: The API gives you more data than you need to see.

Technical definition: The backend queries SELECT * FROM users and sends the entire database row to the frontend, even though the UI only displays 2 of the 15 columns.

The scary part? You don't need broken authentication. You don't need SQL injection. You just need to look at the raw API response.


The 9 Endpoints Where I Always Find This

1. User Profile Endpoints

GET /api/users/{id} or GET /api/users/me

What to look for:

  • email, phone, address
  • password_hash, salt
  • reset_token, verify_token
  • two_fa_secret, backup_codes
  • api_keys, session_tokens

Bug bounty example: I once found stripe_customer_id and subscription_billing_cycle on a /me endpoint. The company paid $500 because they considered it internal architecture exposure.

2. Search Endpoints

GET /api/search?q=john

What to look for:

  • Full emails instead of truncated j***@example.com
  • Exact timestamps of last login
  • Internal user IDs used for other API calls

Pro tip: Even if you can't IDOR to other users, search your own email. The API might return extra fields on your own result that shouldn't be visible to you.

3. Registration / Signup Responses

POST /api/register

What to look for:

  • Your password echoed back in plaintext (yes, this happens)
  • Your session token in the response body
  • Internal account flags like "is_verified": false, "requires_review": true

4. Order History

GET /api/orders/12345

What to look for:

  • Credit card last4, expiry, billing zip
  • Full shipping address
  • Internal cost vs retail price (business logic exposure)
  • refund_eligibility flags

5. File Upload Metadata

POST /api/upload → returns file_idGET /api/files/{file_id}

What to look for:

  • Internal S3 bucket paths (exposes infrastructure)
  • Original filenames (could contain PII like resume_ssn_123.pdf)
  • Uploader's IP address and user agent
  • file_permissions array showing who can access

6. GraphQL Endpoints (The Jackpot)

POST /graphql

What to do: Request fields you shouldn't see even on your own account:

query {
  me {
    name
    email
    password_hash   # ← try it
    reset_token     # ← try it
    internal_notes  # ← try it
    admin_flags     # ← try it
  }
}
Enter fullscreen mode Exit fullscreen mode

Real bounty: A bug hunter found credit_card and ssn fields exposed on the /me query of a major credit bureau. $7,500 bounty.

7. Analytics & Dashboard Endpoints

GET /api/dashboard/stats

What to look for:

  • Other users' emails or IPs in event logs
  • Database connection strings
  • Debug arrays containing full request/response objects

8. Export / Download Endpoints

GET /api/export/users.csv

What to look for: If you can access this endpoint at all (auth bypass or misconfigured role), the CSV often contains:

  • Full database columns including sensitive ones
  • Password reset hashes
  • 2FA secrets (mangled or plain)

9. PATCH / PUT Responses

PATCH /api/users/me (with empty body {})

Why this works: When you send an update with no changes, some APIs still return the full updated object - which might contain fields you never had permission to read.


My 4-Step Testing Methodology

Step 1: Intercept Everything

Stop trusting what you see in the browser/mobile app. Forward every request through Burp Suite, Caido, or ZAP.

The raw response always tells the truth.

Step 2: Build Your Keyword Radar

I search responses for these strings (I keep this as a grep pattern):

email|e-?mail|phone|mobile|telephone|address|location|
ssn|tax|tin|passport|license|driver|
birthday|dob|age|
password|pass|pwd|secret|token|jwt|api[_-]?key|apikey|
hash|salt|reset[_-]?token|verify[_-]?token|
credit[_-]?card|cc|cvv|expiry|stripe|paypal|
ip[_-]?address|user[_-]?agent|device[_-]?id|
internal[_-]?note|admin[_-]?note|flag|reason[_-]?code|
otp|mfa|two[_-]?fa|backup[_-]?code
Enter fullscreen mode Exit fullscreen mode

Step 3: Compare Responses Across IDs

If you can change ?user_id=1 to ?user_id=2, do it. But even without IDOR, compare:

  • Authenticated vs unauthenticated
  • Your low-privilege account vs a different low-privilege account
  • Verbose param (?verbose=true) vs normal

Tool tip: Use Burp's Comparer or the Diffy extension to spot extra JSON keys.

Step 4: Trigger Verbose Modes

Add these parameters to every request:

?verbose=true
?debug=1
?include_fields=all
?format=full
?fields=*
?pretty=true
?show_internal=true
Enter fullscreen mode Exit fullscreen mode

Top comments (0)