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
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.
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 -cResponse headers
Check for headers that indicate rate limits.
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: 60Rate 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.
curl --proxy socks5://127.0.0.1:9050 https://api.target.com/api/users2. Header manipulation
Spoof your IP address using headers like X-Forwarded-For.
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/users3. 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.
GET /api/users?limit=10000
GET /api/users?page=1&per_page=999999
GET /api/search?q=a&results=allDeep pagination
Requesting very high page numbers can stress the database.
GET /api/items?page=1000000Field expansion
Requesting all fields or nested relations can be expensive.
GET /api/users?fields=*
GET /api/users?include=all,nested,relationsBatch requests
Send a large number of operations in a single batch request.
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:
# 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 atomic4. 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
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
Rate Limiting Practice
Test rate limit detection and bypass techniques on real vulnerable APIs.