V0W's Blog

OneThink CMS 缓存漏洞

字数统计: 1,067阅读时长: 4 min
2018/08/02 Share

0x00 前言

最早是2018.7.21 i春秋巅峰极客赛接触到这个漏洞,这也是我第一次认真的搭建环境,分析漏洞。跟着大佬的博客,算是比较清楚的弄懂了这个漏洞。

0x01 漏洞环境

  1. OneThink 1.0
  2. /Temp/Runtime目录可读可写

0x02 漏洞分析

因为ThinkPHP对缓存设计逻辑的漏洞,以及缓存文件名可猜测的原因,导致了这个漏洞

先看一下ThinkPHP的缓存文件的配置:

ThinkPHP中一些系统常量的定义都在ThinkPHP/ThinkPHP.php中定义,缓存路径也在这里:
mark

发现TEMP_PATH 默认值为RunTime/Temp目录。
但是 缓存数据文件存储位置为ThinkPHP/Conf/convention.php
mark

下面一步步分析 OneThink 在登陆的时候缓存文件是如何存储的:
登陆时,请求的网页是http://127.0.0.1/onethink/index.php?s=/Home/User/login.html
搜索login,在onethink\Application\Admin\Controller\PublicController.class.php下找到对应方法
其中的D函数是TP中获取model的方法,这里相当于获得了一个MemberModel类的实例,
mark

登陆成功后,调用Member->login方法,跳转到定义:
onethink\Application\Admin\Model\MemberModel.class.php
其中$uid是从数据库中查询出来的用户名对应的用户id。
mark

登陆时,调用autoLogin()函数,继续跟进,找到get_username()函数
mark

终于找到了对于用户名的缓存操作

在第一次登陆的时候是没有缓存的,if条件应该直接进入else部分,又因为$list是从$uid所在行的第二行拿到的,所以应该是用户名,所以在下面调用S方法缓存数据的时候传入的$list我们是可控的

再进入S函数,查看一下这个函数的具体设计
mark

我们传入的$list就是S函数中的value参数,分析if条件的话可以知道程序直接进入第二个elseif,初始化$cache,关键在最后的set函数,set函数中的$value仍然是我们可控的,进入set函数中,set函数在ThinkPHP/Library/Think/Cache/Driver/File.class.php

发现$filename被file_put_contents直接调用,如果不开启数据压缩的话,$data则是我们控制的$value序列化以后存入的值
mark

下面再知道文件名,就完全可控了,看看文件名是怎么定义的。
mark

'onethink_'.md5(md5($name));

最后就是onethink_md5(md5(name)).php

0x03 漏洞利用

现在我们构造payload:

用户名注册为%0aphpinfo();#这样在存储缓存文件的时候就可以写入webshell,#注释了序列化的剩余部分,前面的%0a的作用则是为了不让$data中<?php\n//".sprintf('%012d',$expire).$check.$data."\n?>//注释攻击代码

在OneThink的注册页面中的注册用户名为:%0aphpinfo();#,但是考虑到有长度限制,可以利用burp抓包改包。
mark

之后用这个用户名登陆,之后访问Temp/Runtime/namemd5.php,即可发现phpinfo得以执行.但是注意一点,就是其$name变量不是usename,而是前面S()函数的参数即sys_active_user_list
mark

访问
http://127.0.0.1/onethink/Runtime/Temp/onethink_d403acece4ebce56a3a4237340fbbe70.php

mark

0x04 漏洞修复

临时解决方案,通过强化缓存的文件名规则,让动态缓存文件难以被反向找到。 修复补丁下载地http://pan.baidu.com/s/1gdAFMSz

补丁修改了最后filename()函数的一句话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  private function filename($name) {
$name = md5(C('DATA_AUTH_KEY').$name); //此处为补丁
// $name = 'onethink_'.md5(md5($name)); //原本代码
if(C('DATA_CACHE_SUBDIR')) {
// 使用子目录
$dir ='';
for($i=0;$i<C('DATA_PATH_LEVEL');$i++) {
$dir .= $name{$i}.'/';
}
if(!is_dir($this->options['temp'].$dir)) {
mkdir($this->options['temp'].$dir,0755,true);
}
$filename = $dir.$this->options['prefix'].$name.'.php';
}else{
$filename = $this->options['prefix'].$name.'.php';
}
return $this->options['temp'].$filename;
}

这样将一个可控的或者说可知的文件名,改成了未知不可控的文件名。

0x05 总结

花了一下午的时间,找漏洞,还是在有博客的帮助下。大牛们挖洞真的也不容易啊。。。

第一次大型CMS的代码审计感觉非常烦躁,好多同名函数,一会就弄混了,希望之后能渐渐游刃有余起来。

0xff 参考链接

OneThink CMS的缓存漏洞的分析
ThinkOX全版本通杀0day

CATALOG
  1. 1. 0x00 前言
  2. 2. 0x01 漏洞环境
  3. 3. 0x02 漏洞分析
  4. 4. 0x03 漏洞利用
  5. 5. 0x04 漏洞修复
  6. 6. 0x05 总结
  7. 7. 0xff 参考链接