php代码审计工具_【学习笔记】PHP代码审计入门:代码审计实例1

本文通过实战演示如何审计并利用SQL注入漏洞,重点介绍了在现代开发框架中寻找潜在风险的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第  34    

 代码审计实战之SQL注入漏洞 

课程入口(付费)

6314608e3c8e74c961be70ddc219039e.png

个人背景

李,本科,电子信息工程专业,毕业一年半,有JavaScript的,PHP,Python的语言基础,目前自学网络安全中。

代码审计实战之SQL注入漏洞

现代开发框架对数据库操作的封装

当今主流的框架中,基本上都会对数据库的操作做一层封装,

       16d59b7ec593012c342327d0c387531f.png

上面的代码中使用了参数绑定,在sql语句中出现了 :name 这个变量,在后续的数组中对于name这个变量填充了pockr作为内容,通过参数绑定可以有效地避免sql注入。

大部分PHP框架会对sql操作有封装,这些封装通常都会有类似get(),findOne() 这样的函数,这个函数一般用于接收一个字符串或数字,但是为了方便使用,还会设计成可以接收一个数组的形式,类似的函数还有拼接where条件的where()函数等。在sql中,是用两个反引号包裹数据表的字段名。

    3c2e62ea60437a4f8257f09d705738e5.png      

根据这2个特征点,我们不难分析出,有人可能贪图方便,或者是没有完整阅读框架文档,再或者是没有用一些用例测试过,反正在总总原因下,写出了这样的代码:

User::findOne($_GET['id'])

那么这样的代码,就是一个危险代码特征,我们审计可以从这样的代码入手.

 fecshop商场系统审计示例 

b153aa989ea7a8bdf2d5c4561a5a05a1.png

根据上面的总结,我们知道findOne函数的危害比较大,主要危害在于当参数是外部完全可控的情况,那么我们审计的目的也很明显了,先从检索findOne函数开始开始。

c9d86c356150a1528b68c19f2e5ac895.png

这里我简单改了一条正则表达式出来,专门用于搜索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这个函数,发现没有调用的痕迹,基本都是一堆同名函数的声明.

       a66d2bcb66f62161230a7d40775e0a50.png       

出现这个结果,要么是真的没有地方调用,要么通过脚本语言灵活的特性去调用,这种特性无非就是去掉函数的第一个单词或者最后一个单词.我们这个Address.php的函数都是action开头,那么不难推断出action是一个通用的前缀,我们取后半段GetByPrimaryKey搜索

这么一来结果的确多了很多,我们继续找到调用这个address里的GetByPrimaryKey的代码

同时我们发现右侧的结果非常有规律,根据这些规律还可以进一步调整我们的检索关键字为address->GetByPrimaryKey 其实这一步比较讲究运气,如果是质量比较高的开源项目的话,都有一定的规范,不会乱写乱改名,所以这里我们这一次搜索还是出了几个结果.

      eb9c253ea1e0ace332679782962fa60f.png      

我们挑一段短点的代码来分析,根据文件名Edit.php不难猜测这可能是一个修改地址功能对应的代码.而这个里调用address的GetByPrimaryKey的参数$this->_address_id来源,则是上面那行$this->_address_id = Yii::$app->request->get('address_id');代码。

我们知道了在Yii框架里面Yii::$app->request->get和$_GET作用类似,同时我们也看到整段代码没有任何的过滤,这时候我们打开浏览器访问这套系统

       c40959bc3c6995efdb9390cc7094f8d9.png      

找到有修改地址的地方,验证一下是不是对应这段代码.验证过程也非常简单,无非是在代码里输出点什么东西,或者结束脚本,或者下个断点.这里我简单结束了脚本,顺便输出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'

       f38beca7afcbbf0275295b69c3d34445.png       

报错也挺明显的,可以看到执行的sql里,where条件中的字段被篡改成了pockr123,电商系统的数据库显然不会有一个叫pockr123的字段,这一点根本不需要查看这个系统的数据库结构来佐证.

其实到这里漏洞已经发现完毕了,那么怎么去利用呢,这里也给大家讲解一下.首先我们的pockr123被一对反引号包裹着,我们先尝试闭合反引号.

先把url中的address_id[pockr123]改成address_id[pockr123]`,看看输出发现尝试执行的sql语句变成了:

SELECT * FROM `customer_address` WHERE pockr123`='3'

       0b54d649bc7f0740217f31f8255faf89.png      

我们开篇的知识背景里介绍到,字段名的正确过滤方案是两边加反引号,而字段名出现反引号时,还要再这个引号前面加多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语句都写到日志里,然后查看数据库的日志

       d50855d0d98e115cf8a5dcf3557d2363.png      

最后提一下,现在Yii框架已经修复了这个函数的问题,大家可以看下更新说明,它的方案其实是把数据表的所有字段列出来,然后判断你输入的字段名是否存在这些字段里,相当于一个白名单策略.

在我们这个案例里,虽然更新Yii的版本可以防范住sql注入,但是仍然可以提交一个数组来篡改where条件,从而达到其它的目的.里面还提到了where()和filterWhere()永远不会转义列名.大家可以顺着这个思路去挖掘其它类似的sql注入。

0

下期更新笔记内容:

PHP代码审计入门:代码审计实例2

90a34e3149a82e64140f020551c68ee3.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值