php 前端框架-ThinkPHP框架代码审计

2023-09-02 0 3,962 百度已收录

第一颗子弹| 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指南完成了漏洞审核。 其实文章中也有不妥的地方,希望大家指正。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 php php 前端框架-ThinkPHP框架代码审计 https://www.wkzy.net/game/190921.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务