從零開始學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字元編碼,樣式為「&#數值;」,例如「<」可以編碼為
<
和<
。 - 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="javascipt: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%3A%2F%2Fwww.baidu.com'">test3</a>'; // test4 // url編碼 -> html字元實體編碼 -> js unicode編碼 body.inner/*防過濾*/HTML = '<a href="location.href='httpu0026#37;3A%2F%2Fwww.baidu.com'">test4</a>'; // test5 // url編碼 -> js unicode編碼 -> html字元實體編碼 body.inner/*防過濾*/HTML = '<a href="location.href='http\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\u002ecom'">test7</a>';
test1隻進行了url編碼,在url裡面存在url編碼可以正常跳轉這個是毫無疑問的。
test2我們把%
編碼成了u0025
,發現一樣可以順利跳轉,為什麼呢?這裡是因為這個a標籤被插入到body裡面的時候已經經過了js可執行環境,即我們在賦值inner/*防過濾*/HTML
的這條語句。在插入到body裡面的時候我們在dom樹里看到的其實和test1沒有什麼區別。
test3我們把%
編碼成了%
,發現還是可以順利跳轉,這又是為啥?原因也很簡單,這個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的基礎上把第一個編碼成
\
,果然掛了!!沒有正確跳轉。然而仔細一看,之所以沒有正確跳轉是因為這個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, '"') .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的~
其他選項和題目就留給大家自己思考一下,就不一一解釋了,應該並沒有太大問題。
好吧,這篇就先到這裡了~~