Skip to content

Commit b81c147

Browse files
authored
Merge pull request #2115 from HackTricks-wiki/research_update_src_pentesting-web_xs-search_event-loop-blocking-+-lazy-images_20260410_132417
Research Update Enhanced src/pentesting-web/xs-search/event-...
2 parents 73250bb + 328d227 commit b81c147

1 file changed

Lines changed: 45 additions & 1 deletion

File tree

src/pentesting-web/xs-search/event-loop-blocking-+-lazy-images.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ This is a **different exploit for the CTF chall** that was already commented in
1111
connection-pool-example.md
1212
{{#endref}}
1313

14+
This technique is useful when the attacker can create a **Boolean oracle** based on whether a **lazy-loaded image** is fetched or not, but **cannot** directly observe that request because of CSP, `img-src` restrictions, or `Cache-Control: no-store`. Instead of waiting for an external callback, the exploit converts image loading into a **timing side channel** by making those image requests compete with other requests.
15+
1416
The idea behind this exploit is:
1517

1618
- The posts are loaded alphabetically
@@ -20,6 +22,12 @@ The idea behind this exploit is:
2022
- If the **images** injected in the post are being **loaded**, these **fetch** requests will take **longer**, so the attacker knows that the **post is before the flag** (alphabetically).
2123
- If the the **fetch** requests are **fast**, it means that the **post** is **alphabetically** **after** the flag.
2224

25+
In other words, the oracle is:
26+
27+
- **State 1**: the attacker-controlled post is within the browser lazy-loading threshold, so `img loading=lazy` requests are issued.
28+
- **State 2**: the attacker-controlled post remains outside that threshold, so those requests are not issued.
29+
- **Leak**: the attacker measures whether those extra requests create enough contention to delay another measurable operation.
30+
2331
Let's check the code:
2432

2533
```html
@@ -154,6 +162,42 @@ Let's check the code:
154162
</html>
155163
```
156164

157-
{{#include ../../banners/hacktricks-training.md}}
165+
## Practical caveats
166+
167+
This trick is **fragile** and needs to be **calibrated per environment**:
168+
169+
- The **lazy-loading distance threshold** is browser-dependent and can change with browser version, connection type, and headless/headful mode. Chromium loads off-screen images **before** they are visible, so the right `<canvas height>` is usually found empirically.
170+
- In practice, **headless Chromium** can require a **different threshold** than a normal browser. In the original writeup, a value that worked locally (`1850px`) had to be increased for the remote headless bot (`3350px`).
171+
- Native `loading="lazy"` is only **deferred when JavaScript is enabled**, so this specific oracle can disappear if the browser disables JS or changes lazy-loading behavior for privacy reasons.
172+
- If the image response is **cacheable**, later probes become noisy or useless because the browser may satisfy the request from cache. This is why cache-busting parameters or `Cache-Control: no-store` matter a lot when testing this technique.
173+
174+
## Reliability notes
175+
176+
Compared to the related [connection pool example](connection-pool-example.md), this variant does **not** need an external image callback. It only needs a measurable slowdown. That slowdown can come from:
177+
178+
- **Server-side event-loop blocking**, such as many image requests hitting a Node.js endpoint that performs synchronous work.
179+
- **Socket / connection contention**, where the attacker saturates available connections and times how long an additional request takes.
180+
181+
To make the oracle more stable:
158182

183+
- Use **multiple lazy images** instead of one.
184+
- Add a **cache-buster** to every image URL.
185+
- Measure **several requests** and compare an **average/median** instead of trusting a single sample.
186+
- Recalculate the **canvas height threshold** against the same browser family and execution mode used by the victim bot.
187+
188+
For more timing-based leak primitives, also check:
189+
190+
{{#ref}}
191+
performance.now-+-force-heavy-task.md
192+
{{#endref}}
193+
194+
> [!WARNING]
195+
> Some privacy-focused defenses can break the "load only after a browser-driven scroll/viewport change" assumption. For example, XS-Leaks wiki documents `Document-Policy: force-load-at-top` as a way to disable load-on-scroll behaviors such as Scroll-to-Text navigation, which can also reduce similar viewport-based oracles.
196+
197+
## References
198+
199+
- [https://blog.huli.tw/2022/10/05/en/sekaictf2022-safelist-xsleak/](https://blog.huli.tw/2022/10/05/en/sekaictf2022-safelist-xsleak/)
200+
- [https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img)
201+
202+
{{#include ../../banners/hacktricks-training.md}}
159203

0 commit comments

Comments
 (0)