记一次CMS代码审计
CVE-2023-1773
产品:信呼oa
影响版本:<=2.32
开源地址:https://github.com/rainrocka/xinhu/commits/master/
该博客为本人第一篇代码审计相关文章,本人才疏学浅,博客乃用于记录个人所学,因此许多地方会较为赘述,细节,这些都是个人的收获,如觉繁琐请见谅
根据官网说明先搭建好框架环境:
在分析漏洞点所在之前,我们需要对框架的功能有一个了解,什么参数是起到什么作用,哪些变量我们可以控制哪些不行,着重观察GET或者POST值处
index.php
主程序入口在index.php,先对其进行审计
代码不多,包含了个config.php是存放框架的各种配置。留意$d
,$m
,$a
三个变量。有个函数$rock->get(),get()方法是什么?
跟进$rock,$rock = new rockClass();
,那就看看get()在rockClass()类中是怎样定义的:
代码也不多
1 |
|
isempt()方法,即若传入字符为空(各种意义的空)并且字符不为数字则返回true:
1 |
|
jmuncode()代码暂且不赘述。总结get()方法的作用就是获取GET传参中的值,并做了一定的xss过滤等处理,其中对单引号进行了转义,去除空格
View.php
到这了,那么看看谁用了$d
,$m
,$a
这仨变量,Find Usages,发现在View.php中调用了这三个变量
红色框为这三个变量赋初值(若未传入则为默认值)
重点在黄色框,如果$m
中存在’|’字符,则将m变量以’|’为分割划分为两部分$m
和$_m
下面引入了$rock中自定义的一个格式化字符串函数strformat():
1 |
|
例如这个:include_once($rock->strformat('?0/?1/?1Action.php',ROOT_PATH, $p));
最终格式化后的字符串为:"ROOT_PATH/".$p."/".$p."Action.php"
,看起来试图包含住一个以Action.php结尾的文件,正巧刚刚翻目录的时候看到了一堆一堆的Action结尾的文件。那么ROOT_PATH
,$p
,这俩又是什么东东呢?
在config.php中对ROOT_PATH有定义:
1 |
|
$p变量向上跟进,为PROJECT,PROJECT再向上跟进,定义了它为’webmain’目录,此时p如果没有收到GET传参的p就会默认为’webmain’
1 |
|
可以留意$ajaxbool变量,下面会用的$ajaxbool = $rock->get('ajaxbool', $ajaxbool);
,总之不要放过任何一个调用get()方法的变量,都是可控的
控制文件被包含
这里有个一路径中的变量$actpath
,被两个下文被包含住的变量$actfile
以及$actfile1
给拼接,弄明白是如何构造的:
用ROOT_PATH,$p,$d,$_m分别填充前面的一个格式化路径
$_m我们可以控制GET传入m参数时’|’字符两边的字符串来控制,$p和ROOT_PATH,$d在上面存在一个简单的处理,如果不是以’/‘结尾就给他加上’/‘
总结起来,假设我们的根目录是/var/www/html/,传入p=webmain&d=task&m=file|api
那么$actpath就是”/var/www/html/webmain/task/api”
紧接着下面的$actfile就是”/var/www/html/webmain/task/api/fileAction.php”,
也就是该文件被包含:
控制类和方法
其实actfile1我们并不能做到真正完全的可控,因为其最后的拼接是$_m.$_m
,就意味着最后包含的php文件的前缀名必须与上级目录相同,具有一定的局限性,这里我们侧重观察actfile被包含后的操作(实际上actfile1被包含后也确实并没有进行更多的操作了),接下来的分析围绕着actfile,$classname变量由$a和”ClassAction”拼接起来,$a可控。,$actname变量由$a和”Action”拼接起来,$a可控。如果ajaxbool为true那么$actname由$a和”Ajax”拼接,$ajaxbool可控(前文提到控制GET传参)。然后new一个名为$classname的值的类,判断该对象中是否存在名为$actname的方法,如果存在,就执行并把结果echo出来
beforeAction()和afterAction()是父类Action类中的抽象方法
如果ajaxbool为false(默认)或者html,就进行模板渲染
至此,我们已经可以做到控制包含工作目录下的Action文件,并且控制执行其中的方法,接下来就是寻找一个Action文件中有可利用的类方法了
寻找可利用漏洞
/webmain/system/cog/conAction.php下:
cogClassAction::savecongAjax()方法中有一处文件写入,只要可以控制$adminnme
就可以控制任意文件写入,在对oa系统本身功能的浏览后发现并不能直接在oa内部修改管理员的名字,思路就是寻找一个sql注入的点,
观察到其实有许多的action都存在sql注入的点,但是get()和post()方法获取参数时引入了黑名单检查直接将许多危险字符(串)过滤了。
注意到/webmain/task/api/reimplatAction.php下的indexAction()方法也存在sql注入点,但是该方法有点特殊:
$body直接调用getpostdata()方法从请求体中获取数据,然后经过一次解密后得到$bodystr,对$bodystr再json解码后,获取其中键值对进行sql操作,思考:我们怎么样操作使得加密后的字符串可控呢?
答:源代码就在自己手上直接调试就好了啊kora!把自己想构造的json发送出去,打印出加密后的字符串不就行了吗
追踪getpostdata()方法,可以看到是一点过滤都没有的。那我们就可以通过直接从请求体发送加密后的字符串从而避免经过get()和post()参数的过滤了!经过strunlook()方法解密后直接拼接到sql语句中
1 |
|
跟踪下解密方法:
1 |
|
找到了strlook()和strunlook()方法
在本地调试时将加密后的字符串打印出来:
经测试没问题,那么我们就可以根据自己的需求,构造恶意json了
回到reimplatAction.php,分析可利用点:
json有这几个键值对:
1 |
|
根据msgtype的构造值不同,可以进行不同的操作,至此,在不需要sql注入漏洞的情况下已经可以修改管理员密码了
发送加密后字符串:
使用新密码666直接登陆后台:
当然我们不止步于此,能sql注入的地方,为什么要放过他们呢?
这里出现了多个m()方法,是什么?跟进去看下
分析一波可以知道是把/webmain/model/下的php文件包含起来了
1 |
|
追踪到底层其实是调用/include/class/mysql.php里的数据库操作函数:
getmou()获取table(此处table由m的构造函数传参决定,传入admin就对xinhu_admin表进行操作)中$where处的$fields的值
1 |
|
update()更新table(同getmou()中指定方式相同)表中$where处$array的值,没有则新增
1 |
|
至此我们回到上面的cogClassAction.php,我们可以通过sql注入更新管理员的名字,思考如何构造?
要执行我们自己插入的rce代码,首先要脱离注释符,那就需要一个”\n”,然后插入完想执行的的php代码后,再用注释符注释掉后面多余的代码。
eg:
1 |
|
然后写入webmain/webmainConfig.php:
此处会被修改
editmobile处比较好操作,editpass也可注入,构造恶意json
传入加密字符串:
admin的名字已经发生变化了:
这时候需要重新登陆一下让session重置,$adminname才会重置:
这时候包含一下cogAction.php:
配置文件变成这样了:
后续测试中发现此处利用点似乎并无法导致getshell,当插入代码如下时:
重新登陆会提示,还是有比较大局限性的:
后记:\neval($_POST[1]);//
就不会太长了。还是可以getshell的,有了eval之后可执行的代码就大大拓宽了
蚁剑连接: