您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何對瀏覽器解析和XSS的深度探究,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
為什么要進行編碼?
主要是因為某些數據不適合傳輸。原因多種多樣,如 Size 過大,包含隱私數據,另外重要的一點就是有些字符會引起歧義。
對于 URL: &用于分割多個參數,倘若有某個參數鍵值為 name=v&lue,就會因為 name 參數的值 v&lue 中攜帶了&而造成歧義。因此需要對&進行 URL 編碼。url編碼后,服務端會把緊跟在“%”后的字節當成普通的字節,就是不會把它當成各個參數或鍵值對的分隔符。
對于 HTML: 當瀏覽器遇到<會識別為元素定義的開始,>會識別為元素的結束。倘若有<div id="1>" ></div>,由于標簽的屬性值攜帶了>,同樣會造成歧義。因此需要屬性值的>需要 進行 HTML 編碼,即使用字符實體。
字符實體是一個預先定義好的轉義序列。 字符實體兩種表示方法:
1、 字符實體以&開頭+預先定義的實體名稱+;分號結束,如“<”的實體名稱為&lt;
2、 字符實體還可以以&開頭+#符號+字符在 ASCII 對應的十進制數字+;分號結束,如<的實體編號為&#60;
字符都是有實體編號的,但有些字符是沒有實體名稱
顯示結果 | 描述 | 實體名稱 | 實體編號 |
---|---|---|---|
空格 | &nbsp; | &#160; | |
< | 小于號 | &lt; | &#60; |
> | 大于號 | &gt; | &#62; |
& | 和號 | &amp; | &#38; |
" | 引號 | &quot; | &#34; |
' | 撇號 | &apos; (IE不支持) | &#39; |
¢ | 分(cent) | &cent; | &#162; |
£ | 鎊(pound) | &pound; | &#163; |
¥ | 元(yen) | &yen; | &#165; |
€ | 歐元(euro) | &euro; | &#8364; |
§ | 小節 | &sect; | &#167; |
? | 版權(copyright) | &copy; | &#169; |
? | 注冊商標 | &reg; | &#174; |
? | 商標 | &trade; | &#8482; |
× | 乘號 | &times; | &#215; |
÷ | 除號 | &divide; | &#247; |
最常用的,如\uXXXX 這種寫法的Unicode 轉義序列,表示一個字符,其中 XXXX 表示一個 16 進制數字,如<的Unicode 編碼為\u003c。
RFC3986 文檔規定,URL 中只允許包含英文字母(a-zA-Z)、數字(0-9)、-_.~4 個特殊字 符以及所有保留字符。 RFC3986 中指定了以下字符為保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ]
編碼方式 %加字符在 ASCII 碼表中的十六進制值。例如,/在 ASCII 碼表中十六進制為 0x2f,那么它對應的 URL 編碼為%2f。
JavaScript 中提供了 3 個函數用來對 URL 編碼以得到合法的 URL:
1、 escape()
2、 encodeURI()
3、 encodeURIComponent()
瀏覽器無論什么情況都會遵守一個這樣的解碼規則:
1、 HTML 解析器對 HTML 文檔進行解析,完成 HTML 解碼并且創建 DOM 樹
2、 JavaScript 或者 CSS 解析器對內聯腳本進行解析,完成 JS、CSS 解碼
3、 URL 解碼會根據 URL 所在的順序不同而在 JS 解碼前或者解碼后
3.1.1HTML 中有五類元素:
1、 空元素(Voidelements),有 area、base、br、col、command、embed、hr、img、input、 keygen、link、meta、param、source、track、wbr
2、 原始文本元素(Raw textelements),有<script>和<style>
3、 RCDATA 元素(RCDATA elements),有<textarea>和<title>
4、 外部元素(Foreignelements),例如 MathML 命名空間或者 SVG 命名空間的元素
5、 基本元素(Normal elements),即除了以上 4 種元素以外的元素
五類元素的區別如下:
1、 空元素,不能容納任何內容(因為它們沒有閉合標簽,沒有內容能夠放在開始標簽和閉合標簽中間)。
2、 原始文本元素,可以容納文本。
3、 RCDATA 元素,可以容納文本和字符引用。
4、 外部元素,可以容納文本、字符引用、CDATA 段、其他元素和注釋
5、 基本元素,可以容納文本、字符引用、其他元素和注釋
3.1.2 HTML編解碼
HTML 解析器以狀態機的方式運行,它從文檔輸入流中消耗字符并根據其轉換規則轉換到不同的狀態。
示例:
<html><body>Hello world</body></html>
1、 初始狀態為"DataState",當遇到"<"字符,狀態變為"Tag open state",讀取一個 a-z 的字符將產生一個開始標簽符號,狀態相應變為"Tag name state",一直保持這個狀態直到讀取到">",每個字符都附加到這個符號名上,例子中創建的是一個 html 符號。
2、 當讀取到">",當前的符號就完成了,此時,狀態回到"Data state","<body>"重復這一處理過程。到這里,html 和 body 標簽都識別出來了。現在,回到"Data state",讀取"Helloworld"中的字符"H"將創建并識別出一個字符符號,這里會為"Hello world"中的每個字符 生成一個字符符號。
3、 這樣直到遇到"</body>"中的"<"。現在,又回到了"Tag openstate",讀取下一個字符"/" 將創建一個閉合標簽符號,并且狀態轉移到"Tag name state",還是保持這一狀態,直到 遇到">"。然后,產生一個新的標簽符號并回到"Data state"。后面的"</html>"將和 "</body>"一樣處理。
總結:當HTML 解析器處于數據狀態(DataState)、RCDATA 狀態(RCDATA State)、屬性值狀態(Attribute Value State)時,字符實體會被解碼為對應的字符。
示例:
<div>&#60;img src=x onerror=alert(4)&#62;</div>
<和>被編碼為字符實體&#60;和&#62;。 當 HTML 解析器解析完<div>時,會進入數據狀態(Data State)并發布標簽令牌。接著解析到實體&#60;時因為處在數據狀態(Data State)就會對實體進行解碼為<,后面 的&#62;同樣道理被解碼為>。
這里會有個問題,被解碼后,img是否會被解析為 HTML 標簽而導致 JS 執行呢?
答案是否定的。因為解析器在使用字符引用后不會轉換到標簽打開狀態(Tag OpenState),不進入標簽打開狀態就不會被發布為 HTML 標簽。因此,不會創建新 HTML 標簽, 只會將其作為數據來處理。 這也是為什么我們可以使用字符實體來避免用戶不安全輸入導致 XSS 的原因。
3.1.3 原始文本元素(Raw text elements)
在 HTML 中,屬于 Rawtext elements 的標簽有兩個:script、style。在 Raw text elements 類型標簽下的所有內容塊都屬于該標簽。
存在一條特性: Raw text elements 類型標簽下的所有字符實體編碼都不會被 HTML 解碼。HTML 解析器 解析到 script、style 標簽的內容塊(數據)部分時,狀態會進入 Script Data State,該狀態并 不在我們前面說的會解碼字符實體的三條狀態之中。
因此,<script>&#97;&#108;&#101;&#114;t&#40;&#57;&#41;&#59;</script>這樣字符實體并不會被解碼,也就不會執行 JS。
3.1.4 RCDATA
在 HTML 中,屬于RCDATAElements的標簽有兩個:textarea、title。
RCDATA Elements 類型的標簽可以包含文本內容和字符實體。
解析器解析到 textarea、title標簽的數據部分時,狀態會進入 RCDATA State。
前面我們提到,處于 RCDATA State 狀態時,字符實體是會被解析器解碼的。
示例:
<textarea>&#60;script&#62;alert(5)&#60;/script&#62;</textarea>
<和>被編碼為實體&#60;和&#62;。 解析器解析到它們時會進行解碼,最終得到<textarea><script>alert(5)</script></textarea>。但是里面的 JS 同樣還是不會被執 行,原因還是因為解碼字符實體狀態機不會進入標簽打開狀態(TagOpen State),因此里面的<script>并不會被解析為 HTML 標簽。
3.1.5 外部元素(Foreign elements)
來源于 MathML 和 SVG 命名空間
<svg>遵循 XML 和 SVG 的定義
實例:
<script>alert&#40;1)</script>
不能彈窗,Raw text elements 類型標簽下的所有字符實體編碼都不會被 HTML 解碼
<svg><script>alert&#40;1)</script>
能彈窗,在 XML 中,&#40;會被解析成(,在 XML 中實體會自動轉義,除了<![CDATA[和]]> 包含的實體
形如 \uXXXX 這樣的 Unicode 字符轉義序列或 Hex 編碼是否能被解碼需要看情況。 首先,JavaScript 中有三個地方可以出現 Unicode 字符轉義序列:
1、字符串中(in String)
Unicode 轉義序列出現在字符串中時,它只會被解釋為普通字符,而不會破壞字符串的上下文。
例如,<script>alert("\u0031\u0030");</script>
被編碼轉義的部分為 10,是字符串,會被正常解碼,JS 代碼也會被執行。
2、標識符中(in identifier names)
若 Unicode 轉義序列存在于標識符中,即變量名(如函數名等…),它會被進行解碼。
例如,<script>\u0061\u006c\u0065\u0072\u0074(10);</script>
被編碼轉義的部分為 alert 字符,是函數名,屬于在標識符中的情況,因此會被正常解碼,JS 代碼也會被執行。
3、控制字符中(in control characters)
若 Unicode 轉義序列存在于控制字符中,那么它會被解碼但不會被解釋為控制字符,而會被解釋為標識符或字符串字符的一部分。 控制字符即'、"、()等。
例如,<script>alert\u0028"xss"); </script>,(進行了 Unicode 編碼,那么解碼后它不再是作為控制字符,而是作為標識符的一部分alert(。
因此函數的括號之類的控制字符進行Unicode 轉義后是不能被正常解釋的。
總結:Unicode 序列不能出現在控制字符中,否則不能被解釋。
示例 1:
<script>\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0031\u0029</script>
被編碼部分為 alert(11)。
該例子中的 JS 不會被執行,因為控制字符被編碼了。
示例 2:
<script>\u0061\u006c\u0065\u0072\u0074(\u0031\u0032)</script>
被編碼部分為 alert 及括號內為12。
該例子中 JS 不會被執行,原因在于括號內被編碼的部分不能被正常解釋,即使反編碼之后為數字,但是仍然按照字符串來處理(這里的12為字符串12,并不是int整數)。要么使用 ASCII 數字,要么加""或''使其變為字符串,作為字符串也只能作為普通字符。
示例 3:
<script>alert('13\u0027)</script>
被編碼處為'。
該例的 JS 不會執行,因為控制字符被編碼了,解碼后的'將變為字符串的一部分,而不再解釋為控制字符。因此該例中字符串是不完整的,因為沒有'來結束字符串。
示例 4:
<script>alert('14\u000a')</script>
該例的 JS 會被執行,因為被編碼的部分處于字符串內,只會被解釋為普通字符,不會突破字符串上下文。
示例 5:
<img src="1" onerror=\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029>
此例無法執行。我們以瀏覽器的視角來看:首先讀到<開始讀取標簽,然后讀到 onerror 調用 JS 解析器。 在JS中,單引號,雙引號和圓括號等屬于控制字符,編碼后將無法識別。所以對于防御來說,應該編碼這些控制字符。
下面這種方式可以解析:
<img src="1" onerror=\u0061\u006c\u0065\u0072\u0074('\u0031')>
可以結合上面的 HTML 編碼 按照解析順序反過去,先 JS 編碼然后 HTML 解碼:
<img src="1" onerror=&#92;&#117;&#48;&#48;&#54;&#49;&#92;&#117;&#48;&#48;&#54;&#99;&#92;&#117;&#48;&#48;&#54;&#53;&#92;&#117;&#48;&#48;&#55;&#50;&#92;&#117;&#48;&#48;&#55;&#52;&#40;&#39;&#92;&#117;&#48;&#48;&#51;&#49;&#39;&#41;>
瀏覽器讀到了<標簽開始構造語法樹,然后 HTML 解碼,解碼之后發現 onerror 于是進行 一個 JS 解碼,成功彈窗
延伸:
開發人員單純的設置 HTML 實體編碼為防御 xss 的手段,但是用戶輸入點在 alert 中 <img src = "https://text.com" onclick = 'alert("輸入點")'>
如果用戶正常輸入的話凡是存在< ," 等都能被轉碼
攻擊者可以通過語句 ");alert("test,在服務端被轉碼:
<img src ="https://gss1.bdstatic.com" onclick = 'alert("FIRSTXSS&#34;&#41;&#59;&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#116;&#101;&#115;&#116;")'>
彈窗兩次,是因為瀏覽器進行 HTML 解碼發現存在兩個 alert()
所以對于這種情況,正確防御 XSS 的方法:
應該是先 JavaScript 編碼然后再進行 HTML 編碼用戶輸入 ");alert("test 后在服務端先 JavaScript 編碼然后再進行 HTML 編碼到瀏覽器端:首先經過第一步 HTML 解碼后變為\u0022\u0029\u003B\u0061\u006C\u0065\u0072\u0074\u0028\u0022\u0074\u0065\u0073\u0074。JavaScript 解析器工作,變為");alert("test ,剛才已經講過 JavaScript 解析時只有標識符名稱不會被當做字符串,控制字符僅會被解析為標示符名稱或者字符串,因此 \u0022 被解釋成雙引號文本,\u0028 和\u0029 被解釋成為圓括號文本,不會變為控制 字符被解析執行。
在這里采用的先 JS 編碼后 HTML 編碼中只彈窗了一次。
通用 URI 的格式如下:
[協議名]://[用戶名]:[密碼]@[主機名]:[端口]/[路徑]?[查詢參數]#[片段 ID]
URL 解析器也被建模為狀態機,文檔輸入流中的字符可以將其導向不同的狀態。
首先,要注意的是 URL 的 Scheme 部分(協議部分)必須為 ASCII 字符,即不能被任何編碼,否則 URL 解析器的狀態機將進入 No Scheme 狀態。
示例1:
<ahref="%6a%61%76%61%73%63%72%69%70%74:%61%6c%65%72%74%28%31%29"></a>
URL 編碼部分的是javascript:alert(1)。
JS 不會被執行,因為作為Scheme 部分的"javascript"這個字符串被編碼(其中javascript是一種偽協議),導致 URL 解析 器狀態機進入 No Scheme 狀態。
URL 中的:也不能被以任何方式編碼,否則 URL 解析器的狀態機也將進入 No Scheme 狀態。
<ahref="javascript%3aalert(3)"></a>
由于:被 URL 編碼為%3a,導致 URL 狀態機進入 NoScheme 狀態,JS 代碼不能執行。
示例2:
<ahref="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;:%61%6c%65%72%74%28%32%29">
"javascript"這個字符串被實體化編碼,:沒有被編碼,alert(2)被 URL編碼。 成功執行 首先,在 HTML 解析器中我們談到過,HTML狀態機處于屬性值狀態(Attribute Value State)時,字符實體時會被解碼的,此處在 href 屬性中,所以被實體化編碼的 "javascript"字符串會被解碼。其次,HTML 解析是在URL解析之前的,所以在進行 URL 解析之前,Scheme 部分的"javascript"字符串已被解碼,而并不再是被實體編碼的狀態。
首先瀏覽器接收到一個 HTML 文檔時,會觸發 HTML 解析器對 HTML 文檔進行詞法解析,這一過程完成 HTML 解碼并創建 DOM 樹。
接下來 JavaScript 解析器會介入對內聯腳本進行解析,這一過程完成 JS 的解碼工作。
如果瀏覽器遇到需要 URL 的上下文環境,這時 URL 解析器也會介入完成 URL 的解碼工 作,URL 解析器的解碼順序會根據 URL 所在位置不同,可能在 JavaScript 解析器之前或之后解析。
總之HTML 解析總是第一步。URL 解析和 JavaScript 解析,它們的解析順序要根據情況而定。
示例 1:
<a href="UserInput"></a>
該例子中,首先由 HTML 解析器對UserInput 部分進行字符實體解碼;
接著 URL 解析器對UserInput 進行 URL decode;
如果 URL 的 Scheme 部分為 javascript 的話,JavaScript 解析器會再對 UserInput 進行解 碼。
所以解析順序是:HTML 解析->URL解析->JavaScript 解析。
示例 2:
<a href=# onclick="window.open('UserInput')"></a>
該例子中,首先由 HTML 解析器對UserInput 部分進行字符實體解碼;
接著由 JavaScript 解析器會再對 onclick 部分的 JS 進行解析并執行 JS;
執行 JS 后window.open('UserInput')函數的參數會傳入 URL,所以再由 URL 解析器對 UserInput 部分進行解碼。
因此解析順序為:HTML 解析->JavaScript解析->URL 解析。
示例 3:
<a href="javascript:window.open('UserInput')">
該例子中,首先還是由 HTML 解析器對 UserInput 部分進行字符實體解碼;
接著由 URL 解析器解析 href 的屬性值;
然后由于Scheme為javascript,所以由 JavaScript 解析;
解析執行 JS 后window.open('UserInput')函數傳入 URL,所以再由 URL 解析器解析。
所以解析順序為:HTML 解析->URL解析->JavaScript 解析->URL 解析。
綜合實例:
<a href="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3a;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x33;&#x31;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x36;&#x33;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x33;&#x35;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x37;&#x25;&#x33;&#x32;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x37;&#x25;&#x33;&#x34;&#x28;&#x31;&#x35;&#x29;"></a>
首先 HTML 解析器進行解析,解析到href 屬性的值時,狀態機進入屬性值狀態(Attribute Value State),該狀態會解碼字符實體;
接著由 URL 解析器進行解析并解碼;
再接著由于 Scheme 為javascript,因此由 JavaScript 解析器解析并解碼,加上編碼部分是函 數名,屬于標識符,因此可以正常解碼解釋;
經過三輪解析解碼后得到結果:<a href="javascript:alert(15)"></a>
關于如何對瀏覽器解析和XSS的深度探究就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。