ThinkPHP简介

ThinkPHP 是一个免费开源的,快速、简单的面向对象的 轻量级PHP开发框架 ,创立于2006年初,遵循Apache2开源协议发布,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。ThinkPHP从诞生以来一直秉承简洁实用的设计原则,在保持出色的性能和至简的代码的同时,也注重易用性。并且拥有众多的原创功能和特性,在社区团队的积极参与下,在易用性、扩展性和性能方面不断优化和改进,已经成长为国内最领先和最具影响力的WEB应用开发框架,众多的典型案例确保可以稳定用于商业以及门户级的开发。

漏洞简述

尽管ThinkPHP 5.0.x框架采用了参数化查询方式,来操作数据库,但是在 insertupdate 方法中,传入的参数可控,且无严格过滤,最终导致本次SQL注入漏洞发生。

ThinkPHP基础知识

在进行漏洞分析之前,我们需要了解一下ThinkPHP基础知识,这里仅介绍对本次漏洞分析有帮助的部分。

ThinkPHP5.0的 目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
thinkphp  应用部署目录
├─application 应用目录(可设置)
│ ├─common 公共模块目录(可更改)
│ ├─index 模块目录(可更改)
│ │ ├─config.php 模块配置文件
│ │ ├─common.php 模块函数文件
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ ├─view 视图目录
│ │ └─ ... 更多类库目录
│ ├─command.php 命令行工具配置文件
│ ├─common.php 应用公共(函数)文件
│ ├─config.php 应用(公共)配置文件
│ ├─database.php 数据库配置文件
│ ├─tags.php 应用行为扩展定义文件
│ └─route.php 路由配置文件
├─extend 扩展类库目录(可定义)
├─public WEB 部署目录(对外访问目录)
│ ├─static 静态资源存放目录(css,js,image)
│ ├─index.php 应用入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于 apache 的重写
├─runtime 应用的运行时目录(可写,可设置)
├─vendor 第三方类库目录(Composer
├─thinkphp 框架系统目录
│ ├─lang 语言包目录
│ ├─library 框架核心类库目录
│ │ ├─think Think 类库包目录
│ │ └─traits 系统 Traits 目录
│ ├─tpl 系统模板目录
│ ├─.htaccess 用于 apache 的重写
│ ├─.travis.yml CI 定义文件
│ ├─base.php 基础定义文件
│ ├─composer.json composer 定义文件
│ ├─console.php 控制台入口文件
│ ├─convention.php 惯例配置文件
│ ├─helper.php 助手函数文件(可选)
│ ├─LICENSE.txt 授权说明文件
│ ├─phpunit.xml 单元测试配置文件
│ ├─README.md README 文件
│ └─start.php 框架引导文件
├─build.php 自动生成定义文件(参考)
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件

我们本次的 payload 为:http://localhost/thinkphp/public/index.php/index/index/index?name[0]=inc&name[1]=updatexml(1,concat(0x7,user(),0x7e),1)&name[2]=1 ,解释如下:

1
2
http://localhost/thinkphp/  public/        index.php/   index/   index/   index
域名 网站目录 对外访问目录 入口文件 前台 控制器 方法名

变量获取

1
2
$name = input("get.name/a");
input()为TP框架的助手函数,get.name/a 表示获取get传入的name变量,并将其强制转换为数组类型

数据库查询

1
2
Db::table("users")->where(["id"=>1])->insert(["username"=>$name]);
TP框架采用的是PDO方式对数据库进行查询

环境搭建

在了解了基本知识后,我们可以开始搭建环境。这里我们使用ThinkPHP 5.0.14版本来进行实验,下载地址:http://www.thinkphp.cn/download/1107.html

我们先安装好phpstudy,然后将下载好的ThinkPHP 5.0.14解压至phpstudy的网站根目录下,安装ThinkPHP 5.0.14需要Mbstring、PDO、Curl三个插件,php版本这里用5.6。

1

接着我们需要配置连接数据库的文件,并开启thinkphp的调试功能。在此之前,你需要先在数据库中创建用于测试的数据,例如这里我用thinkphp作为数据库名,那么就在mysql命令行下执行create database thinkphp; 然后在建立一个users表,列名有id,username,password,执行create table users(id int auto_increment primary key,username varchar(20),password varchar(30)); 即可,最后我们往表中插入测试数据,命令行执行insert into users(id,username,password) values(1,"test","thinkphp"); 这样就算测试数据创建成功了。

2

配置连接数据库的文件,并开启thinkphp的调试功能,如下图:

3

最后修改文件 application\index\controller\Index.php 的内容,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
namespace app\index\controller;
use think\Db;

class Index
{
public function index()
{
$name = input("get.name/a");
Db::table("users")->where(["id"=>1])->insert(["username"=>$name]);
return "ThinkPHP SQL Test.";
}
}

修改好后,访问我们的payload就可以触发漏洞了。

4

漏洞分析

首先,我们知道 insert 方法存在漏洞,那就查看 insert 方法的具体实现。该方法位于 thinkphp\library\think\db\Builder.php 文件中,我们可以看到在函数开头调用了 parseData 方法,并将 $data 作为参数传入, $data 的值是我们通过 get方式传入的一个数组类型的数据,如下图:

5

我们跟进 parseData 方法,该方法也在 thinkphp\library\think\db\Builder.php 文件中。可以看到,在结尾处有个switch语句,而且进入该语句后,会跳到case 'inc'的地方,这里关键就是看看 $this->parseKey 是否有对 $val[1] 变量进行过滤了,因为 $val[1] 变量就是我们payload中的updatexml(1,concat(0x7,user(),0x7e),1) ,如下图:

6

继续跟进 parseKey 方法,会发现直接将传入的 $key 返回了,没有进行任何过滤。

7

我们再回到最开始的 insert 方法,加上调试语句,看看此时的sql语句变成了什么样子,如下图:

8

另一处update函数的注入与这个insert是类似的,这里就不在赘述。

总结

笔者也是第一次审计Thinkphp框架,在审计这套框架前还找了网络上的视频快速入门了下,再结合Thinkphp5.0手册,完成此次漏洞的审计。当然,文章有不当之处,还希望大家斧正。

相关文章

敏信:审计系列之THINKPHP开发框架

先知:ThinkPHP框架 5.0.x sql注入漏洞分析

绿盟:ThinkPHP 5.0版本 SQL注入漏洞分析

安恒:听说thinkphp又出事了?

i春秋:ThinkPHP框架 < 5.0.16 sql注入漏洞分析

ThinkPHP5.0完全开发手册:输入变量

ThinkPHP5.0完全开发手册:数据库更新数据

ThinkPHP5.0框架开发视频教程