第 34 课
代码审计实战之SQL注入漏洞
课程入口(付费)

个人背景
李,本科,电子信息工程专业,毕业一年半,有JavaScript的,PHP,Python的语言基础,目前自学网络安全中。
代码审计实战之SQL注入漏洞
现代开发框架对数据库操作的封装当今主流的框架中,基本上都会对数据库的操作做一层封装,
上面的代码中使用了参数绑定,在sql语句中出现了 :name 这个变量,在后续的数组中对于name这个变量填充了pockr作为内容,通过参数绑定可以有效地避免sql注入。
大部分PHP框架会对sql操作有封装,这些封装通常都会有类似get(),findOne() 这样的函数,这个函数一般用于接收一个字符串或数字,但是为了方便使用,还会设计成可以接收一个数组的形式,类似的函数还有拼接where条件的where()函数等。在sql中,是用两个反引号包裹数据表的字段名。
根据这2个特征点,我们不难分析出,有人可能贪图方便,或者是没有完整阅读框架文档,再或者是没有用一些用例测试过,反正在总总原因下,写出了这样的代码:
User::findOne($_GET['id'])
那么这样的代码,就是一个危险代码特征,我们审计可以从这样的代码入手.
fecshop商场系统审计示例根据上面的总结,我们知道findOne函数的危害比较大,主要危害在于当参数是外部完全可控的情况,那么我们审计的目的也很明显了,先从检索findOne函数开始开始。
这里我简单改了一条正则表达式出来,专门用于搜索findOne函数中有变量传入的代码
工具部分看个人习惯,我习惯是用vscode去搜索,开启正则表达式模式
findOne\s{0,5}\(.{0,40}\$\w{1,15}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1}
我们还可以通过更激进一点的搜索条件,搜索findOne($来保证传入的参数百分百是个变量.
接下来就到了体力活的时候了,根据搜索到的结果代码一个一个点击进去回溯,看看变量是否可控.这里我直接以其中一个问题点给大家分析.
首先我选择的是这个Address.php,在商城系统里,出现Address这个单词,大多数情况它会是一个收货地址相关的代码,它出现findOne的代码有3处
/**
* @property $primaryKey | Int
* @return Object(MyCoupon)
* 通过id找到customer address的对象
*/
protected function actionGetByPrimaryKey($primaryKey)
{
$one = $this->_addressModel->findOne($primaryKey);
$primaryKey = $this->getPrimaryKey();
if ($one[$primaryKey]) {
return $one;
} else {
return new $this->_addressModelName();
}
}
/**
* @property $one|array , 保存的address数组
* @return int 返回保存的 address_id 的值。
*/
protected function actionSave($one)
{
...
/**
* @property $ids | Int or Array
* @return bool
* 如果传入的是id数组,则删除多个address,如果传入的是Int,则删除一个address
* 删除address的同时,删除掉购物车中的address_id
* 删除address的同时,如果删除的是default address,那么重新找出来一个address作为default address并保存到表中。
*/
protected function actionRemove($ids, $customer_id)
{
...
这几个函数顾名思义,一个是根据主键获取一条数据,一个是保存数据,一个是删除数据,我们来看获取数据的代码被谁调用了.
这里直接搜索actionGetByPrimaryKey这个函数,发现没有调用的痕迹,基本都是一堆同名函数的声明.
出现这个结果,要么是真的没有地方调用,要么通过脚本语言灵活的特性去调用,这种特性无非就是去掉函数的第一个单词或者最后一个单词.我们这个Address.php的函数都是action开头,那么不难推断出action是一个通用的前缀,我们取后半段GetByPrimaryKey搜索
这么一来结果的确多了很多,我们继续找到调用这个address里的GetByPrimaryKey的代码
同时我们发现右侧的结果非常有规律,根据这些规律还可以进一步调整我们的检索关键字为address->GetByPrimaryKey 其实这一步比较讲究运气,如果是质量比较高的开源项目的话,都有一定的规范,不会乱写乱改名,所以这里我们这一次搜索还是出了几个结果.
我们挑一段短点的代码来分析,根据文件名Edit.php不难猜测这可能是一个修改地址功能对应的代码.而这个里调用address的GetByPrimaryKey的参数$this->_address_id来源,则是上面那行$this->_address_id = Yii::$app->request->get('address_id');代码。
我们知道了在Yii框架里面Yii::$app->request->get和$_GET作用类似,同时我们也看到整段代码没有任何的过滤,这时候我们打开浏览器访问这套系统
找到有修改地址的地方,验证一下是不是对应这段代码.验证过程也非常简单,无非是在代码里输出点什么东西,或者结束脚本,或者下个断点.这里我简单结束了脚本,顺便输出pockr字样,保存代码后刷新页面,那么可以判断出这个页面对应的代码也找对了.
这个时候我们修改一下url中address_id参数改成一个数组,先看看有什么报错
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'pockr123' in 'where clause'
The SQL being executed was: SELECT * FROM `customer_address` WHERE `pockr123`='3'
报错也挺明显的,可以看到执行的sql里,where条件中的字段被篡改成了pockr123,电商系统的数据库显然不会有一个叫pockr123的字段,这一点根本不需要查看这个系统的数据库结构来佐证.
其实到这里漏洞已经发现完毕了,那么怎么去利用呢,这里也给大家讲解一下.首先我们的pockr123被一对反引号包裹着,我们先尝试闭合反引号.
先把url中的address_id[pockr123]改成address_id[pockr123]`,看看输出发现尝试执行的sql语句变成了:
SELECT * FROM `customer_address` WHERE pockr123`='3'
我们开篇的知识背景里介绍到,字段名的正确过滤方案是两边加反引号,而字段名出现反引号时,还要再这个引号前面加多1个反引号,也就是说这里的pockr123`应该被过滤成 `pockr123```
这里显然没有正确的过滤,只是检测到字段名包含反引号的时候就取消包裹了。如果想篡改这条sql证明sql注入存在,可以尝试让它执行的sql成这个样子:
SELECT * FROM `customer_address` WHERE address_id=-1 or 1=1 and union select * from customer_address;# `='3'
我们用把字段名pockr123改回address_id,是为了防止sql语法自身的错误,观察这个customer_address数据表发现存在address_id这个字段,所以就把pockr123改成了address_id.
而address_id=-1是为了让直接执行后续的条件,因为大部分的系统的主键都是从1开始的.
;#则是为了结束这条sql,并且把后面的sql语句注释掉.所以我们应该修改参数,让下面的这半截语句插入
address_id=-1 or 1=1 union select * from customer_address;#
那么我们的
address_id[pockr123`]
就改成了
address_id[address_id=-1 or 1=1 union select * from customer_address;#`]
当然不要忘记urlencode一下,
address_id%5Baddress_id%3D-1%20or%201%3D1%20union%20select%20*%20from%20customer_address%3B%23%60%5D
再次访问之后发现页面没有报错,那么怎么证明sql注入成功了呢?
你可以查看配置mysql的general_log和general_log_file属性,让每一条sql语句都写到日志里,然后查看数据库的日志
最后提一下,现在Yii框架已经修复了这个函数的问题,大家可以看下更新说明,它的方案其实是把数据表的所有字段列出来,然后判断你输入的字段名是否存在这些字段里,相当于一个白名单策略.
在我们这个案例里,虽然更新Yii的版本可以防范住sql注入,但是仍然可以提交一个数组来篡改where条件,从而达到其它的目的.里面还提到了where()和filterWhere()永远不会转义列名.大家可以顺着这个思路去挖掘其它类似的sql注入。
0下期更新笔记内容:
PHP代码审计入门:代码审计实例2
