APIs are where the testing payoff is highest, for two reasons. They're often your most exposed surface, and they don't get the browser's built-in protections that a rendered page does, no same-origin policy doing quiet work in the background. An API does exactly what the request tells it to. So you test them by sending the requests a well-behaved client never would.
Find every endpoint, not just the documented ones
Start by building the most complete map you can, because you can't test what you haven't found:
Documented sources: an OpenAPI or Swagger spec, GraphQL introspection, gRPC reflection, the client's JavaScript, captured mobile traffic.
Undocumented endpoints, which you fuzz for:
ffuf -u https://api.example.com/api/FUZZ -w api-endpoints.txt -mc 200,401,403
A 401 or 403 is a positive result here: it means the endpoint exists and wants auth. Now you know it's there.
REST testing
Work through these systematically against every endpoint:
- Auth bypass. Call each endpoint with no token at all and see what answers.
- Broken Object Level Authorisation, the single most common API vulnerability. Use one user's token to request another user's object:
GET /api/users/102/documents/5002 # With user 101's token, should be 403 not 200
- Function-level authorisation. As a regular user, try
POST /api/admin/users. - Mass assignment. Send
PATCH /api/users/me {"role": "admin"}and see if it sticks. - Rate limiting. Fire 100 requests. Does anything return a 429?
- Excessive data exposure. Read the responses. Are they handing back
passwordHashor internal IDs you never asked for?
GraphQL
GraphQL has its own attack surface, mostly stemming from its flexibility:
- Introspection.
{ __schema { types { name } } }should be disabled in production. If it answers, you've got the whole schema. - Depth attacks. Deeply nested queries that exhaust the server.
- Batching. Multiple mutations in one request can slip past rate limits that count requests.
- Resolver authorisation. Reaching data through a relationship can bypass a check that only guarded the direct path.
gRPC
The reflection service, if it's on, lists the methods for you:
grpcurl -plaintext localhost:50051 list
grpcurl -d '{"user_id": 102}' -H "auth: $USER_A_TOKEN" localhost:50051 myapp.UserService/GetUser
Then test the same authorisation questions you'd ask of REST.
The discipline that ties it together: map everything including the undocumented, test auth bypass on each endpoint, run BOLA on every ID you can find, and check for mass assignment and over-exposed responses. The OWASP API Security Top 10 is a good structure to test against, so you're covering categories rather than guessing.
