ThinkPHP6.0.13反序列化分析

admin 2022年10月9日00:00:58评论116 views字数 4486阅读14分57秒阅读模式

===================================


免责声明

请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。工具来自网络,安全性自测,如有侵权请联系删除。


0x00 ThinkPHP安装

composer下载链接

https://getcomposer.org/doc/00-intro.md

安装ThinkPHP6.0.13,需要本地PHP>7.2

composer create-project topthink/think tp

ThinkPHP6.0.13反序列化分析

使用PHPStudy搭建安装完成

ThinkPHP6.0.13反序列化分析

0x01 反序列化漏洞分析

漏洞演示

ThinkPHP6.0.13反序列化分析

断点分析

首先反序列化第一步走的就是__destruct()魔术方法,通过全局搜索这个魔术方法,找到这里最有可能是反序列化的入口点。这个AbstractCache类是Psr6Cache的父类

ThinkPHP6.0.13反序列化分析

public function __destruct()
{
if ($this->lazySave) {
$this->save();
}
}

接着$this->save就会跳转到Psr6Cache类的save方法

public function save()
{
$item = $this->pool->getItem($this->key);
$item->set($this->getForStorage());
$item->expiresAfter($this->expire);
$this->pool->save($item);
}

这里在初始化的时候传入与了一个$pool变量

$b = new thinklogChannel();

$a = new LeagueFlysystemCachedStoragePsr6Cache($b);

第一步实例化了Psr6Cache对象,在初始化的时候传入第一个参数,$this->pool

public function __construct(CacheItemPoolInterface $pool, $key = 'flysystem', $expire = null)
{
$this->pool = $pool;
$this->key = $key;
$this->expire = $expire;
}

在Psr6Cache类中,$this->pool->getItem调用时,出发了魔术方法_call,因为Channel对象中没有getItem方法。此时也会执行构造方法

public function save()
{
$item = $this->pool->getItem($this->key);
$item->set($this->getForStorage());
$item->expiresAfter($this->expire);
$this->pool->save($item);
}

在Channel类中的_call方法,__call方法中又调用了$this->log方法

ThinkPHP6.0.13反序列化分析

public function log($level, $message, array $context = [])
{
$this->record($message, $level, $context);
}

public function __call($method, $parameters)
{
$this->log($method, ...$parameters);
}

接着跟踪$this->record方法,这个方法是用来记录日志信息的。这里最终会调用到$this->save方法

# 记录日志信息
public function record($msg, string $type = 'info', array $context = [], bool $lazy = true)
{
if ($this->close || (!empty($this->allow) && !in_array($type, $this->allow))) {
return $this;
}

    if (is_string($msg) && !empty($context)) {
$replace = [];
foreach ($context as $key => $val) {
$replace['{' . $key . '}'] = $val;
}

$msg = strtr($msg, $replace);
}

if (!empty($msg) || 0 === $msg) {
$this->log[$type][] = $msg;
if ($this->event) {
$this->event->trigger(new LogRecord($type, $msg));
}
}

if (!$this->lazy || !$lazy) {
$this->save();
}

return $this;

来到save方法,save方法中又调用到了$this->logger->save()

这里在初始化的时候定义了$this->logger = new thinklogdriverSocket() ,所以在调用的时候会去往Socket类

/**
* 保存日志
* @return bool
*/
public function save(): bool
{
$log = $this->log;
if ($this->event) {
$event = new LogWrite($this->name, $log);
$this->event->trigger($event);
$log = $event->log;
}

if ($this->logger->save($log)) {
$this->clear();
return true;
}

return false;
}

来到thinklogdriverSocket()下的save方法

此时$this->config['format_head'] = [new thinkviewdriverPhp,'display']

这里的$this->app->invoke是调用反射执行callable 支持参数绑定,进行动态反射调用

ThinkPHP6.0.13反序列化分析

/**
* 调试输出接口
* @access public
* @param array $log 日志信息
* @return bool
*/
public function save(array $log = []): bool
{
if (!$this->check()) {
return false;
}

$trace = [];

if ($this->config['debug']) {
if ($this->app->exists('request')) {
$currentUri = $this->app->request->url(true);
} else {
$currentUri = 'cmd:' . implode(' ', $_SERVER['argv'] ?? []);
}

if (!empty($this->config['format_head'])) {
try {
$currentUri = $this->app->invoke($this->config['format_head'], [$currentUri]);
} catch (NotFoundExceptionInterface $notFoundException) {
// Ignore exception
}
}
......

跟踪$this->app->invoke

public function invoke($callable, array $vars = [], bool $accessible = false)
{
if ($callable instanceof Closure) {
return $this->invokeFunction($callable, $vars);
} elseif (is_string($callable) && false === strpos($callable, '::')) {
return $this->invokeFunction($callable, $vars);
} else {
return $this->invokeMethod($callable, $vars, $accessible);
}
}

这里的判断循环最终会进入到return $reflect->invokeArgs(is_object($class) ? $class : null, $args);

通过这里的反射方法来到Php类下的display方法,$calss是一个对象类,$args就是对象类下的方法

public function invokeMethod($method, array $vars = [], bool $accessible = false)
{
//$method = think\view\driver\Php
if (is_array($method)) {
[$class, $method] = $method;

$class = is_object($class) ? $class : $this->invokeClass($class);
} else {
// 静态方法
[$class, $method] = explode('::', $method);
}

try {
//ReflectionMethod回调方法
$reflect = new ReflectionMethod($class, $method);
} catch (ReflectionException $e) {
$class = is_object($class) ? get_class($class) : $class;
throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e);
}

$args = $this->bindParams($reflect, $vars);

if ($accessible) {
$reflect->setAccessible($accessible);
}

return $reflect->invokeArgs(is_object($class) ? $class : null, $args);
}

官方文档

ThinkPHP6.0.13反序列化分析

此时调用方法的参数

输出一个回调方法,$calss是类名,$method是方法

$reflect = newReflectionMethod($class, $method);

通过$this->invokeArgs方法将参数传递给类下的方法

$reflect->invokeArgs(is_object($class) ? $class : null, $args);

通过图可以看出值传递的过程

ThinkPHP6.0.13反序列化分析

来到display方法中,$content就是传递的值,然后拼接到了eval去执行命令

public function display(string $content, array $data = []): void
{
$this->content = $content;

extract($data, EXTR_OVERWRITE);
eval('?>' . $this->content);
}

ThinkPHP6.0.13反序列化分析


欢迎关注公众号"网络安全者",本文来源于奇安信攻防社区。

ThinkPHP6.0.13反序列化分析


原文始发于微信公众号(网络安全者):ThinkPHP6.0.13反序列化分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年10月9日00:00:58
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ThinkPHP6.0.13反序列化分析https://cn-sec.com/archives/1338515.html

发表评论

匿名网友 填写信息