rate-limit-bypass.md (11160B)
1 --- 2 title: "Rate Limit Bypass" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/rate-limit-bypass.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/rate-limit-bypass.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Rate Limit Bypass 14 15 ## Rate limit bypass techniques 16 17 ### Exploring Similar Endpoints 18 19 During an authorized test, compare variations of the protected endpoint, such as `/api/v3/sign-up`, `/Sign-up`, `/SignUp`, `/signup`, `/api/v1/sign-up`, and `/api/sign-up`. A bypass here normally indicates that canonicalization or routing happens after the rate-limit decision. 20 21 ### Incorporating Blank Characters in Code or Parameters 22 23 Test whether the limiter and application normalize delimiters differently. Candidates include a NUL (`%00`), CRLF (`%0d%0a`), CR (`%0d`), LF (`%0a`), horizontal tab (`%09`), form feed (`%0c`), and space (`%20`). For example, `code=1234%0a` may produce a different limiter key even if the application later trims the newline. `%0d%0a` is a two-character line break, not a blank byte; malformed control characters can also be rejected by proxies before reaching the application. 24 25 ### Manipulating IP Origin via Headers 26 27 Test which signal the application uses as the client IP. Spoofable headers matter only when a trusted proxy or the application accepts them from an untrusted client; a secure edge should overwrite them and trust forwarded values only from known proxy hops. Candidate headers include `X-Originating-IP`, `X-Forwarded-For`, `X-Remote-IP`, `X-Remote-Addr`, `X-Client-IP`, `X-Host`, and `X-Forwarded-Host`, including duplicate `X-Forwarded-For` fields. Loopback values are shown for parser testing but can activate separate trust paths, so use controlled addresses first. 28 29 ```bash 30 X-Originating-IP: 127.0.0.1 31 X-Forwarded-For: 127.0.0.1 32 X-Remote-IP: 127.0.0.1 33 X-Remote-Addr: 127.0.0.1 34 X-Client-IP: 127.0.0.1 35 X-Host: 127.0.0.1 36 X-Forwarded-Host: 127.0.0.1 37 38 # Double X-Forwarded-For header example 39 X-Forwarded-For: 40 X-Forwarded-For: 127.0.0.1 41 ``` 42 43 ### Changing Other Headers 44 45 Compare other possible limiter keys such as `User-Agent`, device identifiers, and cookies. If rotating one changes the quota while the account and operation remain identical, document that the limiter is keyed to a client-controlled value. 46 47 ### Leveraging API Gateway Behavior 48 49 Some API gateways key rate limits on a combination of path and parameters. Varying an ignored parameter, such as `/resetpwd?someparam=1`, can reveal that each syntactically distinct request receives a separate quota. 50 51 ### Logging into Your Account Before Each Attempt 52 53 Logging into a controlled account before each attempt, or each small set of attempts, can reveal whether issuing a new session resets the counter. In Burp Intruder, a Pitchfork attack can rotate test credentials or session tokens while following the same redirect flow. Also test whether lockout applies to the account, the source, or both; account lockout must resist distribution across sessions and IP addresses.<sup>[[5]](#references)</sup> 54 55 ### Utilizing Proxy Networks 56 57 With explicit authorization, controlled egress nodes can establish whether a limiter is only source-IP based. Do not use unknown public/open proxies: they can record credentials and may route test traffic through systems whose owners did not consent. 58 59 ### Splitting the Attack Across Different Accounts or Sessions 60 61 If the target system applies rate limits on a per-account or per-session basis, distributing the attack or test across multiple accounts or sessions can help in avoiding detection. This approach requires managing multiple identities or session tokens, but can effectively distribute the load to stay within allowable limits. 62 63 ### Keep Trying 64 65 Note that even if a rate limit is in place you should try to see if the response is different when the valid OTP is sent. In [**this post**](https://mokhansec.medium.com/the-2-200-ato-most-bug-hunters-overlooked-by-closing-intruder-too-soon-505f21d56732), the bug hunter discovered that even if a rate limit is triggered after 20 unsuccessful attempts by responding with 401, if the valid one was sent a 200 response was received.<sup>[[1]](#references)</sup> 66 67 --- 68 69 ### Abusing HTTP/2 multiplexing & request pipelining (2023-2025) 70 71 A misconfigured limiter may count **TCP connections** rather than the HTTP/2 request streams multiplexed inside each connection. Test whether many streams on one authorized connection consume one quota unit or one unit per request; a correct application-level limiter counts the protected operation independently of transport reuse.<sup>[[6]](#references)</sup> 72 73 ```bash 74 # Reuse one HTTP/2 connection for 100 requests containing the same test body 75 printf '%s\n' '{"code":"000000"}' > post.json 76 h2load -n 100 -c 1 -m 100 -d post.json \ 77 -H 'content-type: application/json' https://target/api/v2/verify 78 ``` 79 80 The common `seq | xargs curl` pattern starts many curl processes and therefore does **not** demonstrate a single multiplexed connection. For different OTP values on separate streams, use a client such as Turbo Intruder that supports request variation while retaining connections. 81 82 If the limiter protects only `/verify` but not `/api/v2/verify`, you can also combine **path confusion** with HTTP/2 multiplexing for *extremely* high-speed OTP or credential brute-forcing. 83 84 > 🐾 **Tip:** PortSwigger’s [Turbo Intruder](https://portswigger.net/research/turbo-intruder) supports HTTP/2 and lets you fine-tune `maxConcurrentConnections` and `requestsPerConnection` to automate this attack. 85 86 ### GraphQL aliases & batched operations 87 88 GraphQL aliases allow one operation to invoke a field several times under different response names. If throttling counts only HTTP requests rather than sensitive resolver invocations, aliases can bypass login or password-reset limits; properly instrumented resolvers still enforce a limit per attempt.<sup>[[2]](#references)</sup> 89 90 ```graphql 91 mutation bruteForceOTP { 92 a: verify(code:"111111") { token } 93 b: verify(code:"222222") { token } 94 c: verify(code:"333333") { token } 95 # … add up to dozens of aliases … 96 } 97 ``` 98 99 Inspect each alias's field or error entry in the single GraphQL response. There is only one HTTP status for the entire response, so an individual alias does not independently “return 200”; the successful field may instead contain a token while other fields contain errors or null values. 100 101 PortSwigger's GraphQL testing guidance demonstrates the alias-based rate-limit bypass.<sup>[[2]](#references)</sup> 102 103 ### Abuse of *batch* or *bulk* REST endpoints 104 105 Some APIs expose helper endpoints such as `/v2/batch` or accept an **array of objects** in the request body. If the limiter is placed in front of the *legacy* endpoints only, wrapping multiple operations inside a single bulk request may completely sidestep the protection. 106 107 ```json 108 [ 109 {"path": "/login", "method": "POST", "body": {"user":"bob","pass":"123"}}, 110 {"path": "/login", "method": "POST", "body": {"user":"bob","pass":"456"}} 111 ] 112 ``` 113 114 ### Timing the sliding-window 115 116 A **fixed-window** limiter resets on a boundary (for example, every minute). If that boundary is known from a response such as `X-RateLimit-Reset: 27`, send the maximum authorized test burst just before it and another immediately afterward. Sliding-window, token-bucket, and leaky-bucket algorithms do not all have the same hard reset behavior. 117 118 ```text 119 |<-- 60 s window ‑->|<-- 60 s window ‑->| 120 ###### ###### 121 ``` 122 123 This boundary burst can approach twice the nominal fixed-window allowance over a short interval. 124 125 ### Upgrading to WebSockets / gRPC streaming after the handshake 126 127 Some edge products inspect the initial WebSocket upgrade request but not the messages inside the established connection. Cloudflare documents this limitation for its WAF; an application-level WebSocket limiter may still inspect and throttle every message. gRPC behavior depends on the proxy and application and must be tested separately.<sup>[[3]](#references)</sup> 128 129 Practical workflow: 130 131 ```bash 132 # Flood 1,000 OTP guesses through a single WebSocket connection 133 seq -w 000000 000999 | websocat -n ws://target.tld/api/verify-ws 134 135 # gRPC streaming: send multiple Verify requests in one stream 136 grpcurl -d @ -plaintext target.tld:50051 service.VerifyOTP/Stream <<'EOF' 137 { "code": "111111" } 138 { "code": "222222" } 139 { "code": "333333" } 140 EOF 141 ``` 142 143 If the same authorized login or OTP operation is exposed through HTTP and WebSocket/gRPC variants, compare whether every message consumes the same logical quota. 144 145 ### Exploiting CDN PoP‑sharded counters 146 147 Some CDNs shard rate-limit counters **per data center/PoP instead of globally**. Cloudflare documents that its counters are not shared across data centers, which can allow one quota per PoP for the same key.<sup>[[4]](#references)</sup> Validate this only with controlled egress nodes included in the test authorization. 148 149 Example layout using a tester-owned proxy list: 150 151 ```bash 152 for p in $(cat proxies.txt); do 153 HTTPS_PROXY=$p curl -s -X POST https://target/api/login -d @payload.json & 154 done 155 wait 156 ``` 157 158 Make sure the limiter key is not per-account; otherwise also rotate user IDs / session tokens. 159 160 --- 161 162 ## Tools 163 164 - [**https://github.com/Hashtag-AMIN/hashtag-fuzz**](https://github.com/Hashtag-AMIN/hashtag-fuzz): Fuzzing tool that supports header randomisation, chunked word-lists and round-robin proxy rotation. 165 - [**https://github.com/ustayready/fireprox**](https://github.com/ustayready/fireprox): Creates AWS API Gateway endpoints that can be used to evaluate source-IP-based throttling in an authorized environment; usage incurs AWS resources and costs. 166 - **Burp Suite – IPRotate + extension**: Uses a pool of SOCKS/HTTP proxies (or AWS API Gateway) to rotate the source IP transparently during *Intruder* and *Turbo Intruder* attacks. 167 - **Turbo Intruder (BApp)**: High-performance attack engine supporting HTTP/2 multiplexing; tune `requestsPerConnection` to 100-1000 to collapse hundreds of requests into a single connection. 168 169 ## References 170 171 - [1] [The $2,200 ATO most bug hunters overlooked by closing Intruder too soon](https://mokhansec.medium.com/the-2-200-ato-most-bug-hunters-overlooked-by-closing-intruder-too-soon-505f21d56732) 172 - [2] [PortSwigger Web Security Academy – GraphQL API vulnerabilities: Bypassing rate limiting using aliases](https://portswigger.net/web-security/graphql) 173 - [3] [Cloudflare Docs – WebSockets & WAF applicability](https://developers.cloudflare.com/network/websockets/) 174 - [4] [Cloudflare Docs – Request rate calculation and PoP-local counters](https://developers.cloudflare.com/waf/rate-limiting-rules/request-rate/) 175 - [5] [OWASP WSTG — Testing for Weak Lock Out Mechanism](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/04-Authentication_Testing/03-Testing_for_Weak_Lock_Out_Mechanism) 176 - [6] [PortSwigger Research — HTTP/2: The Sequel is Always Worse](https://portswigger.net/research/http2)