Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ This is a **different exploit for the CTF chall** that was already commented in
connection-pool-example.md
{{#endref}}

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.

The idea behind this exploit is:

- The posts are loaded alphabetically
Expand All @@ -20,6 +22,12 @@ The idea behind this exploit is:
- 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).
- If the the **fetch** requests are **fast**, it means that the **post** is **alphabetically** **after** the flag.

In other words, the oracle is:

- **State 1**: the attacker-controlled post is within the browser lazy-loading threshold, so `img loading=lazy` requests are issued.
- **State 2**: the attacker-controlled post remains outside that threshold, so those requests are not issued.
- **Leak**: the attacker measures whether those extra requests create enough contention to delay another measurable operation.

Let's check the code:

```html
Expand Down Expand Up @@ -154,6 +162,42 @@ Let's check the code:
</html>
```

{{#include ../../banners/hacktricks-training.md}}
## Practical caveats

This trick is **fragile** and needs to be **calibrated per environment**:

- 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.
- 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`).
- 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.
- 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.

## Reliability notes

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:

- **Server-side event-loop blocking**, such as many image requests hitting a Node.js endpoint that performs synchronous work.
- **Socket / connection contention**, where the attacker saturates available connections and times how long an additional request takes.

To make the oracle more stable:

- Use **multiple lazy images** instead of one.
- Add a **cache-buster** to every image URL.
- Measure **several requests** and compare an **average/median** instead of trusting a single sample.
- Recalculate the **canvas height threshold** against the same browser family and execution mode used by the victim bot.

For more timing-based leak primitives, also check:

{{#ref}}
performance.now-+-force-heavy-task.md
{{#endref}}

> [!WARNING]
> 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.

## References

- [https://blog.huli.tw/2022/10/05/en/sekaictf2022-safelist-xsleak/](https://blog.huli.tw/2022/10/05/en/sekaictf2022-safelist-xsleak/)
- [https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img)

{{#include ../../banners/hacktricks-training.md}}