DAST scanner · deep depth · auth-aware

Rate Limit Scanner

A login form that answers every request will answer ten thousand of them. The scanner picks the endpoints where guessing pays (login, signup, password, reset, token, otp) and measures what the server does under load: how many requests pass before the first 429, what happens when a burst arrives at once, and whether switching to a new identity resets the counter.

DASTHighMediumAuth Weakness3 measurements--depth deep only

Counting to the first 429

An endpoint is tested when its URL contains /login, /signin, /auth, /signup, /register, /password, /reset, /forgot, /token or /otp. The first measurement sends the endpoint's own request over and over, one at a time, and waits for a 429.

Which endpoints URL contains any of: /login /signin /auth /signup /register /password /reset /forgot /token /otp each endpoint keeps its own method; the request carries no body Run A · a limiter exists one request every 0.05 s, up to 150 429 INFO · 0.90 limit after 20 accepted requests reported, not a fault Run B · no limiter the same loop against another endpoint … 150 HIGH · 0.85 150 of 150 requests, no 429 "No rate limiting on critical endpoint"

1The scanner filters the endpoints by URL substring, then works through them one at a time, within a shared time budget. If a session was captured, it rides along, so the numbers describe the logged-in user's limit.

2It sends the endpoint's own request sequentially, 0.05 s apart, up to 150 times, and counts every reply that is not a 429. This limiter lets 20 through, then answers 429.

3A 429 at all means a limiter exists. The scanner records the count as an INFO finding, confidence 0.90: a measured threshold you can judge against your own policy, not a vulnerability.

4This second endpoint never answers 429. The status of the replies does not matter, only that none is a 429, so even a stream of 401s from a login form counts as accepted.

5150 requests and no 429 is HIGH, confidence 0.85. That is the brute-force and credential-stuffing condition: the attacker is never slowed down.

Concurrent bursts

A limiter that counts per minute may not react to a slow loop but still trip on a flood. The second measurement sends requests all at once, in three sizes.

Burst of 10 all sent together 10 accepted no 429 in this burst: no finding wait 5 s, try the next size Burst of 50 all sent together 30 accepted · 20 × 429 INFO · 0.90 "Rate limit threshold measured" about 30 requests per burst of 50 Burst of 100 only if no mix yet never sent: the loop stops at the first mixed burst A burst counts only if it holds both a 429 and at least one other reply. A burst that passes entirely stays silent. Missing limits are reported by the sequential run.

1The scanner launches 10, then 50, then 100 requests at once with asyncio.gather, always against the same endpoint and with the endpoint's own method.

2All 10 get through and none is a 429. The burst produces no finding, and the scanner waits 5 seconds so the counter can reset before the next size.

3In the burst of 50, 30 replies are accepted and 20 are 429. A mixed burst is INFO, confidence 0.90: it reports roughly where the limiter bites, so you can compare it with what you intended. It is skipped when the sequential run already measured a threshold.

4The scanner stops at the first burst that shows a threshold, and the whole burst phase is skipped when the sequential run already measured one. The burst of 100 is never sent.

New identity, same address

The last measurement asks what the limiter keys on. It exhausts the limit with one made-up identity, then tries a second made-up identity from the same machine.

Scanner one IP address two fake identities Identity alpha Authorization: Bearer test-rate-limit-user-alpha up to 20 requests, 0.05 s apart, until a 429 429 reached: the limit is triggered Identity beta Authorization: Bearer test-rate-limit-user-beta one request, right after 200: not refused Decision alpha never reached 429: stop, silent beta also 429: stop, silent beta passed: MEDIUM · 0.90 the limiter is keyed on the client-supplied Authorization header, not the address: rotating it bypasses the limit An injected session is blanked for these requests The crawl's Cookie or Authorization header is set to "" so alpha and beta really are two different callers. The other two measurements keep the session.

1The scanner invents an identity, Bearer test-rate-limit-user-alpha, and sends requests with it.

2It stops at the first 429, after at most 20 requests. If 20 requests never trigger a 429 there is nothing to compare and the test ends with no finding.

3Straight away it sends one request from the same address with a different invented identity, Bearer test-rate-limit-user-beta.

4If beta is refused too, the limiter ignores who is asking and the scanner reports nothing. If beta passes, MEDIUM, confidence 0.90: changing the Authorization header was enough to get a fresh allowance.

5On an authenticated scan, the crawl's session would otherwise ride on both identities and make them the same user. This test is the one exception to session injection: it blanks every injected header and sets only the test one.

Read the wording with care

The trigger is "a new identity from the same address was not refused". The finding keeps the title "Rate limit appears IP-based only" so existing baselines still match, but its description and detail now state the actual condition: the limiter is keyed on the Authorization header. The trigger itself is unambiguous: the allowance reset when only the Authorization header changed. Treat it as a limit that is keyed on a client-controlled value.

Why it matters

Why it matters

Without rate limiting, attackers can brute-force passwords at thousands of tries per minute against a login endpoint, run credential stuffing with stolen username and password pairs from other breaches, enumerate accounts with rapid-fire email checks, and overwhelm the server with requests.

iCloud, "The Fappening" (2014)

Apple's Find My iPhone API lacked rate limiting, allowing attackers to brute-force celebrity iCloud passwords. This led to the mass leak of private photos.

Dunkin' Donuts (2015)

Credential-stuffing attacks against Dunkin's app, which had no rate limiting, took over customer accounts and drained stored value.

Vulnerable vs fixed code

// BAD: every attempt is checked, none is ever refused
export async function POST(request: Request) {
  const { email, password } = await request.json()
  const user = await verifyLogin(email, password)
  // ... login logic
}
Key the limit on something the client cannot choose

The identity measurement flags limits that follow a header the caller sets. Key on the network address, or on the account being attacked, and the new-identity trick no longer resets the counter.

Limits and configuration

MeasurementRequests per endpointReports whenSeverity · confidence
Sequential runup to 150, 0.05 s apartno 429 in 150 requests; with a 429, the accepted count is reportedHigh · 0.85 / Info · 0.90
Concurrent bursts10, 50, 100 at once, 5 s betweena burst holds both a 429 and a non-429 reply; stops at the firstInfo · 0.90
Identity switchup to 20, then 1429 for alpha, then beta is not 429Medium · 0.90
What it deliberately stays quiet about

It runs only at --depth deep, with a 900 s timeout and a time budget checked between endpoints, because the three measurements can send over 300 requests to each one. It uses a plain HTTP client on purpose, not the throttled one the other scanners use. Requests carry no body and no credentials of their own, so a limiter that only counts failed logins, or only reacts to a particular payload, may never trip. A dropped connection ends the sequential loop as inconclusive (a reset or WAF throttle): no High "no rate limiting" finding is raised, and the responses received before the cut-off are reported as an Info threshold.