瀏覽器工作原理與XSS-HTML編碼

?簡(jiǎn)介

編碼問題一直是個(gè)痛點(diǎn),尤其是當(dāng)我們對(duì)XSS攻擊原理不是很熟悉的話,防護(hù)起來很容易造成遺漏。要想很好的防護(hù)住XSS攻擊,需要對(duì)瀏覽器解析HTML、JS、CSS的原理弄清楚,了解瀏覽器的工作原理,才能做好防護(hù)工作。小編也是搜集網(wǎng)上資料進(jìn)行學(xué)習(xí),并整理分享下該篇文檔。


(一)瀏覽器的結(jié)構(gòu)?

瀏覽器的主要組件,包含用戶界面、瀏覽器引擎、呈現(xiàn)引擎、網(wǎng)絡(luò)、用戶界面后端、JavaScript解釋器、數(shù)據(jù)存儲(chǔ)。這里我們主要需要了解呈現(xiàn)引擎,其主要負(fù)責(zé)顯示請(qǐng)求內(nèi)容,如果請(qǐng)求的內(nèi)容是HTML,它就負(fù)責(zé)解析HTML和CSS內(nèi)容,并將解析后的內(nèi)容顯示在屏幕上。

值得注意的是,Chrome瀏覽器的每個(gè)標(biāo)簽頁都分別對(duì)應(yīng)一個(gè)呈現(xiàn)引擎實(shí)例,每個(gè)標(biāo)簽頁都是獨(dú)立的進(jìn)程。

呈現(xiàn)引擎一開始會(huì)從網(wǎng)絡(luò)層獲取請(qǐng)求文檔的內(nèi)容,內(nèi)容的大小一般限制在 8000 個(gè)塊以內(nèi)。

呈現(xiàn)引擎解析HTML文檔,將標(biāo)記轉(zhuǎn)換成內(nèi)容樹上的DOM節(jié)點(diǎn),將CSS元素樣式轉(zhuǎn)換成另外的樹結(jié)構(gòu):呈現(xiàn)樹。構(gòu)建完成后,進(jìn)入布局階段,每個(gè)節(jié)點(diǎn)都會(huì)分配一個(gè)應(yīng)該出現(xiàn)在屏幕上的坐標(biāo),由用戶界面后端層將每個(gè)節(jié)點(diǎn)繪制出來。

解析的過程其實(shí)就是編譯原理那一套東西,由解析器和詞法分析器將文檔內(nèi)容構(gòu)造成一個(gè)有效的解析樹,最后由翻譯器,將解析樹翻譯成瀏覽器可執(zhí)行的機(jī)器指令,最后我們看到的就是一個(gè)可視化的web頁面。

HTML解析器的任務(wù)是將HTML標(biāo)記解析成解析樹,常規(guī)解析器是不適用與HTML的,因?yàn)槲覀冎繦TML具有很強(qiáng)的容錯(cuò)性,并不是與上下文無關(guān)的語法,我們會(huì)通過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)于具體如何解析,便不在這里闡述,大家只需要有個(gè)大致的了解即可。

處理腳本和樣式表的順序

HTMl==》CSS==》JavaScript 針對(duì)這個(gè)順序我是報(bào)遲疑態(tài)度的,因?yàn)樯鲜鑫覀円蔡岬竭^,在遇到<script>腳本時(shí)會(huì)立即解析并執(zhí)行腳本,文檔解析將停止,直到腳本執(zhí)行完畢。如果腳本是外部的,解析過程會(huì)停止,直到從網(wǎng)絡(luò)同步抓取資源完成后再繼續(xù)。在HTML5中增加了一個(gè)選項(xiàng),可將腳本標(biāo)記為異步,以便由其它線程解析和執(zhí)行。

?(二)瀏覽器解碼過程?

HTMl==》CSS==》JavaScript ,針對(duì)這個(gè)解析順序,我們這里可以先不討論,我們的目的是關(guān)心如何正確的編碼,那么我只需要關(guān)注,對(duì)于常用的HTML頁面,瀏覽器是如何解碼的即可。

1、HTML實(shí)體

<p>${content}</p>

如上述代碼所示,在P標(biāo)簽中存在一個(gè)輸出變量${content},瀏覽器解析的過程,首先是HTML解析,解析到P標(biāo)簽時(shí),解析Content的內(nèi)容,然后將其在頁面顯示出來。

<p><script>alert("實(shí)體XSS");</script></p>

如果我們把Content的內(nèi)容換成上面內(nèi)容,即script腳本,那么瀏覽器解析的時(shí)候,當(dāng)解析到P標(biāo)簽時(shí),發(fā)現(xiàn)里面的內(nèi)容存在script標(biāo)簽,便會(huì)把其當(dāng)做JavaScript腳本進(jìn)行解析,從而達(dá)到XSS攻擊的目的。

