?簡介
編碼問題一直是個痛點,尤其是當(dāng)我們對XSS攻擊原理不是很熟悉的話,防護(hù)起來很容易造成遺漏佛玄。要想很好的防護(hù)住XSS攻擊,需要對瀏覽器解析HTML累澡、JS梦抢、CSS的原理弄清楚,了解瀏覽器的工作原理愧哟,才能做好防護(hù)工作奥吩。小編也是搜集網(wǎng)上資料進(jìn)行學(xué)習(xí),并整理分享下該篇文檔翅雏。
(一)瀏覽器的結(jié)構(gòu)?
瀏覽器的主要組件,包含用戶界面人芽、瀏覽器引擎望几、呈現(xiàn)引擎、網(wǎng)絡(luò)萤厅、用戶界面后端橄抹、JavaScript解釋器、數(shù)據(jù)存儲惕味。這里我們主要需要了解呈現(xiàn)引擎楼誓,其主要負(fù)責(zé)顯示請求內(nèi)容,如果請求的內(nèi)容是HTML名挥,它就負(fù)責(zé)解析HTML和CSS內(nèi)容疟羹,并將解析后的內(nèi)容顯示在屏幕上。
值得注意的是禀倔,Chrome瀏覽器的每個標(biāo)簽頁都分別對應(yīng)一個呈現(xiàn)引擎實例榄融,每個標(biāo)簽頁都是獨立的進(jìn)程。
呈現(xiàn)引擎一開始會從網(wǎng)絡(luò)層獲取請求文檔的內(nèi)容救湖,內(nèi)容的大小一般限制在 8000 個塊以內(nèi)愧杯。
呈現(xiàn)引擎解析HTML文檔,將標(biāo)記轉(zhuǎn)換成內(nèi)容樹上的DOM節(jié)點鞋既,將CSS元素樣式轉(zhuǎn)換成另外的樹結(jié)構(gòu):呈現(xiàn)樹力九。構(gòu)建完成后,進(jìn)入布局階段邑闺,每個節(jié)點都會分配一個應(yīng)該出現(xiàn)在屏幕上的坐標(biāo)跌前,由用戶界面后端層將每個節(jié)點繪制出來。
解析的過程其實就是編譯原理那一套東西陡舅,由解析器和詞法分析器將文檔內(nèi)容構(gòu)造成一個有效的解析樹舒萎,最后由翻譯器,將解析樹翻譯成瀏覽器可執(zhí)行的機(jī)器指令,最后我們看到的就是一個可視化的web頁面臂寝。
HTML解析器的任務(wù)是將HTML標(biāo)記解析成解析樹章鲤,常規(guī)解析器是不適用與HTML的,因為我們知道HTML具有很強(qiáng)的容錯性咆贬,并不是與上下文無關(guān)的語法败徊,我們會通過document.write添加額外的標(biāo)記。HTML的定義采用了DTD格式掏缎,包括允許使用的元素及其屬性和層次結(jié)構(gòu)皱蹦。HTML的解析是瀏覽器通過標(biāo)記化、樹結(jié)構(gòu)的形式完成解析構(gòu)建的眷蜈。
CSS是上下文無關(guān)的語法沪哺,可以使用常規(guī)的解析器進(jìn)行解析。??關(guān)于具體如何解析酌儒,便不在這里闡述辜妓,大家只需要有個大致的了解即可。
處理腳本和樣式表的順序
HTMl==》CSS==》JavaScript 針對這個順序我是報遲疑態(tài)度的忌怎,因為上述我們也提到過籍滴,在遇到<script>腳本時會立即解析并執(zhí)行腳本,文檔解析將停止榴啸,直到腳本執(zhí)行完畢孽惰。如果腳本是外部的,解析過程會停止鸥印,直到從網(wǎng)絡(luò)同步抓取資源完成后再繼續(xù)勋功。在HTML5中增加了一個選項,可將腳本標(biāo)記為異步库说,以便由其它線程解析和執(zhí)行酝润。
?(二)瀏覽器解碼過程?
HTMl==》CSS==》JavaScript ,針對這個解析順序璃弄,我們這里可以先不討論要销,我們的目的是關(guān)心如何正確的編碼,那么我只需要關(guān)注夏块,對于常用的HTML頁面疏咐,瀏覽器是如何解碼的即可。
1脐供、HTML實體
<p>${content}</p>
如上述代碼所示浑塞,在P標(biāo)簽中存在一個輸出變量${content},瀏覽器解析的過程政己,首先是HTML解析酌壕,解析到P標(biāo)簽時,解析Content的內(nèi)容,然后將其在頁面顯示出來卵牍。
<p><script>alert("實體XSS");</script></p>
如果我們把Content的內(nèi)容換成上面內(nèi)容果港,即script腳本,那么瀏覽器解析的時候糊昙,當(dāng)解析到P標(biāo)簽時辛掠,發(fā)現(xiàn)里面的內(nèi)容存在script標(biāo)簽,便會把其當(dāng)做JavaScript腳本進(jìn)行解析释牺,從而達(dá)到XSS攻擊的目的萝衩。
?所以針對此類HTML實體間的輸出,我們希望輸出的是HTML文本內(nèi)容没咙,而不是HTML標(biāo)簽猩谊、JS代碼等,所以我們在輸出時祭刚,需要對Content進(jìn)行HTML編碼,可使用OWASP ESAPI的ESAPI.encoder().encodeForHTML()牌捷。 HTML編碼一般將如下幾個字符進(jìn)行編碼替換:
1. & —> &
2. < —> <
3. > —> >
4. " —> "
5. ' —> '
6. / —> /
在編碼的字符中,其中&袁梗、<宜鸯、>憔古、"遮怜、' 五個字符是XML中定義的實體,所以我們需要對其進(jìn)行編碼鸿市,因為HTML也算作XML的一種锯梁,/ 字符作為HTML標(biāo)簽的結(jié)束協(xié)助符,避免破壞標(biāo)簽焰情。
HTML編碼的作用就是將原本能被HTML解析成標(biāo)簽的東西陌凳,轉(zhuǎn)換成字符串文本,以文本的形式展現(xiàn)
2内舟、HTML通用屬性
<input name="${firstname}" ></input>
name是input的屬性合敦,所以HTML解析時,會對name屬性的內(nèi)容進(jìn)行HTML解碼验游,假如此時${firstname}的值為如下內(nèi)容時充岛,HTML屬性被截斷插入了onclick事件。
<input name=" " onclick="alert('屬性XSS')" " "></input>
所以針對這種常規(guī)的HTML屬性耕蝉,都需要對其進(jìn)行HTML屬性編碼崔梗,其實和HTML實體編碼類似,只不過編碼字符范圍可能多些垒在,除了字母數(shù)字外蒜魄,所有ASCII碼小于256的字符均進(jìn)行&#XXX;編碼,具體的參見ESAPI.encoder().encodeForHTMLAttribute的實現(xiàn)。其實屬性XSS中最主要的是拆分屬性標(biāo)簽谈为,注入可執(zhí)行JS的屬性旅挤,并對其添加而已代碼。
對于通用屬性的編碼方式不適用于href峦阁、src谦铃、style、事件處理函數(shù)(onclick榔昔、onmouseover等)驹闰,因為本身這些屬性是支持偽協(xié)議的,該編碼無效撒会。
3嘹朗、支持協(xié)議解析的HTML屬性
在上述的2中場景中我們都可以使用HTML編碼,基本都能夠很好的防御住XSS攻擊诵肛,在第二點中我們強(qiáng)調(diào)的是HTML通用屬性屹培,而并非全部屬性,因為在HTML中H還存在許多支持協(xié)議解析的HTML屬性怔檩,如onclick褪秀,onerror,href薛训,src等媒吗,類似這種屬性是無法通過HTML編碼防范XSS攻擊,因為瀏覽器會先解析HTML編碼的字符乙埃,將其轉(zhuǎn)換為該屬性的值闸英,但是該屬性本身支持JS代碼執(zhí)行,所以瀏覽器在HTML解碼后介袜,對該屬性的值進(jìn)行JS解析甫何,故會執(zhí)行相應(yīng)的代碼。
3.1 href屬性引入的XSS
<a href="javascript:alert('href xss')" target="_blank">href xss</a>
<a href="javascript:alert('href xss HTML編碼無效')" target="_blank">href xss HTML屬性編碼無效</a>
href屬性的值應(yīng)該是一個有效的URL鏈接遇伞,如果我們對整個href屬性值進(jìn)行URL編碼辙喂,則會導(dǎo)致URL無法跳轉(zhuǎn),應(yīng)為瀏覽器在解析href時鸠珠,會通過分號巍耗、/字符解析出協(xié)議或目錄,當(dāng)對全部的值進(jìn)行URL編碼后跳芳,則無法進(jìn)行正常解析芍锦;如果對協(xié)議后的內(nèi)容進(jìn)行URL編碼則也無法防御XSS攻擊,如下例所示:
<a href="javascript:%61%6c%65%72%74%28%27%68%72%65%66%20%78%73%73%20URL部分編碼無效%27%29" target="_blank">Href xss 部分URL編碼無效</a>
此時假如我們對alert('href xss')進(jìn)行JavaScript編碼飞盆,結(jié)果又會如何娄琉?(JavaScript編碼將字符編碼成\x+16進(jìn)制的形式次乓,對款字節(jié)編碼成Unicode)
<a href="javascript:alert\x28\x27href xss\x27\x29" target="_blank" >Href XSS JavaScript編碼</a>
測試點擊沒有任何反應(yīng),XSS執(zhí)行失斈跛票腰;
問什么會存在上述的結(jié)果?我們看一下href屬性的解碼解析過程女气,頁面渲染杏慰,首先進(jìn)行HTML解碼解析,解析出是href屬性后(點擊)炼鞠,會對href的值進(jìn)行URL解碼解析缘滥,獲取到URL的實際值,當(dāng)發(fā)現(xiàn)不是HTTP/HTTPS谒主,而是JavaScript協(xié)議后朝扼,就會執(zhí)行JavaScript解碼解析,從而執(zhí)行了alert()函數(shù)霎肯。
3.2 onclick屬性XSS
現(xiàn)在我們來看一下on事件屬性:<p id="addlinecontent" onclick="addlinecontent($value)">點擊增加一行顯示</p> (此處的$value往往一般都是后臺模板替換的變量)
<p id="addlinecontent" onclick="addlinecontent('$value')">點擊增加一行顯示</p>
<script>
function addlinecontent(value){
? ? document.getElementById("addlinecontent").innerText=value;
}
</script>
當(dāng)$value的值 hello world'),alert('onclick xss 時擎颖,出發(fā)XSS攻擊:
<p id="addlinecontent" onclick="addlinecontent('hell0 world'),alert('onclick xss')" >
對$value進(jìn)行HTML編碼:hello world'),alert('onclick xss htmlencode ,結(jié)果顯示如下观游,存在XSS
此時如果將$value進(jìn)行JavaScript編碼:顯示正常搂捧,不存在XSS
onclick屬性解析的過程:先進(jìn)行HTML解析,再進(jìn)行JavaScript解析懂缕,所以僅僅進(jìn)行HTML編碼是無法防御XSS的允跑,需要對值進(jìn)行JavaScript編碼。
如果當(dāng)存在 <input name="username" onclick="$event" />這種情況時提佣,是很難進(jìn)行防護(hù)的吮蛹,因為無法直接對$event進(jìn)行JavaScript編碼荤崇,如果編碼了拌屏,則無法解析出需要執(zhí)行的函數(shù)代碼。如果整個內(nèi)容是來自客戶端輸入的术荤,那么需要對event需要進(jìn)行黑白名單的校驗倚喂,防止出現(xiàn)危險字符,防止調(diào)用危險的JS函數(shù)。通常不建議出現(xiàn)這種寫法瓣戚,建議對event進(jìn)行拆分端圈,通過控制參數(shù)達(dá)到相應(yīng)的目的:
<input name="" onclick="event(encodeForJS(${eventType}))" />
關(guān)于如何編碼,需要抓住一個重點子库,就是傳進(jìn)的值最終是誰消費(fèi)(期望執(zhí)行者)舱权,誰消費(fèi)誰負(fù)責(zé);對于支持偽協(xié)議的屬性仑嗅,僅僅通過編碼是不行的宴倍,需要匹配過濾協(xié)議頭张症,最好后臺拆分拼接返回期望的值。(不能對協(xié)議類型進(jìn)行任何的編碼操作鸵贬,否則URL解析器會認(rèn)為它無類型)
編碼的順序和解碼解析的順序正好相反:
<p onclick="alert('\x26\x23\x78\x32\x32\x3b')">jsencode(htmlencode("))</p>
<p onclick="alert('\x22')">htmlencode(jsencode("))</p>
4俗他、DOM XSS類型
DOM XSS是基于文檔對象模型的XSS,屬于反射型XSS阔逼。
1. 使用document.write直接輸出
2. 使用innerHTML直接輸出
3. 使用location兆衅、location.href、location.replace嗜浮、iframe.src羡亩、document.referer、window.name等
.......
造成DOM XSS的原因主要是在重新修改頁面時危融,沒有考慮到對變量的編碼和校驗過濾夕春;
<script>
? ? document.body.innerHTML="url:<a href=' "+url+" '>" +url+"</a> ";
</script>
其中對于變量url則是注入點:javascript:alert('dom xss');??
對于DOM XSS主要是由于本地客戶端獲取DOM數(shù)據(jù)在本地執(zhí)行導(dǎo)致的,所以在編碼中专挪,要避免客戶端文檔重寫及志,重定向或其它修改DOM元素操縱,對于輸出至HTML中的值進(jìn)行HTML編碼寨腔,輸出至JS中的值進(jìn)行JS編碼速侈。
5、setTimeout/setInterval/eval等XSS
針對這類的XSS迫卢,我們單獨拿出來說倚搬,因為這類XSS隱藏的相對比較深,尤其經(jīng)過JS層層封裝的函數(shù)乾蛤,我們不知道底層是否使用了這些函數(shù)每界,是否存在變量傳入?
這類函數(shù)有一個共性家卖,函數(shù)本身就是JS函數(shù)眨层,但是能夠?qū)魅氲膮?shù)當(dāng)做JS代碼執(zhí)行。
<textarea id="settimeoutxss" onclick="updatecontent('$value')" rows="8" cols="40"></textarea>
<script>
function updatecontent(url){
????setTimeout("showURL('"+url+"')");
}
function showURL(url){
????document.getElementById("settimeoutxss").value=url;
}
</script>
當(dāng)$value的值為:hello world\'\);alert\(0\);eval(\' 上荡,XSS攻擊成功
當(dāng)你在onclick="updatecontent('$value')"處趴樱,對$value進(jìn)行HTML編碼后,XSS攻擊依舊存在酪捡;
<p onclick="updatecontent('hello world HTML編碼叁征,依舊存在XSS');alert(1);eval('')">Click setTimeOut HTMLEncode</p>
當(dāng)你在onclick="updatecontent('$value')"處,對$value進(jìn)行JavaScript編碼后逛薇,XSS攻擊依舊存在捺疼;(有興趣的可以試一試)
<p onclick="updatecontent('hello world JavaScript編碼,依舊存在XSS\x27\x29\x3balert\x282\x29\x3beval\x28\x27')">Click setTimeout JavaScriptEncode</p>
如果要想對$Value進(jìn)行編碼防范xss永罚,需要對其進(jìn)行doublejsencode啤呼,一次JavaScript編碼是不夠议薪,需要2次,才能保證傳入setTimeout中的參數(shù)url只是個字符串媳友。如果僅僅進(jìn)行一次javascript編碼斯议,能夠保證updatecontent函數(shù)傳入的是字符串,但是setTimeout會將該字符串當(dāng)做代碼執(zhí)行醇锚,如果進(jìn)行2次JS編碼哼御,在setTimeout接收到的參數(shù)是一次JS編碼的值,只會對其進(jìn)行一次JS解碼焊唬,當(dāng)做字符串處理恋昼。
<p onclick="updatecontent('hello world \x5cx27\x5cx29\x5cx3balert\x5cx282\x5cx29\x5cx3beval\x5cx28\x5cx27')">Click setTimeout Double JavaScriptEncode</p>
相關(guān)參考:
新式網(wǎng)絡(luò)瀏覽器幕后揭秘:
www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#The_browser_main_functionality
XSS的原理分析與解剖:XSS的原理分析與解剖 - FreeBuf.COM
瀏覽器Lexer與XSS-HTML編碼:瀏覽器Lexer與XSS-HTML編碼 - FreeBuf.COM
XSS (Cross Site Scripting) Prevention Cheat Sheet:
www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet