從零開始學web安全(3)

  • 2019 年 12 月 4 日
  • 筆記

本文作者:IMWeb 劉志龍 原文出處:IMWeb社區 未經同意,禁止轉載

上篇文章寫到了一個親自測試的demo,其中有一個地方講到了「html字元實體」,這是上次xss成功需要知道的非常重要的一個小知識。不僅html字元實體,要繼續學習xss需要了解很重要的一個知識就是編碼。不然很多時候遇到各種對特殊字元的過濾可能就無能為力了。這篇文章就是要學習一下xss各種編碼的知識,內容可能比較枯燥~~

最近sng要求大家做安全考試,跟xss相關有兩個個非常經典的題目:

題目1答案是b,題目2答案是c和d。

看完這兩道題如果大家都很輕鬆的答對了,而且知其所以然,那麼這篇文章大家快速瀏覽下或者直接關掉啦~~

為了解答上面的問題,我們先來學習下面編碼相關的知識吧~~

常用編碼

常用編碼類別的介紹借用一下別人的總結,大概有以下三種,當然還有css編碼等,因為css expression現在也很少人用了在這裡就不介紹了。

  • html實體編碼,十進位、十六進位ASCII碼或unicode字元編碼,樣式為「&#數值;」,例如「<」可以編碼為&#060;&#x3c;
  • js編碼,有4類:

(a)三個八進位數字,如果不夠個數,前面補0,例如「e」編碼為「145」

(b) 兩個十六進位數字,如果不夠個數,前面補0,例如「e」編碼為「x65」

(c)四個十六進位數字,如果不夠個數,前面補0,例如「e」編碼為「u0065」

(d)對於一些控制字元,使用特殊的C類型的轉義風格(例如n和r)

  • url編碼,一個百分號和該字元的ASCII編碼所對應的2位十六進位數字,例如「/」的URL編碼為%2F。我們平時調用的encodeURIComponent等出來的就是url編碼。

看完上面的介紹之後估計本來懂的人知道是幹嘛的,本來不了解的人估計也覺得並沒有什麼用。

還是來看看簡單的demo吧~~

html實體編碼

記得在上篇文章中將到了這個應用到小知識,即是

在html標籤屬性的值里字元實體是會被轉換成相對的字元的。這意味著下面這兩個是等價的:

<button onclick="javascipt:alert(1);">xss</button>  <button onclick="j&#x61;vascipt:alert(1);">xss</button>

這個東西有什麼用呢?比如某些特殊字元單引號雙引號之類的被過濾了但是&#並沒有被過濾,就可以用字元實體替代進行xss啦~~

js編碼

剛剛說到了js編碼有好幾種,其實不用太care,只要知道有js編碼這東西就好了,每一種使用起來效果基本沒什麼不同。

依然看個非常簡單的例子

    var body = document.getElementsByTagName('body')[0];        body.inner/*防過濾*/HTML = '<a href="location.href='http://www.baidu.com'">test</a>';        // 等同於上面那句      body.inner/*防過濾*/HTML = '<a href="location.href='httpu003a//www.baidu.com'">test</a>';

這裡用的是js編碼中的第三種,js的unicode編碼,其他用法一樣。我們把:編碼成了u003a,當進入到js的可執行環境的時候就會被把編碼後的u003a重新解析成:

在xss的應用中也是用來繞過過濾。

url編碼

這個就比較簡單了,我們平時用encodeURIComponent也用了好多次,例如「/」的URL編碼為%2F,這個不像前面兩個那麼常用,但在一些特殊場景也會用到,具體就看看複合編碼裡面的例子吧。

複合編碼

這裡要說的複合編碼是說前面幾種結合起來,這可能是實際場景中用到的最多的了。

依舊來看看一個簡單的demo。

var body = document.getElementsByTagName('body')[0];                    // test1                  // url編碼                  body.inner/*防過濾*/HTML = '<a href="location.href='http%3A%2F%2Fwww.baidu.com'">test1</a>';                    // test2                  // url編碼 -> js unicode編碼                  body.inner/*防過濾*/HTML = '<a href="location.href='httpu00253Au00252Fu00252Fwww.baidu.com'">test2</a>';                    // test3                  // url編碼 -> html字元實體編碼                  body.inner/*防過濾*/HTML = '<a href="location.href='http&#37;3A&#37;2F&#37;2Fwww.baidu.com'">test3</a>';                    // test4                  // url編碼 -> html字元實體編碼 -> js unicode編碼                  body.inner/*防過濾*/HTML = '<a href="location.href='httpu0026#37;3A&#37;2F&#37;2Fwww.baidu.com'">test4</a>';                    // test5                  // url編碼 -> js unicode編碼 -> html字元實體編碼                  body.inner/*防過濾*/HTML = '<a href="location.href='http&#92;u00253Au00252Fu00252Fwww.baidu.com'">test5</a>';                    // test6                  // url編碼 -> js unicode編碼                  body.inner/*防過濾*/HTML = '<a href="location.href='http%3A%2F%2Fwww.baiduu002ecom'">test6</a>';                    // test7                  // url編碼 -> js unicode編碼 -> html字元實體編碼                  body.inner/*防過濾*/HTML = '<a href="location.href='http%3A%2F%2Fwww.baidu&#92;u002ecom'">test7</a>';