?所以針對(duì)此類HTML實(shí)體間的輸出,我們希望輸出的是HTML文本內(nèi)容,而不是HTML標(biāo)簽、JS代碼等,所以我們?cè)谳敵鰰r(shí),需要對(duì)Content進(jìn)行HTML編碼,可使用OWASP ESAPI的ESAPI.encoder().encodeForHTML()。 HTML編碼一般將如下幾個(gè)字符進(jìn)行編碼替換:

1. & —> &amp;

2. < —> &lt;

3. > —> &gt;

4. " —> &quot;

5. ' —> &#x27;

6. / —> &#x2F;

在編碼的字符中,其中&、<、>、"、' 五個(gè)字符是XML中定義的實(shí)體,所以我們需要對(duì)其進(jìn)行編碼,因?yàn)镠TML也算作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解析時(shí),會(huì)對(duì)name屬性的內(nèi)容進(jìn)行HTML解碼,假如此時(shí)${firstname}的值為如下內(nèi)容時(shí),HTML屬性被截?cái)嗖迦肓薿nclick事件。

<input name=" " onclick="alert('屬性XSS')" " "></input>

所以針對(duì)這種常規(guī)的HTML屬性,都需要對(duì)其進(jìn)行HTML屬性編碼,其實(shí)和HTML實(shí)體編碼類似,只不過編碼字符范圍可能多些,除了字母數(shù)字外,所有ASCII碼小于256的字符均進(jìn)行&#XXX;編碼,具體的參見ESAPI.encoder().encodeForHTMLAttribute的實(shí)現(xiàn)。其實(shí)屬性XSS中最主要的是拆分屬性標(biāo)簽,注入可執(zhí)行JS的屬性,并對(duì)其添加而已代碼。

對(duì)于通用屬性的編碼方式不適用于href、src、style、事件處理函數(shù)(onclick、onmouseover等),因?yàn)楸旧磉@些屬性是支持偽協(xié)議的,該編碼無效。

3、支持協(xié)議解析的HTML屬性

在上述的2中場(chǎng)景中我們都可以使用HTML編碼,基本都能夠很好的防御住XSS攻擊,在第二點(diǎn)中我們強(qiáng)調(diào)的是HTML通用屬性,而并非全部屬性,因?yàn)樵贖TML中H還存在許多支持協(xié)議解析的HTML屬性,如onclick,onerror,href,src等,類似這種屬性是無法通過HTML編碼防范XSS攻擊,因?yàn)闉g覽器會(huì)先解析HTML編碼的字符,將其轉(zhuǎn)換為該屬性的值,但是該屬性本身支持JS代碼執(zhí)行,所以瀏覽器在HTML解碼后,對(duì)該屬性的值進(jìn)行JS解析,故會(huì)執(zhí)行相應(yīng)的代碼。

3.1 href屬性引入的XSS

<a href="javascript:alert('href xss')" target="_blank">href xss</a>

<a href="javascript&#x3a;alert&#x28;&#x27;href&#x20;xss&#x20;HTML編碼無效&#x27;&#x29;" target="_blank">href xss HTML屬性編碼無效</a>

href屬性的值應(yīng)該是一個(gè)有效的URL鏈接,如果我們對(duì)整個(gè)href屬性值進(jìn)行URL編碼,則會(huì)導(dǎo)致URL無法跳轉(zhuǎn),應(yīng)為瀏覽器在解析href時(shí),會(huì)通過分號(hào)、/字符解析出協(xié)議或目錄,當(dāng)對(duì)全部的值進(jìn)行URL編碼后,則無法進(jìn)行正常解析;如果對(duì)協(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>

此時(shí)假如我們對(duì)alert('href xss')進(jìn)行JavaScript編碼,結(jié)果又會(huì)如何?(JavaScript編碼將字符編碼成\x+16進(jìn)制的形式,對(duì)款字節(jié)編碼成Unicode)

<a href="javascript:alert\x28\x27href xss\x27\x29" target="_blank" >Href XSS JavaScript編碼</a>

測(cè)試點(diǎn)擊沒有任何反應(yīng),XSS執(zhí)行失敗;

問什么會(huì)存在上述的結(jié)果?我們看一下href屬性的解碼解析過程,頁面渲染,首先進(jìn)行HTML解碼解析,解析出是href屬性后(點(diǎn)擊),會(huì)對(duì)href的值進(jìn)行URL解碼解析,獲取到URL的實(shí)際值,當(dāng)發(fā)現(xiàn)不是HTTP/HTTPS,而是JavaScript協(xié)議后,就會(huì)執(zhí)行JavaScript解碼解析,從而執(zhí)行了alert()函數(shù)。

3.2 onclick屬性XSS

現(xiàn)在我們來看一下on事件屬性:<p id="addlinecontent" onclick="addlinecontent($value)">點(diǎn)擊增加一行顯示</p> (此處的$value往往一般都是后臺(tái)模板替換的變量)

<p id="addlinecontent" onclick="addlinecontent('$value')">點(diǎn)擊增加一行顯示</p>

<script>

function addlinecontent(value){

? ? document.getElementById("addlinecontent").innerText=value;

}

</script>

當(dāng)$value的值 hello world'),alert('onclick xss 時(shí),出發(fā)XSS攻擊:

<p id="addlinecontent" onclick="addlinecontent('hell0 world'),alert('onclick xss')" >

對(duì)$value進(jìn)行HTML編碼:hello world&#x27;&#x29;&#x2c;alert&#x28;&#x27;onclick xss htmlencode ,結(jié)果顯示如下,存在XSS

此時(shí)如果將$value進(jìn)行JavaScript編碼:顯示正常,不存在XSS

onclick屬性解析的過程:先進(jìn)行HTML解析,再進(jìn)行JavaScript解析,所以僅僅進(jìn)行HTML編碼是無法防御XSS的,需要對(duì)值進(jìn)行JavaScript編碼。

如果當(dāng)存在 <input name="username" onclick="$event" />這種情況時(shí),是很難進(jìn)行防護(hù)的,因?yàn)闊o法直接對(duì)$event進(jìn)行JavaScript編碼,如果編碼了,則無法解析出需要執(zhí)行的函數(shù)代碼。如果整個(gè)內(nèi)容是來自客戶端輸入的,那么需要對(duì)event需要進(jìn)行黑白名單的校驗(yàn),防止出現(xiàn)危險(xiǎn)字符,防止調(diào)用危險(xiǎn)的JS函數(shù)。通常不建議出現(xiàn)這種寫法,建議對(duì)event進(jìn)行拆分,通過控制參數(shù)達(dá)到相應(yīng)的目的:

