本文由雲+社區發表
本篇包含了XSS漏洞攻擊及防御詳細介紹,包括漏洞基礎、XSS基礎、編碼基礎、XSS Payload、XSS攻擊防御。
第一部分:漏洞攻防基礎知識
XSS屬於漏洞攻防,我們要研究它就要了解這個領域的一些行話,這樣才好溝通交流。同時我建立了一個簡易的攻擊模型用於XSS漏洞學習。
1. 漏洞術語
了解一些簡單術語就好。
VUL
Vulnerability漏洞,指能對系統造成損壞或能借之攻擊系統的Bug。
POC
Proof of Concept,漏洞證明;可以是可以證明漏洞存在的文字描述和截圖,但更多的一般是證明漏洞存在的代碼;一般不會破壞存在漏洞的系統。
EXP
exploit,漏洞利用;利用漏洞攻擊系統的代碼。
Payload
(有效攻擊負載)是包含在你用於一次漏洞利用(exploit)中的攻擊代碼。
PWN
是一個黑客語法的俚語詞 ,是指攻破設備或者系統。
0DAY漏洞和0DAY攻擊
零日漏洞或零時差漏洞(Zero-dayexploit)通常是指還沒有補丁的安全漏洞。
零日攻擊或零時差攻擊(Zero-dayattack)則是指利用這種漏洞進行的攻擊。
零日漏洞不但是黑客的最愛,掌握多少零日漏洞也成為評價黑客技術水平的一個重要參數。
CVE漏洞編號
Common Vulnerabilities and Exposures,公共漏洞和暴露,為廣泛認同的信息安全漏洞或者已經暴露出來的弱點給出一個公共的名稱。
可以在https://cve.mitre.org/網站根據漏洞的CVE編號搜索該漏洞的介紹。也可以在中文社區http://www.scap.org.cn/上搜索關於漏洞的介紹
2. 漏洞攻擊模型
1.png
上圖為一個簡單的攻擊模型。攻擊就是將Payload通過注入點注入到執行點執行的過程。過程順暢就表明這個漏洞被利用了。
第二部分:XSS基礎知識
基礎知識看完,現在我們可以開始接觸了解XSS基礎了。XSS基礎不好就不用研究了,大家沒用共同語言。
1. 什么是XSS?
XSS全稱Cross-site scripting,跨站腳本攻擊。攻擊者通過網站注入點注入惡意客戶端可執行解析的Payload,當被攻擊者訪問網站時Payload通過客戶端執行點執行來達到某些目的,比如獲取用戶權限、惡意傳播、釣魚等行為。
2. XSS的分類
不了解分類其實很難學好XSS,大家對XSS分類有很多誤解,而且很多文章上都解釋錯的,這里我給出一個相對好的XSS分類。
2.1 按照Payload來源划分
存儲型XSS
Payload永久存在服務器上,所以也叫永久型XSS,當瀏覽器請求數據時,包含Payload的數據從服務器上傳回並執行。
過程如圖:
2.png
存儲型XSS例子:
發表帖子內容包含Payload->存入數據庫->被攻擊者訪問包含該帖子的頁面Payload被執行
反射型XSS
又稱非持久型XSS,第一種情況:Payload來源在客戶端然后在客戶端直接執行。第二種情況:客戶端傳給服務端的臨時數據,直接回顯到客戶端執行。
過程如圖:
3.png
反射型XSS例子 :
- 傳播一個鏈接,這個鏈接參數中包含Payload->被攻擊者訪問這個鏈接Payload在客戶端被執行。
- 在客戶端搜索框輸入包含payload的內容->服務端回顯一個頁面提示搜索內容未找到,payload就被執行了。
2.2 按照Payload的位置划分
DOM-based XSS
由客戶端JavaScript代碼操作DOM或者BOM造成Payload執行的漏洞。由於主要是操作DOM造成的Payload執行,所以叫做DOM-based XSS,操作BOM同樣也可以造成Payload執行,所以這個名詞有些不准確,其實叫JavaScript-based XSS更好。
DOM-based的Payload不在html代碼中所以給自動化漏洞檢測帶來了困難。
過程如圖:
4.png
反射型DOM-based XSS的例子:
在客戶端搜索框輸入包含payload的內容->服務端回顯一個頁面提示搜索內容未找到,payload就被執行了。
存儲型DOM-based XSS的例子:
從服務端接口中獲取包含Payload的內容->JavaScript通過操作DOM、BOM造成Payload執行
HTML-based XSS
Payload包含在服務端返回的HTML中,在瀏覽器解析HTML的時候執行。這樣的漏洞易於做自動化漏洞檢測,因為Payload就在HTML里面。當然HTML-based XSS也有反射型和存儲型的。
過程如圖:
5.png
反射型HTML-based XSS的例子:
在客戶端搜索框輸入包含payload的內容->服務端回顯一個頁面提示搜索內容未找到,payload包含在HTML被執行。
存儲型HTML-based XSS的例子:
發表帖子內容包含Payload->存入數據庫->被攻擊者訪問包含該帖子的頁面Payload在HTML頁面中被執行
3. XSS的攻擊目的及危害
很多寫出不安全代碼的人都是對漏洞的危害沒有清晰的認識,下圖是2017 OWASP 網絡威脅Top10:
6_頭圖 自截取.jpg
可以看到XSS在網絡威脅中的地位舉足輕重。
3.1 目的
- cookie劫持
- 篡改網頁,進行釣魚或者惡意傳播
- 網站重定向
- 獲取用戶信息
3.2 危害
- 傳播類危害
- 系統安全威脅
第三部分:XSS攻擊的Payload
這部分我們分析下攻擊模型中的Payload,了解Payload必須了解編碼,學習好JS也必須要了解好編碼。要想真正做好網絡安全編碼是最基本的。
1. 編碼基礎
編碼部分是最重要的雖然枯燥但必須要會。后面很多變形的Payload都建立在你的編碼基礎。這里通16進制編碼工具讓你徹底學會編碼。
1.1 編碼工具
16進制查看器:方便查看文件16進制編碼
MAC:HEx Friend
windows: HxD
編輯器Sublime:可以通過Sublime將文件保存不同編碼類型
7.jpg
1.2 ASCII
定義:美國信息交換標准代碼,是基於拉丁字母的一套計算機編碼系統,主要用於顯示現代英語和其他西歐語言。
編碼方式:屬於單子節編碼。ASCII碼一共規定了128個字符的編碼,只占用了一個字節的后面7位,最前面的1位統一規定為0。0~31及127(共33個)是控制字符或通信專用字符。32~126(共95個)是字符(32是空格。
1.3 ISO-8859-1(Latin1)
定義:Latin1是ISO-8859-1的別名,ISO-8859-1收錄的字符除ASCII收錄的字符外,還包括西歐語言、希臘語、泰語、阿拉伯語、希伯來語對應的文字符號。歐元符號出現的比較晚,沒有被收錄在ISO-8859-1當中。
編碼方式:ISO-8859-1編碼是單字節編碼,向下兼容ASCII,其編碼范圍是0x00-0xFF,0x00-0x7F之間完全和ASCII一致,0x80-0x9F之間是控制字符,0xA0-0xFF之間是文字符號。
注意:ISO-8859-1編碼表示的字符范圍很窄,無法表示中文字符。但是,由於是單字節編碼,和計算機最基礎的表示單位一致,所以很多時候,仍舊使用ISO-8859-1編碼來表示。比如,雖然”中文”兩個字不存在iso8859-1編碼,以gb2312編碼為例,應該是”d6d0 cec4”兩個字符,使用iso8859-1編碼的時候則將它拆開為4個字節來表示:”d6 d0 ce c4”(事實上,在進行存儲的時候,也是以字節為單位處理的)。所以mysql中latin1可以表示任何編碼的字符。
Latin1與ASCII編碼的關系:完全兼容ASCII。
1.4 Unicode編碼(UCS-2)
Code Point: 碼點,簡單理解就是字符的數字表示。一個字符集一般可以用一張或多張由多個行和多個列所構成的二維表來表示。二維表中行與列交叉的點稱之為碼點,每個碼點分配一個唯一的編號,稱之為碼點值或碼點編號。
BOM(Byte Order Mark):字節序,出現在文件頭部,表示字節的順序,第一個字節在前,就是”大端方式”(Big-Endian),第二個字節在前就是”小端方式”(Little-Endian)。
在Unicode字符集中有一個叫做”ZERO WIDTH NO-BREAK SPACE“的字符,它的碼點是FEFF。而FFFE在Unicode中是不存在的字符,所以不應該出現在實際傳輸中。在傳輸字節流前,我們可以傳字符”ZERO WIDTH NO-BREAK SPACE“表示大小端,因此字符”ZERO WIDTH NO-BREAK SPACE“又被稱作BOM。
BOM還可以用來表示文本編碼方式,Windows就是使用BOM來標記文本文件的編碼方式的。Mac上文件有沒有BOM都可以。
例如:\u00FF :00是第一個字節,FF是第二個字節。和碼點表示方式一樣屬於大端方式。
Unicode編碼字符集:旨在收集全球所有的字符,為每個字符分配唯一的字符編號即代碼點(Code Point),用 U+緊跟着十六進制數表示。所有字符按照使用上的頻繁度划分為 17 個平面(編號為 0-16),即基本的多語言平面和增補平面。基本的多語言平面又稱平面 0,收集了使用最廣泛的字符,代碼點從 U+0000 到 U+FFFF,每個平面有 216=65536 個碼點;
Unicode編碼:Unicode 字符集中的字符可以有多種不同的編碼方式,如 UTF-8、UTF-16、UTF-32、壓縮轉換等。我們通常所說的Unicode編碼是UCS-2 將字符編號(同 Unicode 中的碼點)直接映射為字符編碼,亦即字符編號就是字符編碼,中間沒有經過特別的編碼算法轉換。是定長雙字節編碼:因為我們UCS-2只包括本的多語言平面(U+0000 到 U+FFFF)。
UCS-2的BOM:大端模式:FEFF。小端模式:FFFE。
文件保存成UTF-16 BE with BOM相當於UCS-2的大端模式,可以看到16進制開頭為FEFF
Latin1與Unicode編碼的關系:Latin1對應於Unicode的前256個碼位。
8.png
1.5 UTF-16
定義及編碼:UTF-16是Unicode的其中一個使用方式,在Unicode基本多文種平面定義的字符(無論是拉丁字母、漢字或其他文字或符號),一律使用2字節儲存。而在輔助平面定義的字符,會以代理對(surrogate pair)的形式,以兩個2字節的值來儲存。是雙字節編碼。
UTF-16與UCS-2的關系:UTF-16可看成是UCS-2的父集。在沒有輔助平面字符(surrogate code points)前,UTF-16與UCS-2所指的是同一的意思。但當引入輔助平面字符后,就稱為UTF-16了。現在若有軟件聲稱自己支援UCS-2編碼,那其實是暗指它不能支援在UTF-16中超過2bytes的字集。對於小於0x10000的UCS碼,UTF-16編碼就等於UCS碼。
UTF-16的BOM:大端模式:FEFF。小端模式:FFFE。
1.6 UTF-8
定義及編碼:UTF-8就是在互聯網上使用最廣的一種Unicode的實現方式,這是為傳輸而設計的編碼,並使編碼無國界,這樣就可以顯示全世界上所有文化的字符了。UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度,當字符在ASCII碼的范圍時,就用一個字節表示,保留了ASCII字符一個字節的編碼作為它的一部分,注意的是unicode一個中文字符占2個字節,而UTF-8一個中文字符占3個字節)。從unicode到utf-8並不是直接的對應,而是要過一些算法和規則來轉換。
Unicode符號范圍 | UTF-8編碼方式(十六進制) |
---|---|
0000 0000-0000 007F | 0xxxxxxx |
0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
UTF8的BOM:EFBBBF。UTF-8不存在字符序列的問題,但是可以用用BOM表示這個文件是一個UTF-8文件。
文件保存成UTF-8 BE with BOM,可以看到16進制開頭為EFBBBF
9.png
1.7 GBK/GB2312
定義及編碼:GB2312是最早一版的漢字編碼只包含6763漢字,GB2312只支持簡體字而且不全,顯然不夠用。GBK編碼,是對GB2312編碼的擴展,完全兼容GB2312標准,支持簡體字繁體字,包含全部中文字符。GBK編碼采用單雙字節編碼方案,單字節和Latin1一致,雙字節是漢字部分,其編碼范圍:8140-FEFE,剔除xx7F碼位,共23940個碼位。
GBK與Latin1的關系:GBK單字節編碼區和Latin1編碼一致。
GBK與Unicode的關系:GBK與Unicode字符集編碼不同但是兼容的。如"漢"的Unicode值與GBK雖然是不一樣的,假設Unicode為a040,GBK為b030,但是可以對應轉化的。漢字的Unicode區:4E00-u9FA5。
GBK與UTF-8:GBK漢字采用雙字節編碼比在UTF-8中的三字節要小。但是UTF-8更通用。GBK與UTF-8轉化:GBK—> Unicode —> UTF8
2. 前端中的編碼
有了編碼基礎就可以來認識一下前端中的編碼,這樣你才能真正認識Payload。我這里的應該是總結最全的。
2.1 Base64
Base64可以用來將binary的字節序列數據編碼成ASCII字符序列構成的文本。使用時,在傳輸編碼方式中指定Base64。使用的字符包括大小寫拉丁字母各26個、數字10個、加號+和斜杠/,共64個字符及等號=用來作為后綴用途。所以總共65個字符。
將3字節的數據,先后放入一個24位的緩沖區中,先來的字節占高位。數據不足3字節的話,於緩沖器中剩下的比特用0補足。每次取出6bit對原有數據用Base64字符作為編碼后的輸出。編碼若原數據長度不是3的倍數時且剩下1個輸入數據,則在編碼結果后加2個=;若剩下2個輸入數據,則在編碼結果后加1個=。可以看出Base64編碼數據大約是原來數據的3/4。
標准的Base64並不適合直接放在URL里傳輸,因為URL編碼器會把標准Base64中的/和+字符變為形如%XX的形式,而這些%號在存入數據庫時還需要再進行轉換,因為ANSI SQL中已將%號用作通配符。為解決此問題,可采用一種用於URL的改進Base64編碼,它不在末尾填充=號,並將標准Base64中的+和/分別改成了-和_,這樣就免去了在URL編解碼和數據庫存儲時所要做的轉換,避免了編碼信息長度在此過程中的增加,並統一了數據庫、表單等處對象標識符的格式。
window.btoa/window.atob base64編碼(binary to ascii)和解碼僅支持Latin1字符集。
2.2 JS轉義字符
js字符字符串中包含一些反斜杠開頭的特殊轉義字符,用來表示非打印符、其他用途的字符還可以轉義表示unicode、Latin1字符。
轉義字符 | 含義 |
---|---|
\’ | 單引號 |
\” | 雙引號 |
& | 和號 |
\ | 反斜杠 |
\n | 換行符 |
\r | 回車符 |
\t | 制表符 |
\b | 退格符 |
\f | 換頁符 |
\n … \nnn | 由一位到三位八進制數(1到377)指定的Latin-1字符 |
\xnn | 以16進制nn(n:0~F)表示一個Latin1字符。\x41表示字符A |
\unnnn | 以16進制nnnn(n:0~F)表示一個Unicode字符。只限於碼點在\u0000~\uFFFF范圍內 |
\u{n} … \u{nnnnnn} | Unicode碼點值表示一個Unicode字符 |
特別注意:
- 換行符\n在innerHTML使用只會展示一個空格並不會換行。
- 通過\n、\u和\x可以代表任意unicode字符和Latin1字符。通過這個可以對js加密保證js安全和進行隱蔽攻擊。
例子:
function toUnicode(theString) { //字符串轉換為unicode編碼字符串,切記這個字符串是復制用的,不是讓你拿來直接執行的。
var unicodeString = '';
for (var i = 0; i < theString.length; i++) {
var theUnicode = theString.charCodeAt(i).toString(16).toUpperCase();
while (theUnicode.length < 4) {
theUnicode = '0' + theUnicode;
}
theUnicode = '\\u' + theUnicode;
unicodeString += theUnicode;
}
return unicodeString;
}
var xssStr = "alert('xss')";
var xssStrUnicode = toUnicode(xssStr);
//輸出:"\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u002"
eval("\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u002"); //彈出xss彈窗
2.3 URL編碼
RFC 1738做出規定”只有字母和數字0-9a-zA-Z、一些特殊符號”$-_.+!*’(),”不包括雙引號、以及某些保留字,才可以不經過編碼直接用於URL”。所以當鏈接中包含中文或者其他不符合規定的字符的時候都需要經過編碼的。然而由於瀏覽器廠商眾多,對url進行編碼的形式多種多樣,如果不對編碼進行統一處理,會對代碼開發造成很大的影響,出現亂碼現象。
URL編碼規則:需要編碼的字符轉換為UTF-8編碼,然后在每個字節前面加上%。
例如:
'牛'-->UTF-8編碼E7899B-->URL編碼是%E7%89%9B
JS為我們提供了3個對字符串進行URL編碼的方法:escape ,encodeURI,encodeURIComponent
escape:由於eccape已經被建議放棄所以大家就不要用了
encodeURI:encodeURI不編碼的82個字符:!#$&’()*+,/:;=?@-._~0-9a-zA-Z,從中可以看不會對url中的保留字符進行編碼,所以適合url整體編碼
encodeURIComponent:這個對於我們來說是最有用的一個編碼函數,encodeURIComponent不編碼的字符有71個:!, ‘,(,),*,-,.,_,~,0-9,a-z,A-Z。
可以看出對url中的保留字進行的編碼,所以當傳遞的參數中
包含這些url中的保留字(@,&,=),就可以通過這個方法編碼后傳輸
這三個方法對應的解碼方法: unescape、decodeURI、decodeURIComponent
2.4 HTML字符實體
HTML中的預留字符必須被替換為字符實體。這樣才能當成字符展示,否則會當成HTML解析。
字符實體編碼規則:轉義字符 = &#+ascii碼; = &實體名稱;
XSS字符串需要防御字符的實體轉換表:
10.png
轉化方法:
function encodeHTML (a) {
return String(a)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};
2.5 頁面編碼
頁面編碼設置:
腳本編碼設置:
注意:要想JS即可在UTF-8中正常使用又可以在GBK中正常使用,可以對JS中所有包含中文的字符串做字符轉義。
例子:
alert("網絡錯誤"); //彈出網絡錯誤
alert("\u7f51\u7edc\u9519\u8bef"); //彈出網絡錯誤
3. Payload的分類
現在可以認識Payload的了,我不得不說這里對Payload的分類可以很好的讓你認識Payload。也幫助你更好的對應到執行點。
3.1 原子Payload
最低層級的Payload。
javascript代碼片段
可在eval、setTimeout、setInterval中直接執行,也可通過HTML等構成高階Payload
javascript:javascript偽協議
結構:javascript:+js代碼。可以在a標簽的href屬性被點擊和window.location.href賦值的時候執行。
DATA URI協議
DATA URI結構:data:, 。DATA URI數據在包含在iframe的src屬性和object data屬性中將會變成可執行的Payload.
字符串轉義變種javascript代碼片段
unicode或者Latin-1表示字符串。
eval("\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u002"); //可執行的JS
3.2 純HTMLPayload
這種Payload特點不具有可執行的JS,但是存在傳播風險,可以把別的站點注入到被攻擊網站。
包含鏈接跳轉的HTML片段
主要是傳播危害
<a href="http://ha.ck">哈哈,我來釣魚了</a>
3.3 包含原子Payload的HTML片段Payload
script標簽片段
script標簽片段這種Payload可以引入外部JS或者可直接執行的script。這種Payload一般不能通過直接復制給innerHTML執行,不過在IE上可以。不過通過document.write是可以執行。
例子:
// Payload原始值:data:text/html,<script>alert('xss');</script>
var inputStr ="<script>alert('xss');<\/script>";
document.write(inputStr);
包含事件處理的HTML片段
例如:包含img的onerror, svg的onload,input的onfocus等的HTML片段,都可以變成可執行的Payload。
var inputStr ="<img src=x onerror=alert('xss');>";
var inputStr ="<svg/onload=alert('xss')>";
var inputStr ="<input autofocus onfocus=alert('xss')>";
xssDom.innerHTML = inputStr;
包含可執行JS屬性的HTML片段
- javascript偽協議
xssLink.setAttribute("href","javascript:alert('xss')")//點擊可觸發
var inputStr = "javascript:alert('xss')";
window.location.href = inputStr;
- DATA URI
例子:
// Payload原始值:data:text/html,<script>alert('xss');</script>
//var inputStr = '<iframe src="data:text/html,<script>alert("xss");</script>"></iframe>';
// var inputStr = '<object data="data:text/html;base64,ZGF0YTp0ZXh0L2h0bWwsPHNjcmlwdD5hbGVydCgneHNzJyk7PC9zY3JpcHQ+"></object>';
xssDom.innerHTML = inputStr; //彈出alert("xss")
這里只是介紹了主要的Payload,還有很多不常見的Payload。
第四部分:XSS攻擊模型分析
這部分我們根據漏洞攻擊模型分析一下XSS的執行點和注入點。分析這兩點其實就是找漏洞的過程。
1. XSS漏洞執行點
- 頁面直出Dom
- 客戶端跳轉鏈接: location.href / location.replace() / location.assign()
- 取值寫入頁面:innerHTML、document.write及各種變種。這里主要會寫入攜帶可執行Payload的HTML片段。
- 腳本動態執行:eval、setTimeout()、setInterval()
- 不安全屬性設置:setAttribute。不安全屬性前面見過:a標簽的href、iframe的src、object的data
- HTML5 postMessage來自不安全域名的數據。
- 有缺陷的第三方庫。
2. XSS漏洞注入點
看看我們可以在哪些位置注入我們的Payload
- 服務端返回數據
- 用戶輸入的數據
- 鏈接參數:window.location對象三個屬性href、search、search
- 客戶端存儲:cookie、localStorage、sessionStorage
- 跨域調用:postMessage數據、Referer、window.name
上面內容基本包含了所有的執行點和注入點。對大家進行XSS漏洞攻防很有幫助。
第五部分 XSS攻擊防御策略
1. 騰訊內部公共安全防御及應急響應
- 接入公共的DOM XSS防御JS
- 內部漏洞掃描系統掃描
- 騰訊安全應急響應中心:安全工作者可以通過這個平台提交騰訊相關的漏洞,並根據漏洞評級獲得獎勵。
- 重大故障應急響應制度。
2. 安全編碼
2.1 執行點防御方法
執行點 | 防御 |
---|---|
頁面直出Dom | 服務端XSS過濾 |
客戶端跳轉鏈接 | 域名白名單(例如:只允許qq.com域)、鏈接地址XSS過濾 |
取值寫入頁面 | 客戶端XSS過濾 |
腳本動態執行 | 確保執行Js字符串來源可信 |
|
| 不安全屬性設置 | 內容XSS過濾,包含鏈接同客戶端跳轉鏈接 |
|HTML5 postMessage|origin限制來源|
| 有缺陷的第三方庫 | 不使用
2.2 其他安全防御手段
- 對於Cookie使用httpOnly
- 在HTTP Header中使用Content Security Policy
3. 代碼審查
總結XSS檢查表做代碼自測和檢視
4. 自動化檢測XSS漏洞的工具
手工檢測XSS漏洞是一件比較費時間的事情,我們能不能寫一套自動檢測XSS自動檢測工具。竟然我知道了注入點、執行點、Payload自動化過程是完全有可能的。
XSS自動化檢測的難點就在於DOM型XSS的檢測。因為前端JS復雜性較高,包括靜態代碼分析、動態執行分析都不容易等。
第六部分 總結
上面內容文字比較多,看完還是很累的,總結起來就一句話:安全大於一切,不要心存僥幸,希望以上內容對您有幫助,不過以上內容僅代表個人理解,如有不對歡迎指正討論。
此文已由作者授權騰訊雲+社區發布