<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/6905

            前幾天,在微博上看到一條關于最近的 Chrome XSS Filter Bypass 的鏈接:webo,原始補丁在這里:補丁。在補丁中還提供了 PoC 用于后續的單元測試。

            攻擊者用一種巧妙的方法繞過了 Chrome 的 XSSAuditor 的過濾。不過微博里的那篇短文并沒有對這個漏洞的緣由作分析。碰巧前段時間筆者仔細讀過 Chrome 的 XSSAuditor 的代碼,因此趁此機會自己分析了一下,如果有錯誤的地方還望指教。

            0x00 漏洞描述


            原始 PoC :

            https://localhost/<svg><script>/<1/>alert(0)</script>
            

            補丁如下:

            #!diff
            Index: Source/core/html/parser/XSSAuditor.cppt a/Source/core/html/parser/XSSAuditor.cpp b/Source/core/html/parser/XSSAuditor.cpp
            index a1e1852201d23ac858c3b5065a2e26f52d128f4d..e73259145366c12c821a710fe83d3637529478ee 100644
            --- a/Source/core/html/parser/XSSAuditor.cpp
            +++ b/Source/core/html/parser/XSSAuditor.cpp
            @@ -471,15 +471,18 @@ bool XSSAuditor::filterCharacterToken(const FilterTokenRequest& request)
                 if (m_state == PermittingAdjacentCharacterTokens)
                     return false;
            
            -    if ((m_state == SuppressingAdjacentCharacterTokens)
            -        || (m_scriptTagFoundInRequest && isContainedInRequest(canonicalizedSnippetForJavaScript(request)))) {
            +    if (m_state == FilteringTokens && m_scriptTagFoundInRequest) {
            +        String snippet = canonicalizedSnippetForJavaScript(request);
            +        if (isContainedInRequest(snippet))
            +            m_state = SuppressingAdjacentCharacterTokens;
            +        else if (!snippet.isEmpty())
            +            m_state = PermittingAdjacentCharacterTokens;
            +    }
            +    if (m_state == SuppressingAdjacentCharacterTokens) {
                     request.token.eraseCharacters();
                     request.token.appendToCharacter(' '); // Technically, character tokens can't be empty.
            -        m_state = SuppressingAdjacentCharacterTokens;
                     return true;
                 }
            -
            -    m_state = PermittingAdjacentCharacterTokens;
                 return false;
             }
            

            補丁中的描述對這一漏洞進行了介紹,大意是,當過濾器過濾 script 標簽的內容時,第一個區塊的過濾結果將會影響后續區塊。如果第一個區塊被處理為空時,過濾匹配將會失敗。

            其實漏洞原理這一句話就介紹明白了。不過,如果對瀏覽器解析 HTML 的流程以及 XSS Filter 的實現沒有了解的話,可能看不大懂上面這句話。

            0x01 背景知識


            首先,瀏覽器是如何解析 HTML 的?

            這里只簡單的介紹一下和該漏洞相關的部分。

            實際上,在 HTML5 中, HTML 的詞法解析和語法解析是被寫進規范里的,這個可以直接在 WHATWG 主頁上查到。從筆者讀源碼的了解上看,Chrome(Chromium) 幾乎完全遵循了該規范。

            其中詞法解析部分,將 HTML 源碼解析成一個個 token ,比如

            #!html
            <div>aaa<img src=x><script>x=1;</script>
            

            將被解析成

            [Start Tag (div)][Characters (aaa)][Start Tag (img)(attr: {src: x})][Start Tag (script)][Characters x=1;][End Tag (script)]
            

            每個中括號即為一個 token ,每一個 token 都有一堆自己的屬性,比如 token 的名稱,屬性值等。

            SVG 標簽有什么特殊的性質?

            實際上,一般的 HTML 標簽屬于 HTML namespace 范疇下,而 SVG 標簽屬于 SVG namespace 范疇。在 SVG 內部,HTML 的解析是按照 XML 模式進行的(與此類似的有 MATHML 標簽)。而 SVG 內部也支持 SCRIPT 標簽,不過這里的 SCRIPT 標簽的解析模式和 HTML 環境下有著很大的區別。某些時候,可以借助 SVG 中 SCRIPT 標簽的特殊解析模式,構造一些特殊的攻擊向量。比如:

            #!html
            <svg><script>alert&#40/1/&#41</script> 
            

            在 XML 中實體符號會被解析成對應的字符。而在 HTML 環境中,SCRIPT 標簽內部不會被做任何處理。

            類似的,下面這種用法 Javascript 也會成功執行:

            #!html
            <svg><script>0<a></a>;alert(1)</script></svg>
            

            因為在 SVG 內,進入SCRIPT 標簽內部后,不必等到出現 </script> 才退回標簽解析模式,而是一旦遇到了新的標簽,即可退出,因此在 SCRIPT 標簽內依然可以插入其他標簽。

            Chrome 是如何過濾反射型 XSS 的?

            Chrome 的 XSS 過濾并沒有用到正則。而且,這個過濾是在詞法解析階段進行的。也就是說,Chrome 實際上是根據 token 來做過濾的。過濾器會審查每一個 token ,如果發現 token 中存在危險的屬性或字段,就對該字段進行處理,然后拿去和 URL 比對。如果發現 URL 中出現了該字段,即將該字段清空,并報告惡意腳本的插入。

            比如上面的

            #!html
            <div>aaa<img src=x><script>x=1;</script>
            
            [Start Tag (div)][Characters (aaa)][Start Tag (img)(attr: {src: x})][Start Tag (script)][Characters x=1;][End Tag (script)]
            

            首先解析器讀取到 [Start Tag (div)],判斷沒有危險;接下來讀取 [Characters (aaa)] ,也沒有危險;然后讀取 [Start Tag (img)(attr: {src: x})] ,過濾器將會檢查 src 屬性是否以 javascript: 開頭,這里不是,同樣沒有危險;之后檢查 [Start Tag (script)],這里過濾器將會到 URL 中找 script 字樣,來確認 URL 是否有引入腳本的可能;并且從這里開始進入了 script 標簽內部,過濾器會對此進行標識;之后到了 [Characters x=1;] ,注意到這里已經到了 SCRIPT 標簽內部了,因此,過濾器將會拿 "x=1;" 在 URL 中搜索,這也是文章開頭的 Patch 中出現的代碼:

            #!diff
             -    if ((m_state == SuppressingAdjacentCharacterTokens)
             -        || (m_scriptTagFoundInRequest && isContainedInRequest(canonicalizedSnippetForJavaScript(request)))) {
            

            0x02 漏洞分析


            這里我們先不管這個 Patch 是如何修復的,我們先來分析漏洞的成因。

            PoC 中的注入代碼將會解析成怎樣的 token 序列呢?

            #!html
            <svg><script>/<1/>alert(0)</script>
            

            是這樣么?

              [Start Tag (svg)][Start Tag (script)][Characters /<1/>alert(0)][End Tag (script)]
            

            如果是這樣, XSS Filter 是無法繞過的。

            在這里,由于是在 SVG 標簽內部,詞法解析器將會考察每一個 "<" ,當在字符狀態中出現 "<" 時,即意味著進入了一個新的標簽,詞法解析器會將之前的字符串作為一個單獨的 token 提取出來處理掉,再來處理這個新的標簽。但是這里, <*/> 并不是一個合法的標簽,當解析器處理時,不得不退回,將其當作新的字符串處理。

            因此真正的 token 序列是這樣的:

              [Start Tag (svg)][Start Tag (script)][Characters /][Characters <1/>alert(0)][End Tag (script)]
            

            重復上述的過濾器過濾流程,在考察過 SCRIPT 開始標簽后,過濾器將會處理隨后的字符串。處理函數(補丁前)如下:

            #!js
            bool XSSAuditor::filterCharacterToken(const FilterTokenRequest& request)
            {
                ASSERT(m_scriptTagNestingLevel);
                ASSERT(m_state != Uninitialized);
                if (m_state == PermittingAdjacentCharacterTokens)
                    return false;
            
                if ((m_state == SuppressingAdjacentCharacterTokens)
                    || (m_scriptTagFoundInRequest && isContainedInRequest(canonicalizedSnippetForJavaScript(request)))) {
                    request.token.eraseCharacters();
                    request.token.appendToCharacter(' '); // Technically, character tokens can't be empty.
                    m_state = SuppressingAdjacentCharacterTokens;
                    return true;
                }
            
                m_state = PermittingAdjacentCharacterTokens;
                return false;
            }
            

            先處理 "/",處理函數為 canonicalizedSnippetForJavaScript ;然后看處理后的結果是否在 URL 中(isContainedInRequest)并且是否 URL 中存在 script 標簽(m_scriptTagFoundInRequest);如果不在,則設置當前狀態為 PermittingAdjacentCharacterTokens ,即認為它是沒有危險的。

            接下來處理 "<1/>alert(0)",但是注意到處理函數中,首先檢查了狀態是否為 PermittingAdjacentCharacterTokens ;這里因為兩個字符串 token 是連續的,因此,如果第一個字符串沒有檢測到在 URL 中,那么第二個字符串根本就不會被過濾!因為當前狀態已經被設置為 PermittingAdjacentCharacterTokens 了!

            而這個 PoC 則正是利用了這一點。對于 "/", 在被 canonicalizedSnippetForJavaScript 處理后,將會變成空字符串!而空字符串是不會被認為與 URL 中的子串重復的。

            為什么會變空呢? canonicalizedSnippetForJavaScript 會對傳入的 Javascript 代碼作一系列處理,其中會調用 isNonCanonicalCharacter 函數檢查字符串,將匹配到的字符刪去。匹配代碼如下:

            #!js
            return (c == '\\' || c == '0' || c == '\0' || c == '/' || c == '?' || c >= 127);
            

            “/” 正在其中。

            兩個字符串結合起來,即/<1/>alert(0)是一個合法的 Javacript ,可以成功執行。

            類似的,我們可以自己構造其他 PoC :

            #!html
            <svg><script>0<1>alert(1)</script>
            

            這里利用了 "0" 也會被處理為空字符串的特性。

            如果構造成

            #!html
            <svg><script>0<a></a>alert(1)</script>
            

            能否繞過呢?

            答案是否定的,當進入其他標簽時,當前狀態將會設置為 FilteringTokens ,無法繞過檢查。

            因此,這個繞過利用了 SVG 內標簽的特殊解析模式,過濾器的連續過濾的機制,以及 isNonCanonicalCharacter 函數對幾個特定字符的匹配后清除。顯然,攻擊者是看著 XSSAuditor 的源碼構造的 PoC。

            :)

            其實,這里 canonicalizedSnippetForJavaScript 是很復雜的,因此,可能存在其他方法使得第一個字符串被認為是允許的(即不在 URL 中)。也正是因為這個,在 Patch 的說明中,最后有一句 "Keep looking in that case.",有可能還會出現類似的繞過。

            0x03 補丁分析


            補丁其實很簡單,如果某個處理后的字符串為空,則既不設置為允許也不設置為禁止。這樣既不會干涉后面的處理,也不會增加誤判。

            具體補丁代碼不再分析,其實就只有上面這一點差別。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线