第一颗子弹| ThinkPHP3.x/5.x框架的任意文件都包含ThinkPHP的介绍
ThinkPHP是一款快速、兼容、简单的轻量级国产PHP开发框架。 它诞生于2006年初,原名FCS,将于2007年春节更名为ThinkPHP,将在Apache2开源合约下发布,移植自Struts结构并改进建立。 同时还借鉴了美国许多优秀的框架和模式,采用面向对象的开发结构和MVC模式,融合了Struts和TagLib(标签库)、RoR ORM映射和ActiveRecord模式的思想。
漏洞描述
ThinkPHP在加载模板和解析变量时存在变量覆盖问题,没有对$cacheFile进行相应的清理,导致模板文件的路径被覆盖,导致任意文件包含漏洞的发生。
环境计划
下载thinkphp框架,在“thinkphplibrarythinkController.php”文件中写入以下代码:
在“applicationindexviewindex”目录下构建index.html文件。 如果不完美的话,模板文件不存在就会出错。
至于其他配置数据库连接文件和thinkphp框架结构请参考文章:代码审计| ThinkPHP5.0.x框架SQL注入
漏洞分析
攻击者可以通过POST方法访问该链接:
,
POST 数据为:cacheFile=../phpinfo.php。 这个cacheFile变量是一个可以被攻击者覆盖的变量。
一开始,程序会调用“thinkphplibrarythinkController.php”文件中的assign方法,并传入POST链表数据。 分配方法的代码如下:
allocate方法调用了视图类中的assign方法,而assign方法在“thinkphplibrarythinkView.php”文件中。 可以看到这个方法使用了array_merge方法将POST链表数据合并到$this->data中。 代码如下:
我们再看一下fetch方法。 该方法用于输出模板内容。 代码定义在“thinkphplibrarythinkController.php”文件中。 该方法还调用视图类中的fetch方法。 代码如下:
检查“thinkphplibrarythinkView.php”文件中的fetch方法。 注意这里的代码 $this->engine->$method($template,$vars,$config);
默认情况下,$method的值为fetch,表示调用视图引擎中的fetch方法。 该方法位于“thinkphplibrarythinkviewdriverThink.php”文件中。 代码如下:
视图引擎的fetch方法调用模板类的fetch方法,继续跟进。 我们注意到“thinkphplibrarythinkTemplate.php”文件中的一句话:$this->storage->read($cacheFile,$this->data); 这个是用来读取编译存储的,此时 $cacheFile 的值类似于runtimetempmd5(×××).php
我们跟进read方法。 在“thinkphplibrarythinktemplatedriverFile.php”文件中,我们看到程序使用了extract方法,并使用了EXTR_OVERWRITE参数。 该参数的作用是:如果有冲突则覆盖掉。 现有的变量。 extract方法将$vars链表中的数据元素注册为变量,而$vars字段包含POST链表数据,这就是变量覆盖的原因。 然后该程序在最后包含 $cacheFile 文件,最终导致任意文件包含。 具体代码如下:
如果目标站点启用了allow_url_include,攻击者甚至可以执行任意代码。 攻击方法如下:
第二弹| ThinkPHP3.2.x框架SQL注入漏洞说明
虽然ThinkPHP3.2.x使用了I方法来过滤参数,但过滤不严格,导致SQL注入。
ThinkPHP 基础知识
在进行漏洞分析之前,我们需要先了解一下ThinkPHP3.2的基础知识,这里仅介绍对本次漏洞分析有帮助的部分。
ThinkPHP3.2的目录结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
├─ThinkPHP 框架系统目录(可以部署在非web目录下面)
│ ├─Common 核心公共函数目录
│ ├─Conf 核心配置目录
│ ├─Lang 核心语言包目录
│ ├─Library 框架类库目录
│ │ ├─Think 核心Think类库包目录
│ │ ├─Behavior 行为类库目录
│ │ ├─Org Org类库包目录
│ │ ├─Vendor 第三方类库目录
│ │ ├─ ... 更多类库目录
│ ├─Mode 框架应用模式目录
│ ├─Tpl 系统模板目录
│ ├─LICENSE.txt 框架授权协议文件
│ ├─logo.png 框架LOGO文件
│ ├─README.txt 框架README文件
│ └─ThinkPHP.php 框架入口文件
这次我们的有效负载是: [0]=bind&username[1]=0andupdatexml(1,concat(0x7,user(),0x7e),1)
环境搭建
这里我们使用ThinkPHP3.2.3完整版来进行实验,下载地址:
我们首先安装phpstudy,然后解压下载的ThinkPHP3.2.3完整版,将上面的thinkphp复制到phpstudy网站根目录的thinkphp32文件夹中。 这里的php版本是5.6。 在thinkphp32目录中创建一个新的index.php文件,内容如下:
1
2
3
4
5
<?php
define("APP_PATH","./Application/");
define('APP_DEBUG',True);
include "ThinkPHP/ThinkPHP.php";
浏览器访问会在thinkphp32目录下生成一个Application文件夹。 目录结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Application
├─Common 应用公共模块
│ ├─Common 应用公共函数目录
│ └─Conf 应用公共配置文件目录
├─Home 默认生成的Home模块
│ ├─Conf 模块配置文件目录
│ ├─Common 模块函数公共目录
│ ├─Controller 模块控制器目录
│ ├─Model 模块模型目录
│ └─View 模块视图文件目录
├─Runtime 运行时目录
│ ├─Cache 模版缓存目录
│ ├─Data 数据目录
│ ├─Logs 日志目录
│ └─Temp 缓存目录
更改thinkphp32ApplicationHomeControllerIndexController.class.php文件代码如下:
1
2
3
4
5
6
7
8
9
10
<?php
namespace HomeController;
use ThinkController;
class IndexController extends Controller {
public function index(){
$condition["name"] = I("name");
$data["pass"] = "1998";
$result = M("users")->where($condition)->save($data);
}
}
配置数据库连接的文件ApplicationCommonConfconfig.php包含以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
<?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新增
);
漏洞分析
这次我们依然采用前向审计的方式,根据payload进行本次审计。 首先我们看一下我们创建的thinkphp32ApplicationHomeControllerIndexController.class.php文件代码。 程序使用I方法安全获取username变量,如右图:
我们按照I方法来看一下。 I 方法位于 thinkphp32ThinkPHPCommonfunctions.php 文件中。 可以看到$input变量的值等于超级全局数据$_GET的值。 如右图所示:
然后主要使用htmlspecialchars()对获取到的变量进行过滤,如右图:
可以看到函数最后使用了thinkphp的自定义过滤方法think_filter,如右图:
该文件中也定义了该方法,代码如下。 可以清楚地看到bind没有被过滤,如右图:
1
2
3
4
5
6
7
function think_filter(&$value){
// TODO 其他安全过滤
// 过滤查询特殊字符
if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value)){
$value .= ' ';
}
}
回到thinkphp32ApplicationHomeControllerIndexController.class.php文件,看这句话$result=M("users")->where($condition)->save($data); 我们按照M方式,在M方式下,新建一个ThinkModel类,如右图:
然后调用ThinkModel类的where方法,该方法位于thinkphp32ThinkPHPLibraryThinkModel.class.php文件中。 这其实就是添加$options['where']的操作php 前端框架,如右图。 :
我们继续看save方法,程序最后有一个更新操作,继续跟进
更新方法位于thinkphp32ThinkPHPLibraryThinkDbDriver.class.php文件中。 parseSet方法主要是set[0]=password=:0。 我们主要关注parseWhere方法。
我们跟踪parseWhere方法,发现parseWhereItem方法处理的句子是拼接在一起的,如右图:
当我们跟进parseWhereItem方法时,我们会发现当$val[0]等于bind时,直接拼接参数,如右图:
最后执行攻击者构造的SQL语句效果如下:
总结
这也是笔者第一次审核Thinkphp3.2框架。 在审核这个框架之前,我也在网上找了一个视频来快速上手。 然后,结合Thinkphp3.2指南,我完成了漏洞审核。 其实文章中也有不妥的地方,希望大家指正。
第三颗子弹 | ThinkPHP5.0.x框架SQL注入漏洞详解
虽然ThinkPHP5.0.x框架采用参数化查询的方式来操作数据库,并且在insert和update方法中,传入的参数是可控的,没有经过严格的过滤,最终导致了这个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为:[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方式对数据库进行查询
环境搭建
了解了基础知识后,我们就可以开始搭建环境了。 这里我们使用ThinkPHP5.0.14版本来进行实验,下载地址:
我们首先安装了phpstudy,然后将下载的ThinkPHP5.0.14解压到phpstudy网站的根目录下。 安装ThinkPHP5.0.14需要三个插件:Mbstring、PDO、Curl。 这里的PHP版本是5.6。
然后我们需要配置该文件来连接数据库并启用thinkphp的调试功能。 在此之前,您需要在数据库中创建用于测试的数据。 比如这里我使用thinkphp作为数据库名,所以在mysql命令行执行createdatabasethinkphp; 然后建立一个用户表,列名包括id、用户名、密码,执行createtableusers(idintauto_incrementprimarykey,usernamevarchar(20),passwordvarchar(30));。 最后,我们向表中插入测试数据,并在命令行中执行insertintousers(id,username,password)values(1,"test","thinkphp"); 即使测试数据创建成功。
配置文件连接数据库,并开启thinkphp的debug功能,如右图:
最后,更改文件applicationindexcontrollerIndex.php的内容,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
namespace appindexcontroller;
use thinkDb;
class Index
{
public function index()
{
$name = input("get.name/a");
Db::table("users")->where(["id"=>1])->insert(["username"=>$name]);
return "ThinkPHP SQL Test.";
}
}
进行更改后,访问我们的有效负载可能会触发该漏洞。
漏洞分析
首先我们知道insert方法存在漏洞,所以检查一下insert方法的具体实现。 该方法位于thinkphplibrarythinkdbBuilder.php 文件中。 我们可以看到,在函数的开头调用了parseData方法,并将$data作为参数传入。 $data的值是通过get方法传入的。 一个链表类型的数据php 前端框架,如右图:
我们跟进parseData方法,该方法也在thinkphplibrarythinkdbBuilder.php文件中。 可以看到,最后有一个switch语句,但是输入该语句后,会跳转到case 'inc'。 这里的关键是看$this->parseKey是否过滤了$val[1]变量。 好吧,由于 $val[1] 变量是我们有效负载中的 updatexml(1,concat(0x7,user(),0x7e),1) ,如右图所示:
继续跟进parseKey方法,你会发现传入的$key没有经过任何过滤就直接返回了。
我们回到原来的插入技术,添加调试语句,看看此时的sql语句是什么样子的,如右图所示:
另外一个更新函数的注入和这个insert类似,这里不再赘述。
总结
这也是笔者第一次审核Thinkphp框架。 在审核这个框架之前,我也在网上找了一个视频快速入门,然后结合Thinkphp5.0指南完成了漏洞审核。 其实文章中也有不妥的地方,希望大家指正。