API Rate Limiting & Abuse
OWASP API #4 & #5. Brute-force login via API (no rate limit = unlimited attempts), resource exhaustion via pagination abuse, GraphQL deep query cost attacks, and how API gateways throttle.
OWASP API #4 & #5. Brute-force login via API (no rate limit = unlimited attempts), resource exhaustion via pagination abuse, GraphQL deep query cost attacks, and how API gateways throttle.
APIs do not fail secure by default. OWASP API Security Top 10 ranks Unrestricted Resource Consumption (API #4) and Broken Function Level Authorization (API #5) among the most frequently exploited API flaws. Without rate limiting and proper authorisation checks, a single endpoint can bring down a database, leak millions of records, or let attackers impersonate administrators.
Rate limiting failure (OWASP API #4) occurs when an API does not cap the number of requests a client can make. This opens the door to credential stuffing — attackers try thousands of username/password pairs against a login endpoint — and resource exhaustion, where a single client floods the database with expensive queries. A common vector is pagination abuse: sending requests like ?page=1000&limit=100 forces the database to scan and offset millions of rows on every request.
Broken Function Level Authorisation (OWASP API #5) is different but equally dangerous. An API might expose an admin panel at /api/admin/users without checking whether the caller has an admin role. Attackers discover these endpoints through enumeration, JS source inspection, or leaked API documentation. Once found, they can delete users, modify permissions, or exfiltrate data without authentication bypass — just a missing role check.
GraphQL APIs introduce a third class of abuse: cost attacks. A query like friends { friends { friends { ... } } } can nest arbitrarily deep. Without depth limiting or cost analysis, a single request can trigger hundreds of database queries and exhaust server resources.
Try sending requests to a simulated API endpoint. Watch the rate limit counter increase and see what happens when you exceed the threshold. Can you find the hidden admin endpoint?
No requests yet.
In August 2021, attackers exploited an API rate limiting failure in T-Mobile's customer portal. The API endpoint used for account PIN verification did not enforce any request cap, allowing the attackers to brute-force PINs — a four-digit numeric code — by sending thousands of requests per minute. Once they had the correct PIN for an account, they could access the customer's name, date of birth, billing address, and Social Security number.
The breach exposed personal data for approximately 37 million current and former T-Mobile customers. The root cause was not an authentication bypass or SQL injection — it was the absence of rate limiting on an API endpoint that should have been locked down to a few attempts per minute. T-Mobile later implemented per-endpoint rate limits and introduced CAPTCHA on sensitive endpoints, but the damage had already been done.
This case is a textbook example of OWASP API #4: Unrestricted Resource Consumption. The endpoint was functional and the authorisation logic was correct — the PIN was verified server-side — but the lack of a rate limit turned a cryptographically sound verification into a brute-force target. A token bucket rate limiter set to 5 requests per minute would have made the attack impractical.
Mitigation requires defence in depth at three layers: the API gateway, the application server, and the database. The gateway should enforce per-endpoint rate limits using the token bucket algorithm, with different limits for authenticated and unauthenticated users. The application server must check function-level authorisation on every endpoint, ideally through middleware that maps each route to a required role or permission.
// Token bucket rate limiter (middleware)
const buckets = new Map<string, { tokens: number; lastRefill: number }>();
function rateLimit(endpoint: string, maxTokens = 10, refillPerSec = 1) {
const key = endpoint;
let bucket = buckets.get(key);
if (!bucket) {
bucket = { tokens: maxTokens, lastRefill: Date.now() };
buckets.set(key, bucket);
}
const elapsed = (Date.now() - bucket.lastRefill) / 1000;
bucket.tokens = Math.min(maxTokens, bucket.tokens + elapsed * refillPerSec);
bucket.lastRefill = Date.now();
if (bucket.tokens < 1) {
return { status: 429, body: 'Too Many Requests' };
}
bucket.tokens -= 1;
return { status: 200 };
}
// Function-level authorisation middleware
function requireRole(role: 'user' | 'admin' | 'super_admin') {
return (req: Request, res: Response, next: NextFunction) => {
const userRole = req.user?.role;
if (!userRole || userRole !== role) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// GraphQL depth limiter
function limitQueryDepth(document: DocumentNode, maxDepth = 5): boolean {
function depth(selectionSet: SelectionSetNode, d: number): number {
if (d > maxDepth) return d;
return Math.max(...selectionSet.selections.map((sel) =>
sel.kind === 'Field' && sel.selectionSet
? depth(sel.selectionSet, d + 1)
: d
));
}
return depth(document.definitions[0] as any, 1) <= maxDepth;
}
// Pagination cap
app.get('/api/items', (req, res) => {
const page = Math.min(parseInt(req.query.page as string) || 1, 100);
const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
// safe bounded pagination
});1.What is the primary difference between rate limiting failure and broken function level authorisation?
2.How did attackers exploit T-Mobile's API in the 2021 breach?
3.Which technique prevents GraphQL resource exhaustion?