[PHP代码审计]TP5漏洞--Sql注入分析


试着审计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.155.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

image-20210723144725453

漏洞分析

测试版本:5.0.15

在自定代码处打上断点进行调试

image-20210723144849867

application/index/controller/Index.php的第2085行调用父类的insert方法,跟进

image-20210723145323734

继续跟

image-20210723145612046

thinkphp/library/think/db/Builder.phpparseData()方法中,当 var[0] 为 inc 时(也就是poc的 username[0] ),直接对参数进行了拼接,其中没有任何检测、过滤

image-20210723145722205

而后在thinkphp/library/think/db/Builder.phpinsert()方法中虽然有str_replace()但是其只是简单替换,最终sql语句会造成注入

image-20210723150156428

image-20210723151036388

漏洞修复

改进inc/dec查询

image-20210723154116962

并未修复 exp,为什么?将poc的 username[0] 改为 exp 跟进试试。

thinkphp/library/think/Request.php处将 exp 替换为 exp 多了个空格,自然匹配不到,也就不用修了。。。

image-20210723154854472

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

image-20210723171832586

漏洞修复 & 分析

测试版本:5.1.7

tp 5.1.8更新时有个改进mysql驱动,删除了很多东西

image-20210724170424884

看起来又是直接拼接呢,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

image-20210724190304457

然后是builder.php

image-20210724190709766

跟进parseDate()方法

image-20210724190731547

parseDate()方法的最后出现了官方删除的段代码,跟进去

image-20210724190815070

username[0]为 point 时直接拼接了传入的参数,也是没有过滤

image-20210724191348152

后面就和第一次一样了,没有检测、过滤

至于 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

image-20210727133303551

漏洞分析

测试版本:5.0.10

先不用 poc 的,传一个正常的字符串进去看看最后的语句是什么样的,再去思考 poc 是怎么构造的 http://127.0.0.1/PHPSec/tpdemo/public/index.php/index/index?username=yq1ng

image-20210727134340488

接着进入Query.php,跟进 parseWhereExp(),此处 形参的 op 为 exp ,这是手工构造的,后面有用

image-20210727134454198

parseWhereExp跟进来这个方法后发现就是分析查询表达式,没啥用,跳过。来到Query.phpselect()方法,跟进这个生成 SQL 语句的方法

image-20210727134828155

进来后发现很多方法,问题在parseWhere()这里,跟进去

image-20210727134921700

可以看出来第一句代码为构造 where ,我们开始构造的 index.php 就是 where查询,继续跟

image-20210727135002164

然后 buildWhere()方法走到 parseWhereItem()这里,再跟进去,上面的可以跳过

image-20210727135615264

然后跳到构造 whereStr 字符串这里,从349行开始,当 op(在此处也就是$exp) 为 EXP 时,会直接拼接用户传入数据,整个过程没有任何检测、过滤,造成注入

image-20210727135725975

image-20210727135936009

最后返回 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

image-20210727153524785

漏洞分析 & 修复

测试版本:5.0.10

v5.0.11 中提到更新一个安全问题,去 commit 看看,发现 改进Request类filterExp方法这个commit 增加了 NOT LIKE过滤

image-20210727144700622

直接分析 POC ,在自定义代码处打上断点,由于本次修复是全局过滤,所以本次需打两个断点,看看未修复的Requests.php如何工作的,也方便跳过许多无用函数

image-20210727150413090

首先是get()方法,重点不在这,跟进后面的input()

image-20210727150553184

input()前面是一些解析操作,到 1003 行会将数据交给 filterValue 进行处理,跟进看看。只是递归过滤传入参数,方法最后将数据交给 filterExp() 方法进行下一步过滤,本次过滤将大部分危险关键字给 ban 掉了,但是NOT LIKE并未在其中,这也就有机可乘了

image-20210727151716397

然后按下 F9 回到 index.php 中,跟着 where 走一下,和上一个一样,跟到 parseWhereItem() 方法,其 324 行有检测操作符的操作,这个操作字符可以有用户来定义

image-20210727152443893

接着还是构造 whereStr这里,直接拼接了我们传入的恶意sql语句

image-20210727152712365

然后返回给 buildWhere() ,判断返回是否为空后也是直接拼接,这就造成了 sql 注入

image-20210727153127349

引自七月火师傅

总结

一个思考

既然 5.0.10 可以这么玩,那 < 5.0.10 版本的呢,经过尝试并不行,因为老版本没有not like只有notlike

image-20210727160000232

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

image-20210727170407005

漏洞分析 & 修复

测试版本: 5.1.22

