8 min read

API Security
Beginner
API4 API6

Rate Limiting & Resource Abuse

APIs that lack proper rate limiting are vulnerable to Denial of Service (DoS), brute force attacks, and resource exhaustion. Attackers can also abuse business logic flows (like purchasing or voting) if they are not rate-limited.

Authorization boundary

Load and flow testing degrades a live service: agree a request ceiling and a test window with the client first, run the burst against staging where one exists, and stop on the first 5xx wave — a self-inflicted outage is a finding for you, not for them.

Rate Limiting Detection

First, determine if rate limiting is in place and what the limits are.

Basic testing

Send a burst of requests and check the response codes.

100 sequential requests
for i in {1..100}; do
  curl -s -o /dev/null -w "%{http_code}\n" \
    -H "Authorization: Bearer $TOKEN" \
    https://api.target.com/api/users
done | sort | uniq -c

Response headers

Check for headers that indicate rate limits.

Reading the rate-limit headers
curl -v https://api.target.com/api/users 2>&1 | grep -i rate

# Common headers:
# X-RateLimit-Limit: 100
# X-RateLimit-Remaining: 99
# X-RateLimit-Reset: 1640000000
# Retry-After: 60

Rate Limit Bypass

If rate limiting is detected, try to bypass it using various techniques.

1. IP rotation

If limits are IP-based, rotate your IP address.

Rotating the source IP
curl --proxy socks5://127.0.0.1:9050 https://api.target.com/api/users

2. Header manipulation

Spoof your IP address using headers like X-Forwarded-For.

Spoofed client-IP headers
curl -H "X-Forwarded-For: 127.0.0.1" https://api.target.com/api/users
curl -H "X-Real-IP: 10.0.0.1" https://api.target.com/api/users
curl -H "X-Originating-IP: 192.168.1.1" https://api.target.com/api/users
curl -H "X-Client-IP: 172.16.0.1" https://api.target.com/api/users

3. Endpoint variation

Try different versions or case variations of the endpoint.

  • /api/v1/users
  • /api/v2/users
  • /api/users
  • /v1/api/users
  • /api/Users
  • /API/USERS
  • /Api/Users
  • /api/users/
  • /api/users%20
  • /api/users%00

Resource Consumption (API4)

Test for endpoints that consume excessive resources.

Large response testing

Request large amounts of data via pagination or limit parameters.

Oversized page sizes
GET /api/users?limit=10000
GET /api/users?page=1&per_page=999999
GET /api/search?q=a&results=all

Deep pagination

Requesting very high page numbers can stress the database.

Deep pagination
GET /api/items?page=1000000

Field expansion

Requesting all fields or nested relations can be expensive.

Expensive field selection
GET /api/users?fields=*
GET /api/users?include=all,nested,relations

Batch requests

Send a large number of operations in a single batch request.

Batch endpoint fan-out
POST /api/batch
Content-Type: application/json

{"ids": [1,2,3,...thousands of IDs...]}

Sensitive Business Flows (API6)

Resource consumption is about the cost of one request. This category is about a legitimate sequence being repeated at machine speed, where every individual call is small, cheap and correctly authorized — and the business outcome is still catastrophic. A thousand requests can each be fine while the thousandth order drains the last unit of stock, and no volume limit ever fires.

1. Enumerate flows, not endpoints

Walk the product the way a user does and write down each multi-step flow: registration and verification, add-to-cart and checkout, invite and referral credit, password reset, coupon redemption, posting, voting, reservation holds, refunds and cancellations.

2. Write down the invariant

Each flow has a rule the business believes is true: one per customer, one coupon per order, one vote per account, a hold expires in ten minutes, a refund cannot exceed the charge. The vulnerability is the gap between that rule and what the API enforces.

3. Break it with concurrency, not volume

Sequential requests usually behave. Send the same request in parallel so every worker passes the "has this account already redeemed?" check before any of them commits:

coupon-race.sh
# 50 simultaneous redemptions of one single-use coupon, one valid session
redeem() {
  curl -s -o /dev/null -w "%{http_code}\n" \
    -X POST https://api.target.com/api/coupons/redeem \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"code":"ONCE-ONLY"}'
}
for i in $(seq 50); do redeem & done | sort | uniq -c