test1隻進行了url編碼,在url裡面存在url編碼可以正常跳轉這個是毫無疑問的。

test2我們把%編碼成了u0025,發現一樣可以順利跳轉,為什麼呢?這裡是因為這個a標籤被插入到body裡面的時候已經經過了js可執行環境,即我們在賦值inner/*防過濾*/HTML的這條語句。在插入到body裡面的時候我們在dom樹里看到的其實和test1沒有什麼區別。

test3我們把%編碼成了&#37,發現還是可以順利跳轉,這又是為啥?原因也很簡單,這個a標籤被插入到body之後,就變成了屬性里有html字元實體的場景。我們在講html實體編碼的時候已經說過了,屬性裡面存在html實體編碼在dom樹的渲染中是會被解析出來的。打開chrome的調試器我們看到的和test1並沒有區別。

test4我們在test3的基礎上把第一個&通過js unicode編碼編程u0026,發現居然還可以跳轉!聰明的讀者可能一下子就反應過來了,因為在賦值inner/*防過濾*/HTML的這條語句的時候先經過了js可執行環境,然後到dom中,在js可執行環境里u0026被解碼出來了,在渲染a標籤的時候解碼出來的&和後面的#37拼接起來,被html實體解碼成%,url跳轉的時候被url解碼成特定的字元。也就是說整個過程其實經過了 js unicode解碼 -> html字元實體解碼 -> url解碼。

好吧,你告訴我是先經過js環境,再到html,反過來編碼肯定掛了吧。看看我們的test5,我們在test2的基礎上把第一個編碼成&#92;,果然掛了!!沒有正確跳轉。然而仔細一看,之所以沒有正確跳轉是因為這個url被當成相對路徑了,跳轉出來的路徑是http://192.168.1.100:8848/http%3A//www.baidu.com, 前面那一串ip埠忽略,後面的地址是對的!只不過被當成了相對路徑而已。看來test5這種編碼順序也是可以的?

為了驗證上面這個疑問,我測試了一下test1 -> test6 -> test7這個編碼順序,不出意外,正常跳轉了。

這時候有些讀者可能有點凌亂。先html編碼和先js編碼看來也沒啥區別,瞎逼編就好了。

重新理清下思路,其實我舉的這個例子非常特殊,不僅用到了三種編碼,編碼處理的環境也在不斷變化。整個解碼過程其實分4步,是這樣子的:

inner/*防過濾*/HTML的js可執行環境時候的js解碼 ->

dom渲染時的html字元實體解碼 ->

location.href的js可執行環境時候的js解碼 ->

url跳轉時候的url解碼

看完這個解碼順序大家應該都了解為什麼先html編碼還是先js編碼都可以了吧,並不是瞎逼編的= =

測試題目

回頭來看這個題目,如果大家對上面都理解了,估計對這道題目也沒有什麼疑問。

b為什麼是錯誤的呢,我們來hack它吧~~

看demo:

<div id="test"></div>  <script type="text/javascript">        var t = document.getElementById('test');      var hash = location.hash.substr(1);        t.inner/*防過濾*/HTML = '<a href="#" id="test" onclick="func(''+ hash +'')">test</a>';        function func () {}  </script>

我們模擬了一個這樣的例子,從hash裡面作為輸入點,然後輸出到頁面上。首先上面這個沒有任何過濾。

我們寫了個這樣的hash - "onmouseover="alert(1)"輕鬆破解

我們簡單過濾一下,對單雙引號html編碼

<div id="test"></div>  <script type="text/javascript">        var t = document.getElementById('test');      var hash = location.hash.substr(1);        hash = hash.replace(/"/g, '&#34;')              .replace(/'/g, ''');        t.inner/*防過濾*/HTML = '<a href="#" id="test" onclick="func(''+ hash +'')">test</a>';        function func () {}  </script>

剛才的破解方法失效了,那答案b不是對的嗎?

那怎麼破解?我當時被這個先入為主的思維困擾了好久,其實這邊並不一定要老想著去閉合html裡面的標籤,可以閉合js啊!

我們提交這樣一個hash - '+alert(1)+'

成功彈窗!結合html字元實體編碼 + onclick裡面的js可執行環境,是可以被xss的~

其他選項和題目就留給大家自己思考一下,就不一一解釋了,應該並沒有太大問題。

好吧,這篇就先到這裡了~~