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