目錄
這是什麼?
XSS 系列開篇。WebApp 滲透測試是一個大話題,之前的 SQL 系列之後還會繼續深入。本篇的 XSS 系列也一樣,在後續的工作學習中,不斷記錄。
我不再講 XSS 的基本原理,網上有太多的教程。
最權威的學習資料,列舉在下面:
OWASP,Rapid7,Protswigger(Burp Suite)三大家。
如果對於 XSS 的基本概念不太熟悉,建議先閱讀這三篇文章,再繼續下面的內容。
今日目標
今天,我們一起把 XSS Game 這個有 6 個等級的 XSS 實戰小教程做完。理論結合實踐,是最快的學習方式。
所有的 6 個等級,都要求我們使用 XSS 讓瀏覽器彈出一個 alert
,即可過關。
在開始之前,先推薦兩個個 JSBin HTML JS 線上測試工具 和 JSFiddle。在學習的過程中,如果有代碼需要測試,可以很方便地在兩個工具上運行。
例如你知道了一個輸入框中的用戶輸入會被直接嵌入到 <strong></strong>
標籤中,你想確認在 <strong>
標籤中,<script>
是否能被成功執行。那就可以到 JSBin 做個小測試。
另外,HTML URL Online Encoder Decoder 也是非常有用的工具,可以將需要在地址欄注入的腳本做 url encode。
最後再推薦兩個 XSS Payload Cheet Sheet,OWASP XSS Cheet Sheet,XSS Cheet Sheet From Github
Level 1
Level 1 的頁面是這樣的。
XSS Game 模擬了一個瀏覽器在頁面中央。頁面底部的 toggle
,點擊可以查看頁面的源代碼。
點擊 show
,可以顯示提示信息。
Level 1,展示了最基本的反射型 XSS 攻擊。這裏,有兩種方式可以判斷開發者有沒有對用戶輸入做校驗。
第一種方式,看源碼。
可以看到,沒有任何的用戶輸入校驗。後端代碼從 request
中獲取 query
參數,然後直接拼接到 html <b> tag
中間。
另一種方法,輸入測試。
對於網頁應用來說,最最敏感的字符,就是兩個尖括號,<
和 >
。所以我們可以直接輸入一個 <test
測試以下是否有過濾機制。
有點奇怪的結果,但是我覺得這是瀏覽器的行爲,跟環境沒有關係。我的瀏覽器也是 Chrome,可以做個小測試。
我寫了一個簡單的 html 頁面。
頁面模擬了Level 1 的情況,在 <b>
標籤中間,加上了 <test
作爲用戶輸入。
我們啓動一個 http server。
我在 Windows WSL 下運行的 Ubuntu,它的 IP 是 192.168.1.108。用瀏覽其訪問該測試頁面。
可以看到一樣的情況。
換成其他標籤也一樣。
無論怎麼說,可以肯定的是,後端沒有做用戶輸入的校驗,導致我們可以直接使用 <script>
注入 javascript。
因此,在搜索框中輸入:
<script>alert(1);</script>
或者在 url 中(?query=)輸入:
# html url encode
%3Cscript%3Ealert%281%29%3B%3C%2Fscript%3E
即可。
Level 2
Level 2 是典型的持久型 XSS。
同樣的步驟,我們看一下源碼。
存儲評論的時候,沒有做任何輸入校驗,直接將用戶輸入存儲到數據庫。
顯示評論的時候,同樣沒有做用戶輸入的校驗處理,直接將用戶輸入拼接到 DOM。
根據前一 Level 的經驗,我們也輸入一個 <script>alert(1);</script>
就可以了。但是這裏不行。
innerHTML
innerHTML 可以設置 DOM 樹中的節點的內容。
var ele = document.getElementById('id');
ele.innerHTML = 'this is not an attack';
innerHTML的安全問題
innerHTML 將字符串解析爲 HTML,意思是,如果字符串中有腳本,瀏覽器就有可能會執行。
爲什麼 <script>
在 innerHTML 中無法執行,我們看一下文檔。
根據 Mozilla 官方的文檔,規定 <script>
在 innerHTML 中是無法執行的,僅此而已。
同一篇文檔中,Mozilla 也給出了我們需要的答案,我們仍舊可以使用 事件(Event)
來觸發 javascript 腳本。
Event
Event,也稱 DOM Event,用來通知開發者一些特定的操作或者事件被觸發了。比如,onmouseover
事件會在鼠標移入的時候被觸發,onload
事件會在頁面或者節點被加載的時候被觸發,即將使用的 onerror
在有錯誤發生的時候被觸發。
事件被添加到節點上之後,根據事件性質的不同,會在不同的情況下被觸發。
如 Mozilla 官方文檔所說,在 Level 2 的情況下,我們只需要使用一個事件,就可以觸發 alert 彈窗。
在評論區輸入:
<img src=# onerror="javascript:alert(1);" />
# 或者
<img src=# onerror="alert(1);" />
# 或者,連分號都不用
<img src=# onerror="alert(1)" />
持久型的 XSS 意味着每當你訪問這個頁面,彈窗都會執行。可以嘗試刷新以下頁面,彈窗會再次出現。
Level 3
看源碼找漏洞。
location.hash
圖片來自 Mozilla Location 官方文檔。
這個 hash 大家都見到過,它就像一個麪包屑一樣,指出你正在閱讀的當前頁面上的那一部分內容。
比如大家到我的個人博客,閱讀 從頭開始寫操作系統-啓動扇區與內存的關係及內存尋址的應用 一文,你隨便點一個目錄,就會在 URL 後面看到一個 hash。
hash 是 Location 對象的一個屬性,代表的,就是上圖例子中的 #boosecbyte
部分。
我們結合 Level 3 的源碼看一下。
Level 3 中,chooseTab
方法接收 location.hash.substr(1)
作爲參數,也就是上 #example
中 example
的部分。
之後,這個 example
,被直接拼接到了 html 中,然後 jQuery 將 html 設置給 tabContent 節點來展示。
沒有看到任何用戶輸入的校驗。
我首先嚐試了以下直接使用 <script>
進行注入,但是沒有效果,說明還是要用事件來做。
有了 Level 2 的經驗,我們只需要構造一個一樣的 <img> tag
,在錯誤發生的時候,執行彈窗即可。
在 URL 的 #
之後輸入
whatever.jpg' onerror='alert(1)'/>
即可。
Level 4
看源碼。
後端在獲取 timer
參數的時候,沒有做任何字符校驗,獲取之後,直接傳遞給 timer.html
渲染。
在 timer.html
中,onload
事件觸發後直接將後端獲取的 timer
參數的值傳遞給 startTimer()
方法,作爲參數。
onload
事件,會在圖片加載完成的時候調用。所以,可以在 onload
事件中,構造一個 XSS 注入。
我們需要做的是構造一個輸入,補全 startTimer('
部分,然後注入 XSS 代碼,例如 οnlοad=“startTimer(‘3’); alert('1’);”
黑體加粗的部分就是我們需要構造的輸入。
在輸入框輸入
3'); alert('1
# 或者
3')+alert('1
或者用上面的 HTML URL Encoder Decoder 工具,編碼之後,在地址欄輸入
?timer=3%27%29%3B+alert%28%271
# 或者
?timer=3%27%29%2Balert%28%271
即可。
Level 5
我們點擊 sign up 跳轉到註冊頁面。
看源碼,註冊頁面選的時候,直接使用後端獲取的 next
參數,沒有任何字符校驗和過濾。
我們輸入了 email,點擊 Next >>
的時候,實際上是點了一個 <a>
鏈接,href
屬性是上圖中獲取的 next
參數的值。
因此,直接在瀏覽器地址欄輸入
javascript:alert(1);
點擊 Next >>
即可。
Level 6
最後 Level 6,還是對 location.hash
進行利用。
跟 Level 3 類似,在 getGadgetName
方法中,獲取 #
之後的部分,如果沒有用戶輸入,默認爲 /static/gadget.js
。
接着,獲取到的內容傳遞給 includeGadget()
方法最爲參數。
includeGadget()
方法中,首先創建了一個 script
DOM 節點,接着,上一步獲取的 #
之後的的部分,作爲 script
節點的 src
屬性。
最後,這個 script
節點被添加爲 head
的子節點。
以默認情況爲例,創建的 script
節點爲
<script src="/static/gadget.js" />
因此,我們要傳遞一個惡意的 js 文件的路徑,輸入到 URL 上 #
的後面,替換掉 /static/gadget.js
即可。
自己沒有服務器的同學,可以使用 google 的 callback jsapi。
另外,還有一個點需要注意,如果直接輸入地址欄中複製出來的帶有 https
或者 http
的 URL,是會報錯的。
因爲代碼中(見前文第 21行 代碼)對 http
和 https
進行了過濾,所以,有兩種解決方案。
- 不要協議部分
- 將協議部分全部大寫或者隨意大寫一個字符
Protocol-relative URLs
在 html 中使用 URL 可以不需要協議名的部分。例如,<a href="https://www.google.com" />
,可以寫成 <a href="//www.google.com" />
。根據 wiki 所述,這樣的使用方式叫 Protocol-relative URLs
,對目標地址的訪問會使用當前頁面的協議。
因此,在地址欄輸入
//www.google.com/jsapi?callback=alert
# 或者
httpS://www.google.com/jsapi?callback=alert
即可。
Data URLs
還有另外一種方式,使用 Data URLs
。
Data URLs 是一種協議,可以讓開發者在文檔中嵌入文件。
Data URLs 由 4 部分組成:
data:
,前綴mediatype
,數據類型base64 token
,非必要,用於嵌入 base64 編碼的二進制data
,數據本身
我先在我本機上做了測試,XSS 腳本不只是在 head
節點中可用,添加到任意節點中均可執行。
所以,在地址欄輸入
data:text/plain;charset=utf-8,alert%281%29
同樣可以彈窗。
總結
- 最基本的 XSS 漏洞,可直接使用
<script>code</script>
<script>
無法執行的時候,可以選擇使用事件來觸發 XSS 腳本location.hash
等頁面地址相關的操作如果使用不當,也會造成 XSS 漏洞Protocol-relative URLs
允許我們省略協議名,使用當前頁面的協議訪問目標地址Data URLs
允許我們在 DOM 中嵌入並執行 XSS 腳本- 所有的 XSS 漏洞,都是由於沒有對用戶輸入做校驗和篩選造成的;所以,XSS 防禦的第一步,就是對所有的用戶輸入進行校驗,過濾
- https://owasp.org/www-community/attacks/xss/#:~:text=Overview,to%20a%20different%20end%20user.
- https://www.rapid7.com/fundamentals/cross-site-scripting/
- https://portswigger.net/web-security/cross-site-scripting
- https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
- https://www.tutorialspoint.com/javascript/javascript_events.htm#:~:text=JavaScript’s%20interaction%20with%20HTML%20is,%2C%20resizing%20a%20window%2C%20etc.
- https://jsbin.com/?html,output
- https://jsfiddle.net/
- https://www.url-encode-decode.com/
- https://www.w3schools.com/js/js_events.asp
- https://developer.mozilla.org/en-US/docs/Web/Events
- https://www.w3schools.com/tags/ev_onclick.asp
- https://stackoverflow.com/questions/7761149/dynamically-added-script-will-not-execute
- https://developer.mozilla.org/en-US/docs/Web/API/Location
- https://www.w3schools.com/jsref/event_onload.asp
- https://github.com/rorymurphyza/GoogleXSSGame
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
- https://github.com/payloadbox/xss-payload-list
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet