试着审计PHP,网上大师傅们分析的都很好,我就不班门弄斧了,仅记录复现过程(不推荐跟踪学习),学习审计思路。
贴几个师傅的链接,环境搭建、详细分析可以去看看
标题的xxx注入只是随便起的,不一定只有那一个,举个栗子而已
环境搭建,按版本自行搭建,测试版本在漏洞分析处,只需修改 composer.json
,5.0 和 5.1 不能直接 composer
# v5.0.x
composer create-project --prefer-dist topthink/think=5.0.15 tpdemo
将 composer.json 文件的 require 字段设置成如下:
"require": {
"php": ">=5.4.0",
"topthink/framework": "5.0.15"
}
然后执行 composer update
# v5.1.x
composer create-project --prefer-dist topthink/think tpdemo
将 composer.json 文件的 require 字段设置成如下:
"require": {
"php": ">=5.6.0",
"topthink/framework": "5.1.7"
}
然后执行 composer update
[TOC]
数据库创建:
create database tpdemo;
use tpdemo;
create table users(
id int primary key auto_increment,
username varchar(50) not null
);
insert注入(一)
漏洞概述
Builder
类(thinkphp/library/think/db/Builder.php
)的parseData
方法直接拼接用户输入数据造成 sql 注入
影响版本: 5.0.13<=ThinkPHP<=5.0.15 、 5.1.0<=ThinkPHP<=5.1.5
漏洞利用
使用poc直接访问 http://127.0.0.1/PHPSec/tpdemo/public/index.php/index/index?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1
漏洞分析
测试版本:5.0.15
在自定代码处打上断点进行调试
在application/index/controller/Index.php
的第2085行调用父类的insert
方法,跟进
继续跟
在thinkphp/library/think/db/Builder.php
的parseData()
方法中,当 var[0] 为 inc 时(也就是poc的 username[0] ),直接对参数进行了拼接,其中没有任何检测、过滤
而后在thinkphp/library/think/db/Builder.php
的insert()
方法中虽然有str_replace()
但是其只是简单替换,最终sql语句会造成注入
漏洞修复
并未修复 exp
,为什么?将poc的 username[0] 改为 exp 跟进试试。
在thinkphp/library/think/Request.php
处将 exp
替换为 exp
多了个空格,自然匹配不到,也就不用修了。。。
update注入(二)
先用composer create-project topthink/think=5.1.* tpdemo
再按照七月火师傅的进行下一步,因为现在安装都是6.0了
漏洞概述
Mysql
类的 parseArrayData
方法未检测、过滤用户输入,直接拼接数据造成sql注入
影响版本: 5.1.6<=ThinkPHP<=5.1.7 (非最新的 5.1.8 版本也可利用)
漏洞利用
poc:http://127.0.0.1/PHPSec/tpdemo/public/index.php/index/index?username[0]=point&username[1]=1&username[2]=updatexml(1,concat(0x7,user(),0x7e),1)^&username[3]=0
漏洞修复 & 分析
测试版本:5.1.7
tp 5.1.8更新时有个改进mysql驱动,删除了很多东西
看起来又是直接拼接呢,payload打一下跟着看看
<?php
# application/index/controller/Index.php
namespace app\index\controller;
class Index
{
public function index()
{
$username = request()->get('username/a');
db('users')->where(['id' => 1])->update(['username' => $username]);
return 'Update success';
}
}
在thinkphp/library/think/db/Query.php
的update方法中调用了connection.php
的update
然后是builder.php
跟进parseDate()
方法
在parseDate()
方法的最后出现了官方删除的段代码,跟进去
当username[0]
为 point 时直接拼接了传入的参数,也是没有过滤
后面就和第一次一样了,没有检测、过滤
至于 poc 怎么构造的,看看正常操作时生成的 sql 语句:UPDATE `users` SET `username` = b('c(a)') WHERE `id` = :where_AND_id
,使用异或闭合这个括号即可,即 a = updatexml(1,concat(0x7,user(),0x7e),1)^ 、 b = 0、 c = 1
==> UPDATE `users` SET `username` = updatexml(1,concat(0x7,user(),0x7e),1)^('0(1)') WHERE `id` = :where_AND_id
引用七月火师傅的一张图
where注入(三)
漏洞概述
Mysql 类的 parseWhereItem 方法未对用户输入数据进行检测、过滤造成 sql 注入
漏洞影响版本: ThinkPHP5全版本
漏洞利用
POC:http://127.0.0.1/PHPSec/tpdemo/public/index.php/index/index?username=)%20union%20select%20updatexml(1,concat(0x7,user(),0x7e),1)%23
漏洞分析
测试版本:5.0.10
先不用 poc 的,传一个正常的字符串进去看看最后的语句是什么样的,再去思考 poc 是怎么构造的 http://127.0.0.1/PHPSec/tpdemo/public/index.php/index/index?username=yq1ng
接着进入Query.php
,跟进 parseWhereExp()
,此处 形参的 op 为 exp ,这是手工构造的,后面有用
parseWhereExp
跟进来这个方法后发现就是分析查询表达式,没啥用,跳过。来到Query.php
的select()
方法,跟进这个生成 SQL 语句的方法
进来后发现很多方法,问题在parseWhere()
这里,跟进去
可以看出来第一句代码为构造 where ,我们开始构造的 index.php
就是 where查询,继续跟
然后 buildWhere()
方法走到 parseWhereItem()
这里,再跟进去,上面的可以跳过
然后跳到构造 whereStr
字符串这里,从349行开始,当 op(在此处也就是$exp
) 为 EXP
时,会直接拼接用户传入数据,整个过程没有任何检测、过滤,造成注入
最后返回 SQL 语句为 SELECT * FROM `users` WHERE ( `username` yq1ng )
,所以poc可以构造 ) union select updatexml(1,concat(0x7,user(),0x7e),1)#
进行报错注入(注意 urlencode)
漏洞修复
暂未修复,因为官方觉得这是他们提供的功能
NOT LIKE注入(四)
漏洞概述
这个漏洞的主要原因就是官方在过滤危险关键字时漏掉了NOT LIKE
,且Builder.php!parseWhereItem()
的操作符是可以让用户控制的,这俩个地方导致了 sql 注入
漏洞影响版本: ThinkPHP=5.0.10
漏洞利用
POC:http://127.0.0.1/PHPSec/tpdemo/public/index.php/index/index?username[0]=not%20like&username[1][0]=%%&username[1][1]=233&username[2]=)%20union%20select%201,user()%23
漏洞分析 & 修复
测试版本:5.0.10
在 v5.0.11 中提到更新一个安全问题,去 commit 看看,发现 改进Request类filterExp方法这个commit 增加了 NOT LIKE
过滤
直接分析 POC ,在自定义代码处打上断点,由于本次修复是全局过滤,所以本次需打两个断点,看看未修复的Requests.php
如何工作的,也方便跳过许多无用函数
首先是get()
方法,重点不在这,跟进后面的input()
input()
前面是一些解析操作,到 1003 行会将数据交给 filterValue
进行处理,跟进看看。只是递归过滤传入参数,方法最后将数据交给 filterExp()
方法进行下一步过滤,本次过滤将大部分危险关键字给 ban 掉了,但是NOT LIKE
并未在其中,这也就有机可乘了
然后按下 F9 回到 index.php
中,跟着 where 走一下,和上一个一样,跟到 parseWhereItem()
方法,其 324 行有检测操作符的操作,这个操作字符可以有用户来定义
接着还是构造 whereStr
这里,直接拼接了我们传入的恶意sql语句
然后返回给 buildWhere()
,判断返回是否为空后也是直接拼接,这就造成了 sql 注入
一个思考
既然 5.0.10 可以这么玩,那 < 5.0.10 版本的呢,经过尝试并不行,因为老版本没有not like
只有notlike
order注入(五)
漏洞概述
Builder 类的 parseOrder 方法未对用户输入数据进行过滤造成 sql 注入
漏洞影响版本: 5.1.16<=ThinkPHP5<=5.1.22
漏洞利用
POC:http://127.0.0.1/PHPSec/tpdemo/public/index.php/index/index?orderby[id`|updatexml(1,concat(0x7,user(),0x7e),1)%23]=1
漏洞分析 & 修复
测试版本: 5.1.22
看着 composer.json 中的 tp 版本,自己也去 gayhub 找了对应的漏洞位置,感觉这是最大的收获了,可以自己从 releases 到 commit 再找到此版本的漏洞
在 v5.1.23 版本的 update 中写道 改进order
方法的数组方式解析,增强安全性
然后在 commit 中找到 改进order方法解析,此此漏洞的修复是检测 )
和 #
是否出现过
跟着 POC Debug一下,上一个说的全局过滤在此并未奏效,因为它只过滤了值,并未过滤键。。。
<?php
# application/index/controller/Index.php
namespace app\index\controller;
class Index
{
public function index()
{
$orderby = request()->get('orderby');
$result = db('users')->where(['username' => 'mochazz'])->order($orderby)->find();
var_dump($result);
}
}
一路来到 Query.php
的 find()
方法(所以说标题随意起的,哈哈哈,order方法直接跳过了,没啥用),在 3041 行看到熟悉的 connection
,在第二个注入分析中遇到过,其会调用 Builder.php
内的方法
Builder
还是老样子,用str_replace()
填充 sql 语句。看 commit 是改进 order 方法,那就跟 parseOrder
经过一些判断来到 parseKey
,注意最后一个参数为 true
,跟进去
由于传入的 poc 有 反引号 ,strict
为 true
,所以可以过 if 判断,直接拼接,而其前后添加的 反引号 也正好闭合了 poc 构造的 sql 语句,实则巧妙
接着返回给 parseOrder()
,前面加上 ORDER BY
再返回
最后 sql 语句为 : SELECT * FROM `users` WHERE `username` = :where_AND_username ORDER BY `id`|updatexml(1,concat(0x7,user(),0x7e),1)#` LIMIT 1
,产生报错注入
max注入(六)
漏洞概述
Mysql 聚合方法没有对数据进行过滤导致注入
漏洞影响版本: 5.0.0<=ThinkPHP<=5.0.21 、 5.1.3<=ThinkPHP5<=5.1.25
漏洞利用
5.0.0~5.0.21 、 5.1.3~5.1.10 : id)%2bupdatexml(1,concat(0x7,user(),0x7e),1) from users%23
5.1.11~5.1.25 : id`)%2bupdatexml(1,concat(0x7,user(),0x7e),1) from users%23
POC:http://127.0.0.1/PHPSec/tpdemo/public/index.php/index/index?options=id`)%2bupdatexml(1,concat(0x7,user(),0x7e),1)%20from%20users%23
漏洞分析 & 修复
测试版本:5.1.25
查看 5.1.26 releases 说明,看到包含了一个安全更新,接着查看其 commit ,但是本次安全更新未说明那里的问题,commit 太多我也没找到,看师傅的文章是在 改进mysql驱动和sqlsrv驱动
127 行的变动应该是为了过 parseKey()
中的 if 语句吧,下面又多了字符检测,只能有 字母、点、星 ,否则直接抛异常
上 poc ,max 处打上断点
max 调用了聚合查询:aggregate('MAX',...)
,然后 Query.php
的 aggregate('MAX',...)
又调用了 Connection.php
的 aggregate('MAX',...)
去得到某个字段的值
然后来到 Mysql.php
的 parseKey()
(解释一下,看看 Mysql 的类,他是继承 Builder 的),还是添加反引号,返回到 Connection.php
里面,接着往下走
在 value()
方法的1270行对传入字符串进行分割$field = array_map('trim', explode(',', $field));
,接着调用 $this->builder->select($query)
生成 sql 语句,这个 select 不陌生了吧,填充语句的
这个 parseKey()
只是对表明和字段名进行处理,不会影响我们的 payload,处理完后再用逗号将传入语句拼接起来(214行),没啥过滤
然后生成 sql 语句:SELECT MAX(`id`)+updatexml(1,concat(0x7,user(),0x7e),1) from users#`) AS tp_max FROM `users` LIMIT 1
修复就是最开始的,传入数据只能包含 字母、点、星,否则抛出异常
为什么 payload 有差异
老版本调用 paserKey()
的时候没有传入 true
,导致在这个方法中不能在对传入数据两端加上反引号
总结
注入暂时就分析到这里了,PHP分析下来觉得比Java轻松多了,没有各种封装,没有各种api,直接一撸到底