CSS 变量注入:利用 --var 注入恶意载荷绕过过滤器的分析
各位同学,大家好。今天我们来深入探讨一个比较隐蔽但威力强大的安全漏洞:CSS 变量注入。这种漏洞允许攻击者利用 CSS 变量(custom properties,以 -- 开头的属性)注入恶意载荷,绕过各种输入验证和过滤机制,最终实现跨站脚本攻击(XSS)等危害。
1. CSS 变量简介:不仅仅是样式
CSS 变量是 CSS3 引入的一项强大特性,允许开发者在 CSS 中定义和使用变量。它们可以用于存储颜色、字体大小、间距等样式属性值,并在整个文档中复用。
基本语法:
- 定义变量: 使用
--变量名: 变量值;在:root或其他元素中定义变量。 - 使用变量: 使用
var(--变量名)函数来引用变量。
示例:
:root {
--primary-color: #007bff;
--font-size: 16px;
}
body {
font-size: var(--font-size);
color: var(--primary-color);
}
.button {
background-color: var(--primary-color);
padding: 10px 20px;
border: none;
color: white;
font-size: var(--font-size);
}
在这个例子中,--primary-color 和 --font-size 是两个 CSS 变量。body 和 .button 元素都使用了这些变量来定义它们的样式。
关键点:
- CSS 变量可以存储任意字符串,而不仅仅是合法的 CSS 值。
- 浏览器在解析 CSS 时,会尝试将变量的值应用到对应的属性上。如果变量的值不是合法的 CSS 值,浏览器可能会忽略它,但不会抛出错误。
- CSS 变量具有层叠性和继承性,可以在不同的上下文中覆盖。
2. CSS 变量注入的原理:变量值可控是关键
CSS 变量注入的核心在于攻击者能够控制 CSS 变量的值。如果一个 Web 应用程序允许用户自定义 CSS 样式,或者将用户输入的数据插入到 CSS 样式中,那么攻击者就有可能利用 CSS 变量注入漏洞。
攻击流程:
- 发现注入点: 找到一个可以控制 CSS 变量值的入口。例如,一个允许用户自定义主题颜色的设置页面。
- 构造恶意载荷: 构造包含恶意代码的 CSS 变量值。例如,包含 JavaScript 代码的
expression()函数。 - 注入恶意载荷: 将恶意载荷注入到 CSS 变量中。
- 触发恶意代码: 当浏览器解析包含恶意载荷的 CSS 样式时,会执行其中的恶意代码。
示例:
假设有一个 Web 应用程序允许用户自定义背景颜色,并将用户输入的颜色值插入到 CSS 样式中:
<style>
body {
background-color: var(--user-background-color);
}
</style>
如果用户输入 javascript:alert(1) 作为背景颜色,那么最终生成的 CSS 样式将是:
<style>
body {
background-color: var(--user-background-color);
}
:root {
--user-background-color: javascript:alert(1);
}
</style>
虽然 javascript:alert(1) 不是一个合法的颜色值,但浏览器不会报错。但是,如果攻击者能够找到一种方法,让浏览器将这个字符串作为 JavaScript 代码执行,那么就可以实现 XSS 攻击。
3. 常用的攻击载荷:利用 CSS 的特性执行恶意代码
CSS 本身不是一种编程语言,不能直接执行 JavaScript 代码。但是,攻击者可以利用 CSS 的一些特性,间接地执行 JavaScript 代码:
3.1 expression() 函数 (仅适用于 IE7 及以下版本):
expression() 函数是 IE 特有的一个 CSS 函数,它允许在 CSS 属性值中使用 JavaScript 表达式。当 CSS 属性被应用时,expression() 函数中的 JavaScript 表达式会被执行。
示例:
body {
background-color: expression(alert(1)); /* IE7 及以下 */
}
虽然现代浏览器已经不再支持 expression() 函数,但在一些老旧的系统中仍然可能存在风险。
3.2 behavior 属性 (仅适用于 IE):
behavior 属性允许将一个外部的 HTC (HTML Component) 文件应用到 HTML 元素上。HTC 文件可以包含 JavaScript 代码,从而实现动态的 HTML 元素行为。
示例:
body {
behavior: url(evil.htc); /* IE */
}
evil.htc 文件内容:
<script>
alert(1);
</script>
同样,这种方法只适用于 IE 浏览器。
3.3 利用 CSS 属性值中的 URL:
某些 CSS 属性(例如 background-image、list-style-image 等)允许使用 URL 作为属性值。攻击者可以利用这一点,将 URL 指向一个包含 JavaScript 代码的文件。
示例:
body {
background-image: url("data:text/javascript,alert(1)");
}
或者,如果服务器允许上传任意文件,攻击者可以上传一个包含 JavaScript 代码的 SVG 文件,然后使用 background-image 属性引用该 SVG 文件:
body {
background-image: url("evil.svg");
}
evil.svg 文件内容:
<svg xmlns="http://www.w3.org/2000/svg">
<script>
alert(1);
</script>
</svg>
3.4 利用 @import 规则:
@import 规则允许在一个 CSS 文件中导入另一个 CSS 文件。攻击者可以利用这一点,将 @import 规则指向一个包含恶意代码的 CSS 文件。
示例:
@import url("evil.css");
evil.css 文件内容:
body {
background-color: expression(alert(1));
}
3.5 利用 calc() 函数和 attr() 函数:
calc()函数允许在CSS中进行计算,而attr()函数允许获取HTML元素的属性值。结合这两个函数,可以实现一些复杂的攻击。
示例:
假设有一个HTML元素:<div id="target" data-evil="javascript:alert(1)"></div>
CSS代码:
#target {
width: calc(100% + attr(data-evil string)); /* 某些浏览器可能会解析并执行data-evil中的javascript代码 */
}
这个例子依赖于浏览器的解析行为,不同浏览器处理attr()和calc()的方式可能不同,攻击成功率可能不高,但展示了calc()和attr()结合使用的可能性。
总结
| 攻击方式 | 适用浏览器 | 描述 |
|---|---|---|
expression() |
IE7 及以下 | 利用 IE 特有的 expression() 函数执行 JavaScript 代码。 |
behavior |
IE | 利用 IE 特有的 behavior 属性将 HTC 文件应用到 HTML 元素上,HTC 文件中包含 JavaScript 代码。 |
| URL 中的 JavaScript | 现代浏览器 | 利用 CSS 属性值中的 URL,将 URL 指向一个包含 JavaScript 代码的文件(例如 data:text/javascript,alert(1) 或 SVG 文件)。 |
@import |
现代浏览器 | 利用 @import 规则导入一个包含恶意代码的 CSS 文件。 |
calc() 和 attr() |
部分现代浏览器 | 结合使用calc()和attr()函数,尝试利用浏览器解析行为执行属性值中的JavaScript代码。攻击成功率取决于浏览器实现。 |
4. 绕过过滤器的技巧:编码、混淆和拆分
Web 应用程序通常会对用户输入进行过滤,以防止 XSS 攻击。但是,攻击者可以使用各种技巧来绕过这些过滤器:
4.1 编码:
- URL 编码: 将特殊字符编码为 URL 编码的形式(例如
%20代表空格,%3C代表<)。 - HTML 实体编码: 将特殊字符编码为 HTML 实体编码的形式(例如
<代表<,>代表>)。 - Unicode 编码: 将字符编码为 Unicode 编码的形式(例如
u003C代表<)。
示例:
假设过滤器会过滤掉 <script> 标签,攻击者可以使用 HTML 实体编码来绕过过滤器:
<img src="x" onerror="alert(1)">
4.2 混淆:
- 大小写混淆: 改变标签或属性的大小写(例如
<sCrIpt>)。 - 插入空白字符: 在标签或属性中插入空白字符(例如
< script>)。 - 使用注释: 在标签或属性中使用注释(例如
<!---->).
示例:
假设过滤器会过滤掉 javascript: 协议,攻击者可以使用大小写混淆来绕过过滤器:
<img src="x" onerror="JaVaScRiPt:alert(1)">
4.3 拆分:
将恶意代码拆分成多个部分,然后使用字符串拼接的方式将它们组合起来。
示例:
假设过滤器会过滤掉 alert() 函数,攻击者可以将 alert() 函数拆分成两个字符串,然后使用字符串拼接的方式将它们组合起来:
<img src="x" onerror="eval('al'+'ert(1)')">
4.4 利用 CSS 的特性:
- 利用反斜杠转义: 在 CSS 中,可以使用反斜杠来转义特殊字符。攻击者可以利用这一点,将恶意代码隐藏在反斜杠后面。
- 利用 CSS 的层叠性: 通过覆盖 CSS 样式,可以将恶意代码注入到特定的元素中。
示例:
假设过滤器会过滤掉 expression() 函数,攻击者可以使用反斜杠转义来绕过过滤器:
body {
background-color: expression(alert(1));
}
或者,攻击者可以利用 CSS 的层叠性,将恶意代码注入到特定的元素中:
.evil {
background-color: expression(alert(1));
}
<div class="evil"></div>
4.5 利用字符编码转换
某些过滤器可能只检测特定的字符集,而忽略了其他字符集。攻击者可以利用这一点,将恶意代码编码为其他字符集,然后使用 CSS 的 @charset 规则来指定字符集。
示例:
@charset "UTF-16";
body {
/* 假设 UTF-16 编码的 alert(1) */
background-color: expression( ... encoded alert(1) here ...);
}
这种方式需要服务器支持相应的字符集,并且浏览器能够正确解析。
5. 防御 CSS 变量注入:严格的输入验证和输出编码
防御 CSS 变量注入漏洞的关键在于严格的输入验证和输出编码:
5.1 输入验证:
- 白名单验证: 只允许用户输入预定义的 CSS 属性值。例如,如果用户只能选择背景颜色,那么只允许用户输入合法的颜色值(例如
#RRGGBB或rgb(R, G, B))。 - 正则表达式验证: 使用正则表达式来验证用户输入是否符合预期的格式。
- 长度限制: 限制用户输入的最大长度,防止攻击者注入过长的恶意代码。
5.2 输出编码:
- CSS 编码: 对用户输入的数据进行 CSS 编码,将特殊字符转换为 CSS 编码的形式。例如,将
<转换为3C,将>转换为3E。 - Context-Aware Encoding: 根据输出的上下文选择合适的编码方式。例如,如果在 HTML 属性中使用 CSS 变量,那么需要进行 HTML 编码和 CSS 编码。
5.3 其他防御措施:
- Content Security Policy (CSP): 使用 CSP 来限制浏览器可以加载的资源,防止攻击者加载恶意脚本。
- Subresource Integrity (SRI): 使用 SRI 来验证外部资源的完整性,防止攻击者篡改外部资源。
- 避免使用危险的 CSS 特性: 尽量避免使用
expression()函数和behavior属性等危险的 CSS 特性。 - 定期更新浏览器和 Web 应用程序: 及时修复已知的安全漏洞。
示例代码(PHP):
<?php
function sanitize_css_variable($input) {
// 白名单验证:只允许合法的颜色值
if (!preg_match('/^#([0-9a-fA-F]{3}){1,2}$/', $input) && !preg_match('/^rgb(s*d+s*,s*d+s*,s*d+s*)$/', $input)) {
return ''; // 返回空字符串,表示输入无效
}
// CSS 编码:将特殊字符转换为 CSS 编码的形式
$input = str_replace(['<', '>', '"', "'", '(', ')'], ['3C', '3E', '22', '27', '28', '29'], $input);
return $input;
}
// 获取用户输入的背景颜色
$user_background_color = $_GET['background_color'];
// 对用户输入进行验证和编码
$safe_background_color = sanitize_css_variable($user_background_color);
// 生成 CSS 样式
$css = "body { background-color: var(--user-background-color); } :root { --user-background-color: " . $safe_background_color . "; }";
?>
<!DOCTYPE html>
<html>
<head>
<title>CSS 变量注入示例</title>
<style>
<?php echo $css; ?>
</style>
</head>
<body>
<h1>欢迎光临!</h1>
<p>这是一个 CSS 变量注入示例页面。</p>
</body>
</html>
这个示例代码使用 PHP 对用户输入的背景颜色进行验证和编码,有效地防止了 CSS 变量注入攻击。首先,它使用白名单验证来确保用户输入的是合法的颜色值。然后,它使用 CSS 编码将特殊字符转换为 CSS 编码的形式,防止攻击者注入恶意代码。
6. 案例分析:真实世界的 CSS 变量注入漏洞
虽然 CSS 变量注入漏洞相对隐蔽,但在真实世界中也存在一些案例。例如,某些 CMS (Content Management System) 系统或 Web 应用程序允许用户自定义主题样式,但没有对用户输入进行充分的验证,导致攻击者可以利用 CSS 变量注入漏洞执行 XSS 攻击。
案例描述:
某 CMS 系统允许用户自定义网站的主题颜色。用户可以在后台管理界面中设置主题颜色,然后 CMS 系统会将用户输入的颜色值插入到 CSS 样式中。但是,CMS 系统没有对用户输入进行充分的验证,导致攻击者可以注入包含恶意代码的 CSS 变量值。
攻击过程:
- 攻击者登录到 CMS 系统的后台管理界面。
- 攻击者在主题颜色设置页面中输入包含恶意代码的 CSS 变量值,例如
javascript:alert(1)。 - CMS 系统将攻击者输入的颜色值插入到 CSS 样式中,并保存到数据库中。
- 当其他用户访问网站时,浏览器会解析包含恶意载荷的 CSS 样式,并执行其中的 JavaScript 代码。
修复方案:
- 对用户输入进行严格的验证,只允许用户输入合法的颜色值。
- 对用户输入的数据进行 CSS 编码,将特殊字符转换为 CSS 编码的形式。
- 使用 CSP 来限制浏览器可以加载的资源,防止攻击者加载恶意脚本。
7. 最后的思考:安全无小事,防范于未然
CSS 变量注入是一种隐蔽但威力强大的安全漏洞。Web 开发者应该充分了解 CSS 变量注入的原理和攻击方式,并采取有效的防御措施,防止攻击者利用该漏洞进行 XSS 攻击。安全无小事,只有防范于未然,才能确保 Web 应用程序的安全性。
CSS 变量注入的影响和防御
CSS变量注入漏洞利用了CSS变量的灵活性,允许攻击者控制样式属性,进而可能执行恶意代码。通过严格的输入验证、输出编码、CSP等手段,可以有效地防御此类攻击。安全开发需要对各种潜在的注入点保持警惕,并采取相应的防护措施。
更多IT精英技术系列讲座,到智猿学院