本文由红日安全成员: licong 编写,如有不当,还望斧正。
getshell比较麻烦,分享一下过程。希望大家能有所收获。通过SQL注入,我们成功的进入了后台。接下来我们尝试通过后台功能获取webshell。
在app\common\common\cache.php发现了以下代码:
代码13-14行,设置了文件所在路径,代码20-25行进行对文件内容进行了写入操作,如果变量$cache_data中的内容可控,则可以写入恶意代码,进而getshell。在该文件其余函数中,发现了对该函数的调用,我们来看一下具体代码:
代码68行,将'options'作为参数传入table函数中,返回表前缀+'options',然后对该表进行查询,返回内容放入到$options
中,然后对该变量进行遍历,每次遍历取出其中的'option_name'和'option_value'内容,遍历完成后,调用write_cache函数进行内容写入。这里我们要注意,取出内容通过addslashes()进行了内容转义,而取出内容在单引号之中,意味着,我们无法闭合单引号,注入代码。该利用点无法使用。我们继续往下寻找:
在代码98行,我们发现了$ad['adver_etips']
没有调用addslashes函数进行内容转义,这时候有人或许会疑问了,$ad[adver_type]
等变量也没有调用addslashes函数,我们去看一下数据库表中字段定义:
在未被转义的字段中,只有adver_etips字段数据类型为varchar,可以存储字符串内容。由此,总结一下利用条件。
1.未调用addslashes函数对内容进行转义。
2.数据表字段类型需能存储字符串。
通过观察数据库中该表的内容,发现与广告有关,于是来到了app\admin\controller\adver.php文件中,该文件与广告相关。
在数据插入和更新后都调用了update_cache函数,此时传入的参数为'advers',跟进一下该函数:
如果$cache_name
为空,则初始化一个空数组,如果不为空,判断是否为数组,是数组直接赋值给$update_list
,不是则使用$cache_name
变量初始化一个数组,然后赋值到$update_list
中,遍历$update_list
数组,假设传入参数为'advers',通过call_user_func()函数,调用advers_cache函数。找到函数调用以后,我们来看一下POC该如何构造。由write_cache函数,我们来到data\static\advers.php中:
注意代码第5行,这是一个坑点,在代码13行, 这里是我们构造数据位置,通过上一篇文章可知,该cms全局默认采用htmlspecialchars函数进行转义,'>'符号无法使用,在这里我考虑上下闭合的思路。找到最后一条记录,方便构造。
POC如下:
\')); phpinfo() ;array(array(\'
两个分号中间的位置,可构造任意代码。接下来我们验证一下POC是否可用,:
在过期提示的位置,输入构造的POC,前端有长度限制,可通过burp抓包修改,或者更改前端代码后输入。
缓存文件中的内容成功更新,但因为有代码第6行限制,我们无法直接访问该文件,生成文件总归要使用,我们去寻找一下该文件的调用:
在app\common\common\cache.php中,load_cache函数负责对缓存文件进行调用。如果传入的参数$cache_name
为'advers',则上述利用成功,我们全局搜索一下对load_cache函数的调用:
很不幸,不存在调用load_cache函数时,传入参数为'advers'。找不到办法了,于是想起来cms路由没有进行分析,如果路由存在问题,能够访问到该文件,则可以利用成功,我们去cms路由方法看一看,在core\start.php文件中,通过getParamByPathinfo函数进行路径处理,我们具体看一下代码:
代码40行, 取出当前URL,也就是URL除去域名部分,去除左右两边的'/',然后赋值给$request_url
。代码42行,查找$request_url
中是否有'.html',$url_html_suffix
默认为html,如果不存在则返回false,存在则进入else分支,代码45行,将$request_url
中的'.html'替换为空。然后将'/'作为分割符对字符串进行分割。
如果查询URL为:http://127.0.0.1/home/update.html , $part0=('0'=>'home','1'=>'update');
代码48行,$_SERVER['QUERY_STRING']
变量存储的URL中'?'以后的内容,如果不为空,则使用'&'做为分割符,然后将分割结果存放到$part1
。接下来进行遍历,对数组取出内容使用'='进行分隔,然后'='前面的内容作为键名,后面内容作为键值。存放在$path_param
数组中。
如果查询URL为:http://127.0.0.1/home/update.html ?aa=123&bb=234
则$path_param=('aa'=>'123','bb'=>'234');
我们继续往下看代码:
将$part0
中'.html'内容替换为空,然后降序排序,此时$part0=('0'=>'update','1'=>'home');
$data['module']='home'
;$data['controller'] = 'update';
继续往下看:
判断了一下请求方式,然后调用了array_merge函数,这是个点,稍后有用,由上一篇文章可知,$data
数据内容最终会来到下图位置:
其中,__MODULE__
值等于$data['module']
,__CONTROLLER__
的值等于$data['controller']
,如果$data['controller']
我们可控,传入类似于'../../../data/static/advers',则能够跳转缓存页面,回溯一下变量来源。
$data['controller']
=>array_merge($tmp, $_POST,$data,$path_param)
=>$part
=>$part0
=>$request_url
=>$_SERVER['REQUEST_URI']
$_SERVER['REQUEST_URI']
变量内容我们可控, 但是代码47行:
这里对$part0
使用'/'作为分隔符,对内容进行了分隔,所以上述的猜想是不能实现的。array_merge($tmp, $_POST,$data,$path_param)
函数调用成了唯一救命符,我们来看一下该函数的作用。
此时我们可通过设置$path_param['controller']
覆盖掉data['controller']
,进而包含到指定文件。
该cms分析到此结束了,在后台getshell时做了很多尝试,上传的地方采用了白名单验证,数据库备份在第一句话直接退出,利用找了挺久,希望大家能有收获,审计新手,大家有什么新的方式可以交流一下,数据库删除好像存在任意文件删除,可以删除锁文件,然后重装getshell,感兴趣的小伙伴可以自己审看一看。