为了大致了解此cms,首先来看看这个cms为了防止sql注入,都做了哪些防护

一、程序入口处定义了过滤方法

首先,程序通过set_globals方法,将get与post传入的参数,赋值到$GLOBALS数组中:

在赋值过程中,使用gpc_stripslashes来处理传入的键值

gpc_stripslashes方法的作用是,当MAGIC_QUOTES_GPC开启时,去除MAGIC_QUOTES_GPC所添加的转义符

这里的操作很奇特,在一般的cms中,在进行这样为伪全局变量赋值时,往往会判断MAGIC_QUOTES_GPC是否开启,当MAGIC_QUOTES_GPC为off时,程序会履行MAGIC_QUOTES_GPC的功能,为伪全局变量进行转义过滤。

如下图,另一款cms中的操作:

但是在本文中分析的cms中,正好反向操作,如果MAGIC_QUOTES_GPC开启,则去除其转义效果,这个cms为什么要这样做呢?接着往下看。

可见该cms存在input方法,该方法对$GLOBALS数组中的值通过sql_replace方法进行sql过滤

分析一下sql_replace方法

可见是使用str_replace方法,对传入参数中的特殊字符进行置空处理,防止sql注入攻击

此外,还存在一处safe_htm方法

对输入中的html特殊字符进行转义,方式xss攻击

开发者的意图,在这里猜测一下,应该是:

将GET\POST的传入键值对,原封不动的传递给$GLOBALS数组,若数据需要入库,使用sql_replace方法对数据进行过滤;若数据需要前端页面展示,使用safe_htm方法进行转义

通过我对开发者意图的猜测可知,之所以在将GET\POST的传入键值对传递给$GLOBALS期间使用stripslashes去掉MAGIC_QUOTES_GPC可能添加的反斜杠,是为了防止后续sql_replace等操作时出问题

例如在MAGIC_QUOTES_GPC开启时,GET中传入 grq=tes’t

此时系统会自动在单引号前加反斜线进行转义,$_GET[‘grq’]=”tes\’t”

若不使用stripslashes处理,直接赋值给$GLOBALS,则$GLOBALS[‘grq’]= ”tes\’t”

若使用sql_replace方法对$GLOBALS进行sql注入过滤,则会变成$GLOBALS[‘grq’]= ”tes\t”,多出一个反斜线,显然,这造成了极大的隐患。

那使用sql_replace方法对数据进行过滤是否安全呢?显然,单纯的将特殊字符置空,仍然有安全隐患

如果传入的值为%2%277,在经过str_replace置空后,变为%27,当程序中存在sql语句拼接执行前先解码(urldecode)的操作时,则会将%27解码为单引号,从而造成sql注入。但是这个cms中,并未存在类似 urldecode(sql_replace($grq));这样的操作,因此先不考虑sql_replace方法的绕过。

二、数据库操作类内部定义过滤方法

以该cms中封装的insert方法和update方法举例


insert方法

Update方法

可见,在该cms在使用封装的数据库操作类时,这些类内部的方法,也会对传入的数据新型过滤处理

以上两类便是该cms框架层面上对sql注入的防护,下面看看在这些防护下,是否还存在sql注入隐患

第一类安全隐患

第一类安全隐患,是由于开发者在框架中定义了安全的接收用户输入的方法(input方法),在开发过程中却忘记使用或记错这个方法的功能所导致的。

回归漏洞正文,经过我们的分析,这个cms的防护虽然繁琐,且存在绕过的风险,但只要正确使用input方法对入库前的数据进行处理,且处理后不要进行urldecode操作,那就能大概率规避sql注入问题,但是在漏洞挖掘的过程中,发现的问题却令人张目结舌

漏洞文件: \api\sms_check.php 中:

位于上图13行,可见$code变量的值由$GLOBALS[‘param’]中的值经过strip_tags方法处理后得来

接着,位于16行处,code变量被拼接到sql语句的where部分,进行执行

由上文分析下$GLOBALS[‘param’]是从GET\POST中原封不动的传来

这样一来,GLOBALS的值我们可以通过GET/POST传递。$GLOBALS[‘param’]的值可控,进而控制$code值,随后,$code变量被拼接到sql语句中执行

难道不应该使用input方法获取GET/POST传入的参数吗?使用类似如下的代码

1
$code = strip_tags(input('param'));

而不是程序中使用的

1
$code = strip_tags($GLOBALS['param']);

显然,开发者在这里忘了之前定义的用来接收用户输入并安全过滤的”input”方法,很明显这是开发时候的失误导致的。

那是不是由于开发者的疏忽,程序中只有很少几处存在这样的问题呢?

我们全局搜索一下input方法

在系统中,仅仅有四处使用了input来接收并过滤用户的输入

跟入其中一处,如下图

可见username通过input方法,从GET/POST请求中读取username值

input方法会调用sql_replace方法进行sql注入过滤,如下图

但是,位于上图第二个红框处,又使用了一次sql_replace对输入进行过滤。在这里我猜测,可能开发者忘记了input的功能了

构造如下payload:

1
http://127.0.0.1/www/api/sms_check.php?param=1%27%20and%20updatexml(1,concat(0x7e,(SELECT%20@@version),0x7e),1)--%20

Sql注入成功

同理,这种类型的漏洞在此cms中大量出现

第二类安全隐患

第二类安全隐患,是由于封装的数据库操作类内部的方法,只对传入数组的键值进行过滤,而忽略了键名仍有传入payload的风险

漏洞文件: \coreframe\app\content\admin\category.php 中的add方法:

由于$formdata = $GLOBALS[‘form’],因此$formdata可由GET/POST传入,可控。上图87行处, $formdata被传入db->insert方法进行sql语句执行

跟进db->insert方法:

上图位于109行至116行,此处代码段对传入$data的值($values)部分通过escape_string进行过滤,但是并未对$data数组的键($field)部分进行过滤,因此可以将payload传入key部分,绕过escape_string过滤,造成sql注入

回到注入点,如下图

问题出在上图73行处,直接将GET\POST传入的值赋值给$formdata,导致可以从请求中传入数组,进而控制$formdata数组的键名

Payload为

1
&form[seo_description`)values(updatexml(1,concat(0x7e,version(),0x7e),1))%23]=666

最终注入结果如下图:

注入成功

同理,这种类型的漏洞在此cms中大量出现

结束语

从本次代码审计的结果来看,程序虽然在框架入口以及封装的数据库操作类中进行了过滤,但由于对过滤方法的错误使用以及过滤点不全面,导致了大量的注入产生。