Cross-Site Scripting(XSS) 是一種常見的攻擊方式,而且方式相當多種變化,只要網頁上有 input欄位工使用者輸入,並且會在後續將使用者輸入資料呈現於網站上,就有很高的機會產生XSS弱點。
XSS主要原因為未正確的對使用者輸入資料作驗證與過濾,而允許攻擊者將程式注入至網站中,使得使用者連至網站時載入被植入的惡意代碼,而讓瀏覽器自動執行,後果可能導致使用者資料暴露或是下載其他惡意代碼。
XSS 攻擊可以分為三種類型:
因為未正常過濾使用者所輸入的資料,導致攻擊的script被儲存至server端造成後續的攻擊,稱為 Stored XSS attack,常見的像是表單資料、系統記錄或是留言板回覆等欄位,惡意代碼輸入留言板,接著存入資料庫後,後續看見同一個留言資料的人都會受害。Stored XSS通常也稱為 Persistent 或是 Type-I XSS。
<%
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("sql command");
if (rs != null) {
rs.next();
String title = rs.getString("title");
String message = rs.getString("message");
%>
<!--從資料庫中取出之資料可能包含惡意代碼,未處理情況下直接輸出至畫面少可獳導致惡意代碼被執行-->
Title: <%= title %>
Message: <%= message%>
從DVWA提供之程式來測試,提供一個流言版,並將輸入之資料寫回資料庫中儲存,於下一個使用者進到該畫面時,在將資料抓出來呈現於畫面上:
當輸入欄位中加入了javascript程式後送出,資料將會儲存至資料庫中
當其他人檢視到同一串留言時(或剛剛我留下的內容),因為文字中含有javascript,瀏覽器會自動運行代碼。
與Stored XSS不同, Reflexted XSS不需要儲存於Server端。主要發生原因是Server端未正確過濾前端傳入內容,在未適當過濾使用者輸入資料後,直接將使用者輸入之資料寫回response中導致的漏洞。這類攻擊通常稱為 Non-Persistent或是Type-II XSS。這類攻擊經常會搭配社交工程,攻擊者將有問題連結編碼後,寄送給大量受害者,等待受害者自己點擊連結。
<% String name = request.getParameter("name"); %>
<!--未將name做適當過濾,而直接呈現於Response中-->
Your name: <%= name %>
這個攻擊方式可以直接從DVWA 這個程式來做實際操作:
在input上輸入名字後點 submit,畫面上會出現使用者輸入之內容
當輸入內容中加入javascript時
可以看DVWA上提供的後端程式,會知道後端僅將輸入作為結果輸出至畫面上,這情況很容易被利用,:
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
所以在Response的內文上,看見寫入的script 直接作為 response 回傳回來,導致使用者端直接運行代碼
所以與前兩項主要的差異在於,Stored XSS與Reflected XSS是Server端未正常處理資料,將惡意代碼一同返回Response中而導致使用者瀏覽器運行惡意代碼。
DOM based XSS則是Server端傳回正常代碼,傳回正常的HTML與javascript,但是前端javascript 未適當處理接收到的使用者輸入資料,在動態產生DOM的過程中,因為輸入內容中被植入惡意代碼,導致程式與資料以未預期的方式執行
從一個簡單的例子來看,下列一段完整HTML,當使用者輸入完name後,點選按鈕,會將name作為參數傳至同一個網頁呈現:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
Your name:
<span id="name">
<script>
if (document.location.href.indexOf("name=") !== -1) {
const name = decodeURIComponent(document.location.href.substring(document.location.href.indexOf("name=") + 5));
document.getElementById("name").innerHTML = decodeURI(name);
// document.write(name)
}
</script>
</span>
<form>
<input type="text" name="test" value="">
<button onclick="testClick()" type="button">Click</button>
</form>
<script>
function testClick() {
location.href = "/index.html?name=" + document.querySelector("input[name=test]").value;
}
</script>
</html>
這時候請求 http://localhost/index.html?name=joseph<3,這段程式從location.search中取得 name參數,並呈現至畫面上
但一旦這個規則被知道了,請求上的參數可能會改變,可能的參數如
http://localhost/index.html?name=<img src=x onError="alert('Hello')" />
這時重新進入網網頁,會發現html上不會出現參數中的內容,反而跳出一個alert
另一種寫法,將原本 innerHtml改為 document.write後,可以直接執行 script 語法
http://localhost:12345/index.html?name=<script>alert('xxx')</script>
Dom-based XSS 與Reflected XSS最大的差異在於,對網頁做請求後**產生的Response內容**,Reflected XSS是已經將惡意代碼作為Response傳回使用者端,而 DOM-based XSS則是請求的Response內容正常,但是透過Javascript動態產生內容時,誤將惡意代碼放入DOM中,導致瀏覽器執行惡意代碼。
XSS 可能會造成使用找session cookie被揭露、 劫持 user session、將使用者轉導至惡意網站安裝木馬程式等,甚至可能更改使用者畫面上所見的內容,如更改藥局網站上藥品的劑量,導致服用過量藥物。
XSS 最根本防護方式,為對不受信任的資料作過濾與編碼,避免讓資料呈現於網頁上時有機會可以運行惡意代碼,輸出時針對下列幾個字元,統一做跳脫處理:
原字元 | 跳脫字元 |
---|---|
< | \< |
> | \> |
” | \" |
’ | \' |
& | \& |
/ | \&/#x2F; |
各個程式語言大多都有提供針對字元做Encode的套件,OWASP也提供了 XSS的 Prevention Cheat Sheet可供參考。
<div>escape untrusted data here</div>
<span>escape unstructed data here</span>
<!--盡量避免使用unquoted attribute,OWASP上提及unquoted attribute容易被其他符號打亂導致弱點產生-->
<div attr=escape unstructed data here></div>
<div attr='escape unstructed data here'></div>
<div attr="escape unstructed data here"></div>
<script>alert('escape unstructed data here')</script>
<div onclick="x='escape unstructed data here'"></div>
setInterval與 setTimeout比較特別,OWASP中提到,即便是跳脫過的資料,放入setInterval與setTimout中都一樣危險,所以要盡量避免
<!--避免在setInterval, setTimeout中使用不信任的資料,即便跳脫後仍然一樣-->
window.setInterval('escape or not escape unstructed data ')
同時也建議前後端分離的架構中,如果是透過ajax請求回來的資料, response中的 Content-Type 如果是json,則應該要呈現 application/json,而非 application/html,如此一來可以避免瀏覽器解讀錯誤。
<style>
selector{ property: escape unstructed data here}
</style>
<a style="{property: escape unstructed data here}" ></a>
<a href="https://example.com?param1=escape unstructed data here" >Link</a>
其他建議的部分
# nginx setting 表示瀏覽器只允許從網頁 origin端與 example.com 端讀取資源
add_header Content-Security-Policy "default-src 'self' 'example.com';";
XSS 攻擊方式相當多元,也許這就是XSS一直留在 OWASP Top 10的原因,其中還包含自己尚未弄清原理的攻擊方式,不過稍微做了一些功課後才終於知道,為什麼之前專案在掃白箱時,自己寫的防XSS語法為什麼會被叛定無效,稍微知道攻擊的思路後,未來在開發上才會更加小心。
參考資訊
XSS Filter Evasion Cheat Sheet
OWASP - Cross_Site_Scripting_Prevention_Cheat_Sheet