一、环境配置
-
源码:thinkphp-3.2.3
-
phpstudy中开启php的xdebug插件
-
vscode配置php环境,如下,在vscode中搜索php,找到php的Executable Path,点击下面的在setting.json中编辑
-
在setting.json中填写php的可执行文件路径
-
在vscode的运行和调试中打开launch.json,类型选择php,进入如下界面,将xdebug的端口修改为phpstudy中设置的端口
-
修改php.ini中的xdebug设置如下
-
修改thinkphp的数据库配置,配置文件路径为ThinkPHP/Conf/convention.php
-
新建一个数据库tp3.2.3,在库中新建一个表,注意这个表得和sql查询入口中写的保持一致,如下
-
在Application目录下新建一个/Home/Controller/IndexController.class.php文件,写一个查询入口,如下。然后使用phpstudy和thinkphp3.2.3的源码在本地搭建一个网站,至此thinkphp 5.2.3的sql注入审计环境配置完成
namespace HomeController;
use ThinkController;
class IndexController extends Controller {
public function index(){
highlight_file(__FILE__);
$data = M('users')->find(I('GET.id'));
var_dump($data);
}
}
二、漏洞分析
-
打开上面写好的sql查询入口,出现如下界面即代表配置无误
-
vscode中设置断点,如下
-
浏览器中访问sql查询入口http://localhost:81/index.php/home/index/index?id=1,单步调试后到这个地方,开始传入get方法的id参数
-
跟踪到这里,发现了filter过滤,filter使用的是默认的DEFAULT_FILTER,其值为htmlspecialchars,该函数把一些预定义的字符转换为 HTML 实体,预定义的字符是:
-
& (和号)成为
&
; -
" (双引号)成为
"
; -
' (单引号)成为 '
-
< (小于)成为
<
-
>
(大于)成为>
; -
继续往下存在一处参数过滤,array_walk_recursive() 函数对数组中的每个元素应用用户自定义函数,data参数为接受的用户输入参数,使用自定义的think_filter对data数据进行了过滤
-
全局搜索think_filter,发现过滤了如下关键字,会置换为空
EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN
-
继续往下,又发现一处判断
-
进入这个函数,代码如下,会对表达式进行判断,根据表达式的不同会进入不同的if
protected function _parseOptions($options = array())
{
if (is_array($options)) {
$options = array_merge($this->options, $options);
}
if (!isset($options['table'])) {
// 自动获取表名
$options['table'] = $this->getTableName();
$fields = $this->fields;
} else {
// 指定数据表 则重新获取字段列表 但不支持类型检测
$fields = $this->getDbFields();
}
// 数据表别名
if (!empty($options['alias'])) {
$options['table'] .= ' ' . $options['alias'];
}
// 记录操作的模型名称
$options['model'] = $this->name;
// 字段类型验证
if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) {
// 对数组查询条件进行字段类型检查
foreach ($options['where'] as $key => $val) {
$key = trim($key);
if (in_array($key, $fields, true)) {
if (is_scalar($val)) {
$this->_parseType($options['where'], $key);
}
} elseif (!is_numeric($key) && '_' != substr($key, 0, 1) && false === strpos($key, '.') && false === strpos($key, '(') && false === strpos($key, '|') && false === strpos($key, '&')) {
if (!empty($this->options['strict'])) {
E(L('_ERROR_QUERY_EXPRESS_') . ':[' . $key . '=>' . $val . ']');
}
unset($options['where'][$key]);
}
}
}
// 查询过后清空sql表达式组装 避免影响下次查询
$this->options = array();
// 表达式过滤
$this->_options_filter($options);
return $options;
}
-
由于上面跟踪发现的过滤器会将常见的报错符号过滤掉,所以这里使用单引号肯定是不行的,先构造一条payload
id=1 union select 1,database(),3#
-
继续往下跟,进入_parseType函数,这里会对参数的类型进行检测,如果不是数字型的就会强制转换,也就是说上面构造的payload是无法绕过这里的。
-
修改payload为数组,如下,重新断点调试,跟踪后发现成功绕过了上面的if判断,进入到了select函数
id[where]=1 union select 1,database(),3
-
然后又从buildSelectSql()函数进入到了parseSql()函数
-
进入到parseSql()函数
-
继续往下跟,又发现一处过滤
-
继续往下跟,进入到了parseWhere()函数
-
继续往下看,进入parseWhereItem函数,没有特别值得关注的
-
再往下,进入parseValue函数,这里就是对查询结果进行处理了
-
最后跟完全部过程,发现执行成功,但是获取到的是id=1的用户数据,而不是数据库名
-
继续修改payload如下,断点跟踪,发现注入成功,获取到了数据库名tp3.2.3
id[where]=id=-1 union select 1,database(),3
原文始发于微信公众号(菜鸟的渗透测试之路):代码审计:thinkphp3.2.3sql注入漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论