# More than one 200 means the check-then-act is not atomic

4. Price the damage

A finding is only actionable with a number: units of stock held, margin lost per abusive order, credits minted per referral loop, minutes of human support consumed per fake registration. Measure it on the engagement's fixtures, never by burning a real inventory.

5. Per-IP limits do not help here

Abuse of an authenticated flow needs one valid session, not many addresses. If the only control is an IP rate limit, an attacker with a single account and one residential connection is already inside it — and a second account defeats per-account limits only if nothing binds the two (device, payment instrument, verified email domain).

Rate limit bypass headers

Many APIs trust client-provided headers to identify the originating IP. Try adding these headers to bypass per-IP limits: X-Forwarded-For: 127.0.0.1, X-Real-IP: 10.0.0.1, CF-Connecting-IP: 1.2.3.4, True-Client-IP: 1.2.3.5. Increment the fake IP on each request to simulate rotating IPs.

Evidence Collection

For API4 or API6, capture:

The burst, with per-request results: Timestamps and status codes for every request in the batch, plus the same request sent serially as the control — an average hides the burst that matters.

The identity each request carried: Token, key, tenant and any address headers, so the report shows which dimension the counter keys on and which one moved the limit.

A cost number: Units consumed per request on the engagement fixtures — stock held, credits minted, margin lost, provider spend — because an unpriced finding is not actionable.

False Positive Identification

  • A 429 at the documented limit: If the docs state a batch size or a per-minute quota and the limit fires one request past it, the control works. Send n+1 against the published number, not 5n against nothing.
  • Contention mistaken for a missing invariant: A parallel run where most workers return 500 or time out has measured latency, not a double-spend. Re-run and require two distinct successes that both committed — two 200s and one balance change is a pass.

Testing Checklist

Consumption (API4)

  • ☐ Probe each authenticated endpoint past its documented limit, then rotate token, key and spoofed address headers one at a time to see which one resets the counter.
  • ☐ Try unbounded page size, unbounded array length and GraphQL batching on the same identity before concluding the limit is global.

Business flows (API6)

  • ☐ Fire the single-use action (coupon, refund, hold, vote) in parallel and confirm whether two commits landed.
  • ☐ Price the abuse on fixtures, and note whether per-account or per-instrument limits exist at all.

Remediation & Defense

Two different controls are needed, and neither is a network appliance: a cost ceiling per identity for API4, an atomic business invariant for API6.

Quotas keyed on identity, not headers

  • • Token bucket per token subject, per API key and per tenant, enforced close to the resource. An edge control keyed on X-Forwarded-For is bypassed by the same header the tests above use — only trust a header your own edge sets and strips.

Cap the cost of one request

  • • Maximum body size, maximum page size, a required upper bound on limit, query cost limits for GraphQL, and a timeout on every outbound call the request makes.

Make the invariant atomic

  • • Enforce one-redemption-per-account with a unique constraint or a conditional update at commit, not a read-then-write in application code: single-use has to mean single-use under concurrency.

Budget and alert on money, not 5xx

  • • Where requests cost money downstream, set a spend cap with the provider and alert on the KPI the flow moves — redemptions per hour, holds per account — so abuse is visible before it is expensive.

Idempotency for submit endpoints

  • • Accept an idempotency key on checkout, payment and refund calls so a retry or duplicated request cannot apply the action twice, and return the first result for a repeated key.

Friction on high-value flows

  • • Step-up authentication, human verification and device binding on account creation, checkout and password reset — driven by risk signals such as velocity, instrument reuse and disposable email domains.

Financial impact of missing rate limits

APIs billed per-request (OpenAI, Twilio, AWS) can generate unbounded cost if an attacker floods them unrestricted: the limit is the provider's, not the victim's, and the invoice arrives before the alert does. Always verify rate limiting on all authenticated endpoints, especially those that trigger external API calls, and set a hard spend cap at the provider as the last line of defence.

Rate Limiting Practice

Test rate limit detection and bypass techniques on real vulnerable APIs.

crAPI — Rate Limit Bypass Custom Lab easy
Header spoofingIP rotationEndpoint variation
PortSwigger — Brute Force Labs PortSwigger medium
IP rotation via headersAccount lockout bypassRate limit enumeration