之前国外的一个水准比较高的比赛,当时没空做,有时间就来复现学习一波,题目提供了源码,也算是一个比较有趣的逻辑注入了。
题目背景
题目代码实现了一个简单的交易系统,每个用户在注册以后,就有 100000000MGC,用户之前可以互相交易。
题目要求
在泄露的数据库文件中,我们发现了有关flag1的sql语句: 因此这个题目第一步就是要能够注入出flag。
代码架构分析
代码使用了编写路由来写逻辑的形式,很像是python开发中的flask框架,这种方式实现起来会比较清晰,对开发者的逻辑处理有比较好的帮助。
代码入口文件
整个代码的入口文件是初始化环境加上所有路由的前置路由实现:
可以看到,默认全局是使用了google的图片验证码,由于是在国内调试,所以我们注释了这部分。
然后我们看一下基本的逻辑:
基本的登陆注册逻辑
这里我们可以只看post的部分,因为get的部分都是渲染前端模板的,post的部分是代码具体逻辑实现:
可以看到注册和登陆逻辑没有什么问题,都实现的比较严谨,如果底层没有出现问题,是可以保证稳定运行的。因为是sql注入,所以我们要尤其关注sql的部分,下面我们跟进底层去看下。
交易的逻辑实现
经过对代码的理解,这个系统其实是做了一个类似于当前流行的区块链的钱包,每个用户在第一次使用的时候,是可以生产一个唯一的钱包地址,如果别人要跟你进行交易,就需要知道你的钱包地址,否则是不能实现的,可以通过下面的图片来理解:
底层分析—发现漏洞
可以看到具体的数据库操作都封装到了$app->db
中,并且使用了绑定变量的预处理方法执行sql语句,那我们就具体去看一下具体的实现:
可以发现刚开始的几个函数是没有问题的,都是直接把预处理给了数据库来执行,但是下面却有一个很特殊的函数:
可以看到这里并没有直接把预处理交给数据库,而是自己实现了,根据ctf中的经验,这里一定有问题:
foreach ($param as $key => $value) {
$search[] = $key;
$replace[] = sprintf("'%s'", mysqli_real_escape_string($this->link, $value));
}
$sql = str_replace($search, $replace, $sql);
乍一看,貌似没有什么问题,但是自己想一下,这里确实是存在问题的:我们来写个demo测试一下:
可以看到如果是正常的绑定变量,确实可以完成功能。
但是如果是这样呢?
可以很明显的看到,这里是可以造成二次绑定变量的,所以按理来说,是可以造成注入的,构造如下:
找到利用点
既然我们知道了这个函数是可以造成注入的,那我们就要梳理一下这个漏洞的利用需要什么?
- 我们需要一个绑定两个参数的sql语句
- 我们需要两个参数都可控
经过简单的浏览,我们发现了可以利用的位置:
这个位置是在插入交易信息的位置,就是说我们只要能注册两个恶意用户名的用户,互相交易,就可以注入出flag。
我们继续将代码抽象出来单独构造:
经过一段时间的测试,发现可以构造如下:
first: ,1000000,100000000,(select(flag1)from(flag1)));#:notes
second: ,1000000,100000000,(select(flag1)from(flag1)));#
这样经过处理我们的sql语句就变成了:
INSERT INTO account (user_id, debit, credit, notes) VALUES (',1000000,100000000,(select(flag1)from(flag1)));#',1000000,100000000,(select(flag1)from(flag1)));#
获取flag
先分别注册用户:
然后使用第一个账号,生成交易token,然后第二个账号验证token,
然后第二个账号向第一个账号发起转账:
然后我们就可以在交易记录中发现flag:
后记
整个流程略显复杂,设计到交易代码的具体实现,可能刚开始看会觉得很乱,但是自己梳理,流程还是非常清晰的。这题主要考了快速发现代码异常,以及构造poc,以及找到合适的利用点几个方面的能力,还是很有意思的,和大家分享一下。