OneThink1.0文件缓存漏洞分析及题目复现

0x01 前言

snipaste20180722_172810.png

最近的极客巅峰CTF中碰到了这个漏洞的实际利用:

snipaste20180722_172854.png

一开始陷入了僵局,还以为是ueditor的文件上传,绕了半天,结果凉凉:

snipaste20180722_173002.png

首先访问题目页面,发现是一个OneThink1.0的CMS:

snipaste20180722_173042.png

上网站扫描器进行目录扫描和源码泄露扫描,发现存在源码泄露的压缩包www.zip:

snipaste20180722_173136.png

 

0x02 分析

源码下载之后,拖入Seay源码审计工具中分析上述的缓存漏洞是否存在:
首先查看源码中定义的缓存文件路径配置信息,在ThinkPHP/ThinkPHP.php中对TEMP_PATH定义为Runtime/Temp

snipaste20180722_173228.png

ThinkPHP/Conf/convenion.php中同时定义了DATA_CACHE_PATH也为Runtime/Temp:

snipaste20180722_173302.png

Think/Cache/Driver/File.class.php中构造函数定义了File缓存类的options['temp']DATA_CACHE_PATH,因此File缓存类的存放路径在Runtime/Temp/

snipaste20180722_173349.png

在File缓存类中定义了设置缓存的set函数,在该函数中,一开始定义了$filenamefilename($name)的返回结果,并且通过file_put_contents函数将缓存内容写入$filename路径的文件中,如下:

snipaste20180722_173423.png

这个类中的filename函数定义如下:

snipaste20180722_173450.png

其中的FIle类的options['prefix']为全局设置的空值’’:

snipaste20180722_173517.png

另外,由于DATA_CACHE_SUBDIR配置中设置为false,因此filename中会进入else分支,最后$file为空+$name+.php

snipaste20180722_173551.png

其中$nameonethink_+md5(md5($name参数))
所以filename函数最后返回内容是:

Runtime/Temp/onethink_+md5(md5($name参数))

而set函数中调用时,传入的$name$value参数也来自set函数被调用时的参数,而传入的$value经过serialize序列化函数进行处理后赋值$data,并最终作为写入内容
另外注意到:在写入前$data做了字符拼接,并且前面的字符<?phpn//格外引人注目,这里要注意//注释符号,后面会解释这一点。

snipaste20180722_173655.png

通过调用查询,在ThinkPHP/Mode/Api/functions.php中定义的缓存函数S中使用了File缓存类的set函数,其中$name$value又是来自调用上层的传参$name$value

snipaste20180722_173736.png

继续回溯查询S被调用的情况,在Appalication/Common/Api/UserApi.class.php中,发现get_username函数逻辑中调用了S函数,并且传参"sys_active_user_list"$list

snipaste20180722_173818.png

$list

的内容是通过从数据库查询uid返回的用户信息中,提取第二项拿到的,既

nickname

snipaste20180722_173844.png

所以此处调用S函数,会写入临时文件:

onethink_md5(md5("sys_active_user_list"))

既内容为

onethine_d403acece4ebce56a3a4237340fbbe70

内容是用户的nickname。

仍然继续回溯get_username()函数的调用情况,定位到Application/Home/Model/MemberModel.class.php模型文件中的autoLogin函数和login函数:

snipaste20180722_173946.png

snipaste20180722_174002.png

分析上面两个函数,login函数将传入的uid进行判断,如果是已注册的用户,则前面的判断分支全部跳过,直接调用了autoLogin函数,并且将数据库查询结果find($uid)的用户信息返回结果作为$user进行传参。
在autoLogin函数中将$user中的uid一项继续传入上面介绍过的get_username()函数。

这样,这个逻辑的代码层面已经走过一遍了。在用户登录后的逻辑执行中,将用户的用户名作为缓存项写入了Runtime/Temp中的临时文件中,并且对用户名没有做任何的过滤和转义。如果缓存文件存在访问权限,可以导致代码执行以及GetShell。

 

0x03 利用

首先利用该漏洞尝试执行phpinfo。
注册用户%0aphpinfo();#
其中%0a是让用户名在缓存文件中以新的一行开始(前面分析代码时提到$data会和前面的<?phpn//拼接,这里是防止被//注释),而#注释符则让之后序列化产生的字符失效,防止影响函数执行。

snipaste20180722_174128.png

提交注册,通过Burpsuit进行拦截,这里将%0a进行url编码解码,成为换行符

snipaste20180722_174156.png

snipaste20180722_174212.png

将数据包放行后,用户注册成功,进入登录界面进行登录:

snipaste20180722_174230.png

同样进行抓包拦截,修改%0a后放行:

snipaste20180722_174250.png

snipaste20180722_174300.png

payload用户登录成功,接下来访问缓存页面
发现成功执行phpinfo()!

snipaste20180722_174336.png

在本地搭建同样环境后测试,发现缓存文件中内容如下:

snipaste20180722_174355.png

同样的方法向缓存文件中写入代码执行:

%0a$a=$_GET[a];#
%0asystem($a);#

snipaste20180722_174423.png

snipaste20180722_174450.png

snipaste20180722_174455.png

最后,感谢阅读(#^.^#)

审核人:yiwang   编辑:边边

(完)