wuzhicms代码审计
一、环境搭建
这里使用phpstudy2018搭建,官网:https://www.wuzhicms.com/。这个cms好久没更新了,官网地址的cms码源(开源版)也下载,在网上找的源码。
这个cms目录结构比较奇怪,网站主页和主要展示文件都在WWW目录中,而一些框架、配置文件等却在www同目录在的文件夹中。安装是访问www下的index目录即可开始安装。
二、路由分析
因为这个cms的目录结果比较其怪,我们先来分析一下大致的路由。
整个cms的路由从www目录下index.php开始,首先包含/configs/web_config.php加载网站的一些基础配置,主要功能是通过包含核心框架文件coreframe下core.php实现。通过load_class函数加载application等类。
core.php文件中load_class
和load_function
两个函数用来加载传入的类或者函数。
整个函数主要的函数或者类都保存在coreframe\app\core\libs目录中。
这里将GET,POST 参数全部转给 GLOBALS ,然后注销 get/post
还对参数值进行了处理,如果开启了GPC魔术引号会删除反斜杠
index.php通过获取到的前端参数对相应的模块、类、函数等进行调用来完成cms的功能。
index.php最后通过load_class() 类实例化 WUZHI_application 类。
调用WUZHI_application 类构造方法
最后调用run方法获取路由信息
三、代码审计
1、前台SQL注入
在mysql.class.php中找到一处多个变量未使用引号包裹的SQL语句,只要是调用get_one
这个函数的地方都存在SQL注入分险
搜索这个函数,需要参数可控才有可能存在SQL注入。
在/api/sms_check.php中找到一处:
$code
变量先是通过$GLOBALS
来获取参数param的值,$GLOBALS
是可以获取post get的值而且这个文件没有定义param变量。这里可以通过GET方法提交。这里没有看到明显的过滤行为,直接注入测试。
http://192.168.83.131/wuzhicms/www/api/sms_check.php?param=1%27%20or%20extractvalue(1,concat(0x7e,(select%20user())))%20--+
要注意一下,这里在get_one函数中并没有引号,但是在/api/sms_check.php中对$code
进行了单引号包裹
,但这里没有对输入的单引号做转义因此可以闭合引号进行注入。
通过全局搜索get_one
函数发现,绝大多数都是以数组形式的传参。大多数get_one
函数中在执行sql之前先会先加载加载’db’类或者直接使用这个类中的get_one
函数,这里调用array2sql
函数对$where
变量进行过滤。
如果$where
变量为数组array2sql
函数会过滤掉空格、单双引号、括号等特殊符号
,不是数组则只过滤掉空格和url编码的单引号。
2、后台SQL注入1
在mysql.class.php发现一个与get_one
类似的函数get_list
也是一样的问题,多个参数没有做引号保护,同样存在SQL注入的风险。
通过全局搜索,找到调用这个函数的地方(很多模块会对get_list函数进行重写)
这里$keywords
和fileldtype
两个参数直接拼接到$where
变量中然后传入到db类中的get_list函数中。
这里对使用上面提到的array2sql函数对参数值进行过滤。但是这里没有引号保护,所以不需要单引号等特殊符号进行闭合。
这里要注意查询结果数,是0,20的话会无法报错注入(会报其他错误)。因此需要提交fieldtype=place
http://192.168.83.131/wuzhicms/www/index.php?m=promote&f=index&_su=wuzhicms&v=search&fieldtype=place&keywords=1%27%20or%20extractvalue(1,concat(0x7e,user()))%20--+
3、后台注入2
在copyfrom.php文件中可以看到这里外部变量$keyword
直接拼接到$where
变量中然后传入get_list
函数中执行。这中间没有什么过滤,但要注意$where
中的引号闭合。
直接构造测试语句
http://192.168.83.131/wuzhicms/www/index.php?m=core&f=copyfrom&_su=wuzhicms&v=listing&keywords=1%27%20or%20extractvalue(1,concat(0x7e,user()))%20--+
4、后台注入3
/coreframe/app/member/admin/group.php中groupid参数存在SQL注入,在del
函数中会先检查groupid参数是否为空是否为数组类型,然后
然后传入$where
参数中接着传入db类中的delete函数,跟进delete函数。
先过滤一遍然后传入array2sql
进行过滤,如果不是数组类型的话这个函数只会过滤%27
即二次编码的'
单引号。然后在传入master_db->delete
函数中。
跟进mysql.class.php
,delete函数使用query
执行SQL语句,我们传入的groupid
也会拼接到$sql
中。
如果执行sql出错,query
会把报错显示出来,这里可以使用报错注入。这里$where
没有引号保护且位于SQL语句最后,不用闭合也不用注释后面的语句。
http://192.168.83.131/wuzhicms/www/index.php?m=member&f=group&v=del&_su=wuzhicms&groupid=999%20or%20extractvalue(1,concat(0x7e,user()))
5、任意文件写入导致RCE
这里通过搜索file_put_contents()
函数,找到一处写入内容可控的地方。file_put_contents() 函数把一个字符串写入文件中。
这里的$data
是可控的。搜索set_cache
函数发现coreframe/app/attachment/admin/index.php 中$data
外部可控,可以通过seting变量接收外部的值。这里需要设置submit
构造访问路由,测试
http://192.168.83.131/wuzhicms/www/index.php?m=attachment&f=index&_su=wuzhicms&v=ueditor&submit=1&setting=%3Cphp%20phpinfo();%3E
可以看到成功写入
那要如何访问,我们注意到在ueditor
函数中,当未设置submit时,调用get_cache函数读取缓存文件。
这样也就能访问到上传的文件了。
http://192.168.83.131/wuzhicms/www/index.php?m=attachment&f=index&_su=wuzhicms&v=ueditor
6、目录遍历
全局搜索glob函数,发现 coreframe/app/attachment/admin/index.php 中$dir
参数外部可控
而且此处还有过滤,但是可以绕过,.....///
过滤后成../
这里会在路径最后自动加一个/
符号,这里要注意。
还可以删除文件。
/coreframe/app/template/admin/index.php这个文件中还有一个函数也有同样的问题。
测试
http://192.168.83.131/wuzhicms/www/index.php?m=template&f=index&_su=wuzhicms&v=listing&dir=.....///.....//
7、任意文件删除
通过全局搜索unlink
,看哪些地方调用。
在coreframe/app/attachment/admin/index.php中对定义了一个my_unlink函数。这里调用unlink进行删除操作。
搜索发现,unlink
函数和my_unlik
配合使用。但是这里$path
变量并不好控制。在coreframe/app/attachment/admin/index.php中del
函数中,当$id
没有值时会将url参数值传入到$path
变量中。
这里构造语句,使$this->my_unlink(ATTACHMENT_ROOT . $path);
执行,这里通过url参数控制path变量
测试:
成功删除