看着 composer.json 中的 tp 版本,自己也去 gayhub 找了对应的漏洞位置,感觉这是最大的收获了,可以自己从 releases 到 commit 再找到此版本的漏洞

v5.1.23 版本的 update 中写道 改进order方法的数组方式解析,增强安全性

image-20210727170229044

然后在 commit 中找到 改进order方法解析,此此漏洞的修复是检测 )# 是否出现过

image-20210727170331572

跟着 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.phpfind()方法(所以说标题随意起的,哈哈哈,order方法直接跳过了,没啥用),在 3041 行看到熟悉的 connection,在第二个注入分析中遇到过,其会调用 Builder.php 内的方法

image-20210727172315902

image-20210727172423755

Builder还是老样子,用str_replace()填充 sql 语句。看 commit 是改进 order 方法,那就跟 parseOrder

image-20210727172522131

经过一些判断来到 parseKey,注意最后一个参数为 true ,跟进去

image-20210727172729072

由于传入的 poc 有 反引号 ,stricttrue,所以可以过 if 判断,直接拼接,而其前后添加的 反引号 也正好闭合了 poc 构造的 sql 语句,实则巧妙

image-20210727172939988

接着返回给 parseOrder() ,前面加上 ORDER BY再返回

image-20210727173631803

最后 sql 语句为 : SELECT * FROM `users` WHERE `username` = :where_AND_username ORDER BY `id`|updatexml(1,concat(0x7,user(),0x7e),1)#` LIMIT 1 ,产生报错注入

image-20210727173839838

引自七月火师傅

总结

max注入(六)

漏洞概述

Mysql 聚合方法没有对数据进行过滤导致注入

漏洞影响版本: 5.0.0<=ThinkPHP<=5.0.215.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

image-20210728094317538

漏洞分析 & 修复

测试版本:5.1.25

查看 5.1.26 releases 说明,看到包含了一个安全更新,接着查看其 commit ,但是本次安全更新未说明那里的问题,commit 太多我也没找到,看师傅的文章是在 改进mysql驱动和sqlsrv驱动

image-20210728094547245

image-20210728094935075

127 行的变动应该是为了过 parseKey()中的 if 语句吧,下面又多了字符检测,只能有 字母、点、星 ,否则直接抛异常

上 poc ,max 处打上断点

image-20210728095318163

max 调用了聚合查询:aggregate('MAX',...),然后 Query.phpaggregate('MAX',...) 又调用了 Connection.phpaggregate('MAX',...) 去得到某个字段的值

image-20210728095512122

image-20210728095712574

image-20210728100049756

然后来到 Mysql.phpparseKey() (解释一下,看看 Mysql 的类,他是继承 Builder 的),还是添加反引号,返回到 Connection.php里面,接着往下走

image-20210728101620289

value()方法的1270行对传入字符串进行分割$field = array_map('trim', explode(',', $field));,接着调用 $this->builder->select($query) 生成 sql 语句,这个 select 不陌生了吧,填充语句的

image-20210728101853115

这个 parseKey()只是对表明和字段名进行处理,不会影响我们的 payload,处理完后再用逗号将传入语句拼接起来(214行),没啥过滤

image-20210728102256474

然后生成 sql 语句:SELECT MAX(`id`)+updatexml(1,concat(0x7,user(),0x7e),1) from users#`) AS tp_max FROM `users` LIMIT 1

修复就是最开始的,传入数据只能包含 字母、点、星,否则抛出异常

总结

为什么 payload 有差异

image-20210728104826052

老版本调用 paserKey() 的时候没有传入 true,导致在这个方法中不能在对传入数据两端加上反引号

总结

注入暂时就分析到这里了,PHP分析下来觉得比Java轻松多了,没有各种封装,没有各种api,直接一撸到底


文章作者: yq1ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yq1ng !
评论
 上一篇
[PHP代码审计]TP5漏洞--RCE [PHP代码审计]TP5漏洞--RCE
还是先上七月火师傅的文章:ThinkPHP 环境搭建,按版本自行搭建,测试版本在漏洞分析处,只需修改 composer.json,5.0 和 5.1 不能直接composer # v5.0.x composer create-project
2021-07-28
下一篇 
Java反序列化漏洞(四)--初窥反序列化及Shiro550漏洞(CVE-2016-4437)分析 Java反序列化漏洞(四)--初窥反序列化及Shiro550漏洞(CVE-2016-4437)分析
不涉及太深,大师傅们写的很详细了,没必要再去抄下来了,写上自己的复现过程及理解就行 本篇所有代码及工具均已上传至gayhub:https://github.com/yq1ng/Java [TOC]
2021-06-29
  目录