近日,对某cmsV9.9版本进行代码审计,发现了4处漏洞。

这4处漏洞漏洞比较基础,也很经典。从这4处漏洞,可以反应了在程序开发过程中一些容易忽略的问题,下面分享下本次审计过程。

审计之旅

在开发程序时,如果没有正确的过滤单引号(’)、双引号(”)、反斜杠(\)等特殊字符,往往会产生代码/sql注入漏洞。

在针对这些特殊字符,开发者经常使用如下方式进行过滤:

  1. 使用addslashes进行过滤
  2. 使用str_replace对单引号等进行替换操作
  3. 使用is_numeric等方法对数字类型的输入进行判断与过滤

审计此cms时发现,以上3种方式,在此程序的开发过程中,都有使用。但是,不严谨的使用,使得注入漏洞仍然存在

首先分析下此cms,来看下\include\common.php文件
1.png

如上图,在common.php文件中require filter.inc.php

在filter.inc.php文件中,存在如下图代码
2.png
此cms使用伪全局变量的模式,使用foreach从’_GET’,’_POST’,’_COOKIE’中遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值过滤后作为变量的值
3.png
跟进_FilterAll
4.png
可见,程序在此处使用addslashes对键值进行过滤

这样一来,我们可以通过’_GET’,’_POST’,’_COOKIE’为程序中的任意参数传入值,但是传入的值会被addslashes过滤

例如 http://www.testcms.com/index.php?grq=1’23

程序接收此url后,程序中的grq变量会为:$grq=”1'23”

开发者对这样的处理方式仍然不放心,于是,在\include\common.php文件中require_once(sea_INC.”/filter.inc.php”)进行过滤后,再次从’_GET’,’_POST’,’_COOKIE’中取键值对进行伪全局变量赋值与过滤
5.png
跟进_RunMagicQuotes方法
6.png
_RunMagicQuotes方法中对键值使用addslashes进行过滤与赋值变量

以上两段代码依次执行,对比下这两处代码
7.pngfilter.inc.php文件中的过滤与赋值代码

8.pngcommon.php文件中的过滤与赋值代码

filter.inc.php文件中赋值的${$k}被后续common.php文件中的${$k}覆盖了,filter.inc.php文件中的过滤部分代码白写了

虽说filter.inc.php过滤部分代码在做无用功,但是仍然可以看出开发者对特殊字符的防范意识很高

在了解完该程序之后,接下来漏洞:

1.未对拼接参数使用单引号闭合而导致的sql注入漏洞

9.png
位于上图143行处

经过上文对程序伪全局变量赋值方式的分析可知,这里$leftSelect 可由GET方法传递的来。当使用GET传入$leftSelect时,程序会使用addslashes对参数进行过滤

但是如上图143行拼接的sql语句来看,并未对$leftSelect变量使用单引号进行闭合,导致虽然使用addslashes函数进行过滤,但仍然存在sql注入漏洞
构造payload
&leftSelect=1 or updatexml(1,concat(0x7,user(),0x7e),1)

最终执行的sql语句是:

UPDATE sea_data SET tid= 1 where tid= 1 or updatexml(1,concat(0x7,user(),0x7e),1)

同样,在另一处文件中admin_tempvideo.php, 也存在相同的漏洞
10.png
11.png
如上图可见,$ids变量被拼接到sql语句中,并在上图25行被执行

$ids变量由$e_id通过implode方法拼接而来,而$e_id变量可以通过GET方法直接传入,$e_id变量可控,由此造成sql注入漏洞

构造的payload如下

[&e_id0]=1)%20or%20extractvalue(1,concat(0x7e,(SELECT%20CONCAT_WS(0x23,name,%20password)%20FROM%20sea_admin%20limit%200,1)))–%20&type=1 or extractvalue(1,concat(0x7e,(SELECT CONCAT_WS(0x23,name, password) FROM sea_admin limit 0,1)))– &type=1)

2.对键值进行过滤,忘记对键名进行处理

漏洞文件: admin_config.php
12.png
如上图,看到将$configstr变量写入文件中去

查看下写入文件的具体位置
13.png
该位置固定,即为/data/config.cache.inc.php
14.png
config.cache.inc.php

跟踪下$configstr变量
15.png
$configstr变量由$k与$v拼接而来,

在下图红框中所示,$$k的值经过str_replace方法过滤
16.png
在程序入口处,通过伪全局变量的方式,其实已经对$_POST中的$k进行变量赋值,所以$$k的值即为通过POST传入的变量的经过过滤的键值

例如POST中

&edit___grq=te’st

那么,此时的$k=“&edit___grq”,$&edit___grq=”te\’st”,$$k=” te\’st”

值得注意的是,上图中仅仅对$$k 进行str_replace处理,而$k并没有经过任何过滤,直接拼接到$configstr变量中,也就是说,可以通过POST提交的KEY值传递构造好的payload,该payload将会被写入文件中去,造成远程代码执行

回头看一下config.cache.inc.php文件
17.png
开发者在写过滤代码时,考虑到程序在处理配置文件经常会出现漏洞:即配置文件中变量值注入的问题

在以往的此类漏洞中,往往是未对配置文件中变量值进行合理的过滤,导致单引号等特殊字符被写入值部分,从而构造闭合结果导致注入的产生。

所以,开发者吸取了以往的经验,对写入配置文件中的值部分进行str_replace处理,对”’”与”\”进行转义。但是,由于这里的变量名同样可控,而且未对变量名进行任何过滤,因此,str_replace处理形同虚设

使用如下payload,直接注入

&edit___a;phpinfo();//=1
18.png

3.经过严格的过滤,报错日志文件中却存在利用点

漏洞触发点位于\comment\api\index.php
19.png
由于seacms采用伪全局变量的形式,$page $id等变量可以从GET请求参数中传递进来

程序使用is_numeric对$page进行限制,使得$page必须是数字

上图最下面一行,$h = ReadData($id,$page);

可见将GET请求传入的$id,$page 传递进ReadDate方法中

跟入ReadDate方法
20.png
可见GET请求传入的$id,$page 传递进Readmlist方法中

跟进Readmlist方法
21.png
在Readmlist方法中的88行,可见存在一处sql语句
22.png
可以看到,该sql语句中拼接了$page变量,而$page变量是由GET请求接收而来,可控

当我们通过GET请求传递一个负数值的page时(例如-1),此时执行的sql语句为
SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=1 AND v_id=666 ORDER BY id DESC limit -20,10

该sql语句会导致sql语法错误而报错,如下图
23.png
到此为止,程序仍然是安全的,并不存在sql注入或是代码执行漏洞。但是此cms对报错日志的处理方式,却很有意思:

当sql语法出现错误时,程序会将报错日志会被写入\data\mysqli_error_trace.php
24.png
跟进mysqli_error_trace.php文件,错误日志的格式如上图:

错误日志会被包裹

第一行(第一个红框)为触发错误的url

第二行(第二个红框)为sql语法错误信息

因此,当构造payload如下时

comment/api/index.php?page=-1&gid=666&payload=/phpinfo();/

执行成功后,可见成功写入mysqli_error_trace.php,如下图:
25.png
访问/data/mysqli_error_trace.php

即可执行phpinfo

结束语

从本次代码审计的结果来看,程序在使用addslashes等过滤方式对输入进行过滤后,仍然产生了两处代码执行,两处sql注入。因此使用addslashes等过滤方式在程序入口处对输入进行过滤并不能代表程序固若金汤。配合程序自身的逻辑,使用恰当的方式进行参数过滤才是最优解。