以瀏覽器解析機制來理解XSS載荷的編碼轉換
0x00 前言
之前在學習XSS的時候總感覺不是很系統,許多技巧背後原理都沒有理解,光是會用罷了,如部分繞過編碼技巧。
今天打算花時間來補補基礎。
0x00 基礎知識
HTML基礎
常見的字元實體
部分具有特定名稱的字元實體
而對於其他沒有特定名稱的實體來說:
- 十進位制:對應符號的Ascii的值前加上&#,後以;結尾
- 十六進位制:對應符號的Ascii的值換算成16進位制前加上&#x,後以;結尾
注意:字元實體解碼後得到的值為字串型,HTML解析器只將其當做字串文字處理。
HTML元素
共有5種元素:空元素、原始文字元素、RCDATA元素、外來元素以及常規元素。
- 空元素:area、base、br、col、command、embed、hr、img、input、keygen、link、meta、param、source、track、wbr
- 原始文字元素:script、style
- RCDATA元素:textarea、title
- 外來元素:來自MathML名稱空間和SVG名稱空間的元素。
- 常規元素:其他HTML允許的元素都稱為常規元素。
原始文字、RCDATA以及常規元素都有一個開始標籤來表示開始,一個結束標籤來表示結束。某些元素的開始和結束標籤是可以省略的,如果規定標籤不能被省略,那麼就絕對不能省略它。空元素只有一個開始標籤,且不能為空元素設定結束標籤。外來元素可以有一個開始標籤和配對的結束標籤,或者只有一個自閉合的開始標籤,且後者情況下該元素不能有結束標籤。
元素內容限制
空元素不能有任何內容(因為空元素沒有結束標籤,自然沒辦法在開始標籤和結束標籤之間放內容)。
原始文字元素只可以包含文字
RCDATA元素可以包含文字和字元引用,但是文字中不能包含意義不明的符號。
對於外來元素,當開始標籤自閉合時,不能包含任何內容(因為沒有結束標籤,所以不能在開始標籤和結束標籤之間放內容)。當開始標籤不自閉合時,其內容可以包含文字、字元引用、CDATA塊、其他元素和註釋,但是文字不能包含編碼為U+003C的小於符號(<)或者意義不明的符號。
瀏覽器顯示頁面流程:
先逐行載入頁面,並將引用的外部檔案下載下來->接著逐行解析頁面,解析一部分後會將已解析的部分進行渲染,實現邊解析邊渲染。
瀏覽器解析機制
一個HTML解析器作為一個狀態機,它從輸入流中獲取字元並按照轉換規則轉換到另一種狀態。在解析過程中,任何時候它只要遇到一個'<'符號(後面沒有跟'/'符號)就會進入"標籤開始狀態(Tag open state)"。然後轉變到"標籤名狀態(Tag name state)","前屬性名狀態(before attribute name state)"......最後進入"資料狀態(Data state)" 並釋放當前標籤的token。當解析器處於"資料狀態(Data state)"時,它會繼續解析,每當發現一個完整的標籤,就會釋放出一個token。
1.標籤a解析例子
<a href="http://www.0x002.com">0x002</a>
1)起始標籤a
範圍:<a href="http://www.0x002.com">
DataState:碰到<
,進入TagOpenState狀態
TagOpenState:碰到a
,進入TagNameState狀態(HTMLToken的type為StartTag)
TagNameState:碰到空格,進入BeforeAttributeNameState狀態(HTMLToken的m_data為a)
BeforeAttributeNameState:碰到h
,進入AttributeNameState狀態
AttributeNameState:碰到=
,進入BeforeAttributeValueState狀態(HTMLToken屬性列表中加入一個屬性,屬性名為href)
BeforeAttributeValueState: 碰到"
,進入AttributeValueDoubleQuotedState狀態
AttributeValueDoubleQuotedState:碰到b
,保持狀態,提取屬性值
AttributeValueDoubleQuotedState:碰到"
,進入AfterAttributeValueQuotedState(HTMLToken當前屬性的值為http://www.0x002.com).
AfterAttributeValueQuotedState: 碰到>
,進入DataState,完成解析。
在完成startTag的解析的時候,會在解析器中儲存與之匹配的end標籤(m_appropriateEndTagName),等到解析end標籤的時候,會同它進行匹配(語法解析的時候)。
html,body起始標籤類似a起始標籤,但沒有屬性解析
2)a元素內容
DataState:0x002,碰到0
,維持原狀態,提取元素內容(HTMLToken的type為character)。
DataState:0x002,碰到<
,完成解析,不consume'<'。(HTMLToken的m_data為w3c)。
3)a結束標籤
DataState:0x002,碰到<
,進入TagOpenState。
TagOpenState:0x002,碰到/
,進入到EndTagOpenState。(HTMLToken的type為endTag)。
EndTagOpenState:0x002,碰到a
,進入到TagNameState。
TagNameState:0x002,碰到>
,進入到DataState,完成解析。
這部分設計到狀態機的知識,與解析原理有關。
為什麼要講這部分呢?因為他與接下來要講的XSS載荷字元實體編碼有關。
HTML解析器,部分標籤在完成解析時,會按照節點型別、節點屬性等生成不同的解析器去完成接下來的工作。
如a標籤的href屬性,HTML解析器會生成一個Url解析器去解析裡邊的內容。
對於<script>
,HTML解析器生成JS解析器去執行標籤內容,執行時HTML解析器阻塞、渲染阻塞,等待執行完畢後恢復。
0x02 各種編碼講解
1.字元實體編碼
解碼操作在HTML解析器中。
有三種情況可以容納字元實體,"資料狀態中的字元引用","RCDATA狀態中的字元引用"和"屬性值狀態中的字元引用"。在這些狀態中HTML字元實體將會從&#...
形式解碼,對應的解碼字元會被放入資料緩衝區中。例如,在問題4中,"<"和">"字元被編碼為<
和>
。當解析器解析完<div>
並處於"資料狀態"時,這兩個字元將會被解析。當解析器遇到&
字元,它會知道這是"資料狀態的字元引用",因此會消耗一個字元引用(例如"<")並釋放出對應字元的token。在這個例子中,對應字元指的是<
和>
。讀者可能會想:這是不是意味著<
和>
的token將會被理解為標籤的開始和結束,然後其中的指令碼會被執行?答案是指令碼並不會被執行。原因是解析器在解析這個字元引用後不會轉換到"標籤開始狀態"。正因為如此,就不會建立新標籤。因此,我們能夠利用字元實體編碼這個行為來轉義使用者輸入的資料從而確保使用者輸入的資料只能被解析成"資料"。
簡單的說,字元實體編碼僅在下列幾種情況適用:
- 標籤內容(不包括原始文字元素,如
<script>
):<>[在這]</>
- RCDATA元素內容中(
<textarea>、<title>
):<>[在這]</>
- 標籤屬性值內容:
<xx xx="[在這]">(</xx>)
2.URL編碼
解碼操作在URL解析器中
例子:
對於<a href="javascript:alert%281%29"></a>
來說:HTML解析後,把javascript:alert(1)
傳送給URL解析器,此時URL解析器會先尋找:
冒號,以確定該內容的協議。如未找到或無法確定,則預設為http協議,本例中為javascript協議。Url解析器會將協議冒號後邊(若無冒號或無法確定協議,則解碼的群體為全部內容)的字元全部進行一次url解碼,即對alert%281%29進行URL解碼,得到alert(1)。
3.Unicode編碼
解碼操作在Javascript解析器中
JS解析器支援對識別符號進行Unicode編碼。什麼是識別符號?簡單的說,識別符號包括了函式名、字串常量。主要看編碼的字元是構成哪個部分的字元,如:
<script>
Alert('xss');
</script>
對於該例來說,alert(屬於函式名部分,是識別符號)可進行部分或全部的Unicode編碼,xss(屬於字串常量,是識別符號)也可進行全部/部分Unicode編碼。換句話說,經Unicode解碼後的內容只能構成函式名和字串。
0x03 常見新增編碼的XSS載荷講解
1. <a href="%6a%61%76%61%73%63%72%69%70%74:%61%6c%65%72%74%28%31%29"></a>
2. <a href="javascript:%61%6c%65%72%74%28%32%29">
3. <a href="javascript%3aalert(3)"></a>
4. <div><img src=x onerror=alert(4)></div>
5. <textarea><script>alert(5)</script></textarea>
6. <textarea><script>alert(6)</script></textarea>
7. <button onclick="confirm('7');">Button</button>
8. <button onclick="confirm('8\u0027);">Button</button>
9. <script>alert(9);</script>
10. <script>\u0061\u006c\u0065\u0072\u0074(10);</script>
11. <script>\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0031\u0029</script>
12. <script>\u0061\u006c\u0065\u0072\u0074(\u0031\u0032)</script>
13. <script>alert('13\u0027)</script>
14. <script>alert('14\u000a')</script>
15. <a href="javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(15)"></a>
載荷1:
HTML解析器解析a標籤,將屬性href的內容:%6a%61%76%61%73%63%72%69%70%74:%61%6c%65%72%74%28%31%29
傳送給URL編碼器,URL編碼器找到:的位置,但%6a%61%76%61%73%63%72%69%70%74協議不存在,故無法確定協議,預設為http協議。對整個內容進行url解碼,得:javascript:alert(1),最終的結果相當於,載荷無效
載荷2:
HTML解析器在解析a標籤,發現屬性href的值記憶體在字元編碼,將其轉碼為:javascript:%61%6c%65%72%74%28%32%29,將結果傳送給URL解析器,URL解析器尋找:出現的位置,判斷其協議,並將:後邊內容進行URL轉碼,得到:alert(2),由於是javascript協議,URL解析器將內容傳送給JS解析器。載荷有效
載荷3:
同載荷1,由於找不到:,URL轉碼器按照預設http協議進行。結果相當於<a href="http://javascript:alert(3)"></a>
載荷4:
div屬於常見元素中的一個,字元實體可以被成功解碼,不過此時得到的是字串型的資料,無法構成標籤。
即:<div><img src=x onerror=alert(4)></div>
中,=所代表的<
轉碼後得到的值為字串型,無法被HTML解析器解析成構成的標籤起始位的<
。若是<div><img src=x onerror=alert(4)></div>
的話是可以被成功解析的。
載荷5:
Textarea屬於RCDATA元素,無法執行js指令碼,雖支援實體字元編碼,但此時得到的值為字串型,也不能被解析,但可正常顯示。
載荷6:
Textarea屬於RCDATA元素,無法執行js指令碼。
載荷7:
經HTML解析器解析,屬性onclick的值confirm('7');
被Unicode解碼得:confirm('7');
由於是事件型屬性,HTML編碼器直接傳送confirm('7');
給JS解析器,載荷有效。
載荷8:
經HTML解析器解析,屬性onclick的值 confirm('8\u0027);
由於是事件型屬性,HTML編碼器直接傳送confirm('8\u0027);
給JS解析器,雖JS解析器支援Unicode解碼,但該字元不為識別符號(函式名/字串常量),載荷無效。
載荷9:
script標籤屬於原始文字元素,不支援字元實體編碼。載荷無效。
載荷10:
script標籤屬於原始文字元素,HTML解析器直接將內容傳送給JS解析器,JS解析器支援Unicode編碼,且此處\u0061\u006c\u0065\u0072\u0074(10);
為alert(10)
,屬於函式名,滿足識別符號限制,故可解碼得到alert(10)
。載荷有效。
載荷11:
\u0028\u0031\u0031\u0029
解碼後不屬於識別符號,載荷無效。
載荷12:
\u0031\u0032
解碼後為整數型數字,不屬於識別符號,載荷無效。
載荷13:
同11
載荷14:
14\u000a
解碼後,屬於字串型,屬於識別符號,載荷有效。
載荷15:
javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(15)
經HTML實體解碼得:
javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(15)
經URL解碼得:
javascript:\u0061\u006c\u0065\u0072\u0074(15)
經Unicode解碼得:alert(15)
,載荷有效。
0x04 編碼之外
我們知道HTML解析器,在解析時會去掉一些干擾字元,如換行符、回車鍵、跳格鍵t等,故我們可以使用這個特性來繞開部分Filter
例子:
<iframe
src
=
"
j
avas
cript
:
aler
t
(
1)
"
>
</iframe>
0x05 參考資料:
- https://www.jianshu.com/p/c0dc4bbab8e8
- https://blog.csdn.net/wh_xmy/article/details/79567070
- https://www.w3.org/html/ig/zh/wiki/HTML5/syntax
- https://www.freebuf.com/articles/web/100675.html
- https://blog.csdn.net/dlmu2001/article/details/5998130
- https://blog.csdn.net/wh_xmy/article/details/79567070
- https://xz.aliyun.com/t/1556
- https://security.yirendai.com/news/share/26
- http://bobao.360.cn/learning/detail/292.html