复现
先搭建thinkphp5.0.9环境
配置下测试环境
然后访问
http://tptest.cc/index.php/index/index/getage?names[0,updatexml(0,concat(0xa,user()),0)]=1
复现成功
【点击获取学习资料】
渗透工具
技术文档、书籍 最新大厂面试题目及答案
视频教程
应急响应笔记
学习思路构图等等
漏洞分析
先看看传进来了什么
是一个关联数组
跟进where
跟进parseWhereExp,看了一番,就是普通的对where参数解析
继续跟进select
Query的select函数从这里开始,构造的sql语句就出问题了
继续跟进builder->select -> parseWhere -> buildWhere -> parseWhereItem
在这里将关联数组的key拼接了上去
正常查询,传参names[]=li,构造出来的应该是这样
生成的预处理sql应该是这样,where_name_in_0会被替换为li
而恶意payload,生成的是
看一下最终生成的sql语句
调试发现,预处理发生了错误,但是报错注入成功,语句被执行了?预处理时执行了sql语句?
想调试下,但是这条语句,进不去,不能调试,,不知道什么原因,
$this->PDOStatement = $this->linkID->prepare($sql);
在网上看到了p神的文章
https://www.leavesongs.com/PENETRATION/thinkphp5-in-sqlinjection.html
就是说在PDO::ATTR_EMULATE_PREPARES => false模式下,预处理是假的,边替换边执行,这就可以解决
释得通了
找一下不能仔细查询的原因
构造payload:
?names[0,updatexml(0,concat(0xa,(select group_concat(SCHEMA_NAME) from information_schema.SCHEMATA )),0)]=1
预处理没有报错
但在$result = $this->PDOStatement->bindValue($param, $val[0], $val[1]);时报错
应该是PDO的预处理,不能解析这一长串
那就从param入手看一下,
param来自$bind
$bind来自上面遇到过的
想绕过就要让$bindKey变为where_name_in_0,,但是构造的sql语句不变,做不到,立刻放弃
令我疑惑的两个点
user()查询是在预处理时报错退出的,子查询是在绑定key-value时退出的
而且使用子查询,PDO是解析成功了的,在mysql日志看到如下
使用user()查询日志:
直接用sql语句查日志
为什么PDO调用user(),在mysql日志看不到记录
搞不懂
总结
thinkphp没有对输入过滤,直接做拼接
众所周知预处理不能处理in,order by这些动态的变量,所以审计的时候可以重点看一下
这个漏洞没什么用,不过可以当个练习了
造成这个漏洞的原因一是thinkphp没有对输入进行过滤,直接将输入拼接。二是由于PDO的特性,在预处理时把代码执行了
如何修复
看5.0.22,已经修复
select生成的语句没有我们构造的内容了
继续调试,bindkey后面不使用key拼接了,而是使用递增的i
这是5.0.9的