这是酒仙桥六队第93篇文章。
全文共2986个字,预计阅读需要11分钟。
前言
记得在一次授权渗透测试中遇到过这样一个项目,开始fuzz前端,扫描端口,没发现可利用的漏洞,本来可以挖出xss,结果没找到,不行就挖出信息泄露,果然挖出了信息泄露,拿到了程序的指纹。
它是一个开源网站系统,我在百度上搜索了程序暴露的漏洞,并进行了测试,发现全部失败,我猜测是程序升级了较新的版本,导致网上暴露的漏洞被修复了。既然没有捷径,只能去官网下载一份源码审计测试一下。
源代码审计
1.数据采集
通过查看程序源码发现,程序封装了自己的数据获取辅助函数:get()、post()、()等,其获取流程如下:以post()函数为例:post('name', 'vars')。
可以看到我们传入的数据又被传递到了函数中:
在函数中会对获取的数据进行一系列强过滤,比如这里的vars只能传递汉字、字母、数字、点、逗号、空格等。(PS:因为不能传递括号“(,)”,所以SQL注入中的函数无法使用,也导致了误注入、盲注,只能使用联合查询。)然后函数最后进行处理。
跟进功能:
数据也经过双重处理。
数据采集流程图如下所示:
现在我们对这个程序的数据获取已经有了初步的了解,主要的数据获取是通过post()、get()、get()三个辅助函数来实现的。
2. 注入开采
在审计过程中,发现该程序含有大量如下代码:
这里我重点讲一下DB类库中封装的where方法,代码如下:
如果这里传入的$where变量是索引数组,那么就会输入红框里的代码,并将值连接起来。
明确了目标之后,我们就可以开始寻找目标了:
一番查找之后发现,要么是数据不可控,要么是数据经过了函数,加上单引号的保护,达到了过滤的效果。
3. 事态突然转变
经过多次搜索where等关键字,均未获得突破性进展,随后尝试搜索$GET、$POST等原生态数据:
问题出现在apps\home\\.php文件中的方法中:由于此方法代码较长,因此仅抓取关键部分。
可以看到,首先遍历了$_POST数组,然后把该key作为$where数组的键。(这个就是key),但是这里我们需要验证一下key的值是不是1,以及这个1是整数还是字符串,因为我们要控制索引数组的输入。
是int类型的,完全满足我们上述的要求。
此时我们可以控制这个$array的key和value了,但是$key:只能是/^[\w-.]+$/,$value:只能是/^[\x{4e00}-\x{9fa5}\w-.,\s]+$/u。
然后继续看代码:
这里的$page是true,因为默认值就是true,而重新赋值的时候我们也没有办法控制,所以这里肯定会执行该方法。
方法代码如下:代码较长,只截取关键部分。
我们传递的$array已成功传递给where方法。
然后执行一个页面方法:
这里设置了一个sql属性,后面会用到。
然后执行了最后一个方法。
做法就是把我们之前设置的属性值组合起来,形成一个完整的SQL语句。
这里我们直接通过where、order、page等链式操作把我们之前设置的属性进行替换和串联起来了,而且因为我们前面分析过,在where方法中,如果传入的是索引数组,是不受单引号保护的,所以看到这里,我们差不多可以明白我们成功逃脱了单引号的保护。
总体流程图如下:
整个过程已经基本完成,测试结果如下:
可以看到我们的输入已经成功带入SQL语句执行了,需要注意的是,括号()里面是我们输入的内容。
结合上面辅助函数的过滤,我们知道输入的数据只能是指定的字符:
常规的错误注入是无法成功的,比如:
页面并没有像上面一样报错,而是返回了一个正常的页面,因为检测到了括号,直接将我们的数据设置为了null。
4. 旁路注射
因为可控制点在where之后,所以where后面可以跟子查询,如图:
所以我们绕过这个问题的方法是通过子查询进行操作,因为子查询不需要使用括号,如:
我们的注入没有被过滤,成功引入到了SQL语句中。但是由于不能使用括号,不能使用mid等截断函数,也不能使用=等一些比较运算符,如何获取精准数据又成了问题?
这里的突破目标是改造SQL语句,首先需要了解SQL的执行顺序。
可以看到之前已经执行了where,那么如何使用呢?如下:
可以看出,即使是常量,如果不满足后面的where条件,那么也不会查询出任何数据,我们可以利用where比例来进行数据对比。
因为不能使用=之类的比较运算符,所以我们需要找一些东西来代替它,而且因为不能使用截取函数,所以我们不能逐一比较数据,所以我们必须想办法逐一进行比较。
找到一个完美的替代,因为它后面可以跟一个正则表达式,而且 . 可以代表任意字符,* 代表任意数字,刚好满足我们的要求。用法如下:
把我们需要查询的字段放到where中,通过where来控制返回的数据。
(请使用^来定义开头。例如:^ad.*)
因为数据不能使用引号,所以我们需要将引号里的数据用十六进制进行编码,效果是一样的。
对返回的内容进行控制,实现的效果如下:
在Sql语句中,先执行的是子查询,而在整个父语句中,我们的子查询的结果在where语句中,而且使用了AND连接。也就是说,我们的子查询的结果也控制了整个sql语句的结果,因此才可以准确的判断数据。
5. 本地测试
正确的页面显示:
错误页面显示:
我们的真实数据:
我们将我们的正则表达式编码成16进制并执行,通过页面返回的内容判断数据量,最终达到绕过过滤,输出数据的目的。
最终整体流程图可以分为三步,如图所示:
测试结果证明我们的注入漏洞被成功利用,接下来就是将其映射到项目网站上,经过大量的,我们成功获取了管理员账号密码:
后台
易受攻击的文件:core\\file.php
后缀白名单来自于函数的第三个参数,寻找函数被调用的地方。
后缀白名单来自于函数的第二个参数,搜索函数调用。
触发文件:apps\home\\.php
继续跟进方法。
方法是返回对应的配置项,通过self::()来加载配置项内容。
部分配置项来自于md5().php文件,我们只要控制这个文件中的选项,就能控制允许上传的后缀白名单。
这是您修改配置文件的地方。
文件:apps\admin\\\.php
从 $_POST 遍历的键被传递到 $this-> 方法中。
程序会先把修改后的配置内容更新到数据表中,然后再把数据表中的内容写入到md5().php文件中,这样我们就可以添加任意类型的后缀文件了。
使用
登录后台->全局配置->配置参数->立即提交,使用burp抓包。
只需在 POST 数据中添加 :=php 字段。
已成功将其写入文件。
设置好允许上传白名单之后,我们可以通过上传PHP来实现。
上传文件 exp: .html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>pbootcms文件上传title>
head>
<body>
<form action="http://xxxxx/?member/upload" method="post" enctype="multipart/form-data">
<input type="file" name="upload">
<input type="submit" name="mufile">
form>
body>
html>
将exp保存为HTML文件,修改对应域名后直接上传,如文件上传证明所示。
在本地顺利走完流程之后,在项目网站上使用也很顺利,直接就搞定了,又可以开开心心的喝冰可乐了。
总结
整个过程从网站获取指纹开始,然后找到源代码进行审计。审计过程中还是花了不少时间,主要是寻找前端审计的入口点,绕过过滤器注入数据。当时觉得根本用不上,还好当时没放弃,慢慢一步步做,最后搞定了。
扫一扫在手机端查看
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。