<input name="" onclick="event(encodeForJS(${eventType}))" />

關(guān)于如何編碼,需要抓住一個(gè)重點(diǎn),就是傳進(jìn)的值最終是誰消費(fèi)(期望執(zhí)行者),誰消費(fèi)誰負(fù)責(zé);對(duì)于支持偽協(xié)議的屬性,僅僅通過編碼是不行的,需要匹配過濾協(xié)議頭,最好后臺(tái)拆分拼接返回期望的值。(不能對(duì)協(xié)議類型進(jìn)行任何的編碼操作,否則URL解析器會(huì)認(rèn)為它無類型)

編碼的順序和解碼解析的順序正好相反:

<p onclick="alert('\x26\x23\x78\x32\x32\x3b')">jsencode(htmlencode("))</p>

<p onclick="alert('&#x5c;&#x78;&#x32;&#x32;')">htmlencode(jsencode("))</p>

4、DOM XSS類型

DOM XSS是基于文檔對(duì)象模型的XSS,屬于反射型XSS。

1. 使用document.write直接輸出

2. 使用innerHTML直接輸出

3. 使用location、location.href、location.replace、iframe.src、document.referer、window.name等

.......

造成DOM XSS的原因主要是在重新修改頁面時(shí),沒有考慮到對(duì)變量的編碼和校驗(yàn)過濾;

<script>

? ? document.body.innerHTML="url:<a href=' "+url+" '>" +url+"</a> ";

</script>

其中對(duì)于變量url則是注入點(diǎn):javascript:alert('dom xss');??

對(duì)于DOM XSS主要是由于本地客戶端獲取DOM數(shù)據(jù)在本地執(zhí)行導(dǎo)致的,所以在編碼中,要避免客戶端文檔重寫,重定向或其它修改DOM元素操縱,對(duì)于輸出至HTML中的值進(jìn)行HTML編碼,輸出至JS中的值進(jìn)行JS編碼。

5、setTimeout/setInterval/eval等XSS

針對(duì)這類的XSS,我們單獨(dú)拿出來說,因?yàn)檫@類XSS隱藏的相對(duì)比較深,尤其經(jīng)過JS層層封裝的函數(shù),我們不知道底層是否使用了這些函數(shù),是否存在變量傳入?

這類函數(shù)有一個(gè)共性,函數(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')"處,對(duì)$value進(jìn)行HTML編碼后,XSS攻擊依舊存在;

<p onclick="updatecontent('hello world HTML編碼,依舊存在XSS&#x27;&#x29;&#x3b;alert&#x28;1&#x29;&#x3b;eval&#x28;&#x27;')">Click setTimeOut HTMLEncode</p>

當(dāng)你在onclick="updatecontent('$value')"處,對(duì)$value進(jìn)行JavaScript編碼后,XSS攻擊依舊存在;(有興趣的可以試一試)

<p onclick="updatecontent('hello world JavaScript編碼,依舊存在XSS\x27\x29\x3balert\x282\x29\x3beval\x28\x27')">Click setTimeout JavaScriptEncode</p>

如果要想對(duì)$Value進(jìn)行編碼防范xss,需要對(duì)其進(jìn)行doublejsencode,一次JavaScript編碼是不夠,需要2次,才能保證傳入setTimeout中的參數(shù)url只是個(gè)字符串。如果僅僅進(jìn)行一次javascript編碼,能夠保證updatecontent函數(shù)傳入的是字符串,但是setTimeout會(huì)將該字符串當(dāng)做代碼執(zhí)行,如果進(jìn)行2次JS編碼,在setTimeout接收到的參數(shù)是一次JS編碼的值,只會(huì)對(duì)其進(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容