ThinkPHP5 SQL注入漏洞 && 敏感信息泄露【通过】

ThinkPHP5 SQL注入漏洞 && 敏感信息泄露

介绍:

ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架,诞生于2006年初,原名FCS,2007年元旦正式更名为ThinkPHP,遵循Apache2开源协议发布,从Struts结构移植过来并做了改进和完善,同时也借鉴了国外很多优秀的框架和模式,使用面向对象的开发结构和MVC模式,融合了Struts的思想和TagLib(标签库)、RoR的ORM映射和ActiveRecord模式。

Poc : http://172.20.0.3/index.php?ids[0,updatexml(0,concat(0xa,user()),0)]=1

影响: <5.0.9

前言

这是第一篇漏洞复现文章,学了php但是在之前还没有接触过thinkphp框架

一个鸡肋的sql注入漏洞

以下是执行大概流程,一遍看一边调试

开始

先在kali安装vulhub


教程文章

第一次用打开发现出问题了,去网上搜索了半天没发现解决方法,就去安全群问了一下发现是没有启动docker

打开复现环境执行payload

先看漏洞的形成原因,‘控制了预编译SQL语句中的键名’

搭建本地环境用于调试 这里用的demo 是

image

主要分析以上的内容

$ids = input('ids/a');

Ids/a 是将input接受到的数据强行转换成数组

image

$ids值 就是上面浮窗的数组

$t 是以下

image

下断点进入

image

$t调用了where和select 函数,先进入select函数分析 位置在(thinkphp\library\think\db\Query.php)

重点是2306行的生成sql语句 不过在这之前先看个变量后面才好理解

$opthins =

image

分析成了用于方便sql的对象

进入2306行

$sql = $this->builder->select($options);

进入了(thinkphp\library\think\db\Builder.php

会执行637行的代码进入parseWhere函数

224行调用了buildWhere函数

进入243行的buildWhere函数

buildWhere函数中主要有以下重要的(主要是IN查询)

    $bindName = $bindName ?: 'where_' . str_replace(['.', '-'], '_', $field);
    if (preg_match('/\W/', $bindName)) {
        // 处理带非单词字符的字段名
        $bindName = md5($bindName);
    }

} elseif (in_array($exp, ['NOT IN', 'IN'])) {
        // IN 查询
        if ($value instanceof \Closure) {
            $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value);
        } else {
            $value = is_array($value) ? $value : explode(',', $value);
            if (array_key_exists($field, $binds)) {
                $bind  = [];
                $array = [];
                foreach ($value as $k => $v) {
                    if ($this->query->isBind($bindName . '_in_' . $k)) {
                        $bindKey = $bindName . '_in_' . uniqid() . '_' . $k;
                    } else {
                        $bindKey = $bindName . '_in_' . $k;
                    }
                    $bind[$bindKey] = [$v, $bindType];
                    $array[]        = ':' . $bindKey;
                }
                $this->query->bind($bind);
                $zone = implode(',', $array);
            } else {
                $zone = implode(',', $this->parseValue($value, $field));
            }
            $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')';
        }
    } 
    ....
    return $whereStr;
}

一开始他是有对$bindName进行过滤的,但是$value是数组他会用foreach把$bindName和键名进行拼接

image

最后形成

image

导致在编译SQL语句的时候发生错误,从而产生报错

为什么叫鸡肋漏洞呢,查了原因是因为thinkphp用了PDO来连接数据库的

通常,PDO预编译执行过程分三步:

  • prepare($SQL) 编译SQL语句
  • bindValue($param, $value) 将value绑定到param的位置上
  • execute() 执行

我们在第二部的$param那里用报错语句,在执行到第二时直接抛出错误无法执行到第3,
在$SQL中也一样 ,如果$SQL等于报错语句就会直接报错无法执行二和三

还有一个主要原因是thinkphp默认配置PDO::ATTR_EMULATE_PREPARES => false

引用大佬的话:
这个选项涉及到PDO的“预处理”机制:因为不是所有数据库驱动都支持SQL预编译,所以PDO存在“模拟预处理机制”。如果说开启了模拟预处理,那么PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行;

远程执行代码漏洞

Poc:

index?s=index/\think\container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

版本:5.0.5-5.0.22
5.1.0-5.1.30

调试注意要点:因为提前设置了断点导致提前跳转到固定的函数中,导致调试失败参数失败无法运行

复现

下载5.0.22的thinkphp
下载地址:

http://www.thinkphp.cn/down/1260.html
http://localhost/thinkphp_5.0.22_with_extend/public/index.php?s=captcha
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami
_method=__construct&filter[]=system&method=get&get[]=whoami 

这里2个都能达到同样的效果,如果是调试建议第2个

一开始复现的时候以为要demo,结果不用

设置断点进入App.php

image

创建了一个http请求实例

继续往下

来到了115行,这里会进去路由检测传入的参数是,新建的http实例,和配置信息
参数赋值在79和81

进入后发现$request->path()函数

我们进入path函数在 433行

image

这里this是http实例$request调用的
437会进入用this调用进入pathinfo函数

进入后发现会用(isset($_GET[Config::get('var_pathinfo')])) 进行检测

这里会进入,说明一下Config::get('var_pathinfo')是配置文件中的设置的参数,默认值为s,从GET中获取键值这里给的是captcha

image

这里把给this.pathinfo赋值

这里把返回值赋值给$pathinfo , 有赋值给this.path,最后return this.path

继续执routeCheck函数,会在642行进入,check函数

image

在Route.php中第839行进入check

857会调用method函数

image

进入method,

image

在524中的 isset($_POST[Config::get('var_method')]) var_method和上面的一样,配置文件中的设置的参数 获取_method的值,这里是__construct
this->method 赋值大写的__construct,然后用this调用构造函数参数为发送post数据的数组形式

image

进入__construct函数,这里的foreach会遍历所有变量,把http实例中存在的覆盖,不存在的添加 method为get

image

返回method函数,把getthis->method返回,也就是get

image

返回check函数,最后执行并返回

image

image

返回内容

在这调用完成之后routeCheck将返回method赋值给dispatch

image

继续执行run到,138行会执行exec

image

到exec函数里,这里会因为type为method执行相应的分支。

468行,有调用param函数 634

image

突然发现这里的method已经是返回执行whoami后的数据了,

不过不能显示出来
继续执行到了,661 执行了input函数

要执行array_walk_recursive函数

这里用system做参数,$tada为null,执行filterValue
Foreach循环 $

$value值为whoami $key是0 $filters是

image

image

到1083就是一个回调函数,$filter当作函数,$value当作参数执行

完成

首先$filters是一个数组而且至少2个值而且值要是一个函数或者方法才能执行call_user_func函数

往上

这里$data也要是数组才能执行array_walk_recursive函数
这里this要指向一开始创建的http实例,然后$filter要是一个空值

  • 通过
  • 未通过

0 投票者