跳转到帖子

靶场练习:一次文件上传到 WebShell 落点的完整排查思路

精选回复

发布于

背景

文件上传类题目在 CTF 和靶场里很常见,但很多新手会卡在“能传上去,但访问不到”或“传了 PHP 却不解析”这类细节上。这里整理一个比较通用的解题流程,重点放在上传点探测、后端校验绕过、存储路径判断和 WebShell 落点验证。内容仅面向本地靶场和授权环境。

典型场景:页面提供头像上传或附件上传,只允许图片格式。目标是上传一个可执行脚本,或者利用解析差异拿到代码执行。

技术分析

文件上传题一般不是单点判断,常见限制会叠加出现:

  • 前端校验:通过 JavaScript 判断后缀、MIME、大小。这类校验只能影响浏览器,不可信。
  • 后端后缀校验:黑名单或白名单,比如禁止 .php,只允许 .jpg/.png/.gif。
  • Content-Type 校验:根据请求头里的 Content-Type 判断是否图片。
  • 文件内容校验:检查文件头,如 GIF89a、\x89PNG。
  • 重命名策略:上传后改成时间戳、随机 hash,影响落点定位。
  • 解析策略:Web 容器是否会把某些后缀交给 PHP/ASP/JSP 解析。

解题时不要一开始就盲传一句话木马,建议先确认三个问题:

  1. 上传请求的真实字段名是什么?
  2. 上传后的文件是否可访问?路径如何构造?
  3. 上传目录是否具备脚本解析能力?

操作步骤

1. 抓包确认上传请求

使用 Burp Suite 或浏览器开发者工具抓包,重点观察以下字段:

  • multipart/form-data 中的文件字段名,例如 file、upload、avatar。
  • filename 参数是否保留原文件名。
  • 服务端返回中是否包含保存路径、文件名、URL。
  • 是否存在前端接口和后端接口不一致的情况。

一个典型上传包如下:

POST /upload.php HTTP/1.1
Host: target.local
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryX

------WebKitFormBoundaryX
Content-Disposition: form-data; name="file"; filename="test.jpg"
Content-Type: image/jpeg

GIF89a
hello
------WebKitFormBoundaryX--

如果这个请求能成功,先访问返回的图片地址,确认上传目录。例如返回 /uploads/20250101/test.jpg,说明后续测试可以围绕该目录展开。

2. 区分前端校验和后端校验

如果页面选择 .php 文件时直接提示格式错误,先看是否是前端 JS 限制。可以直接抓一个正常图片上传包,再在 Burp 里修改文件名和内容。

例如把:

filename="a.jpg"
Content-Type: image/jpeg

改成:

filename="a.php"
Content-Type: image/jpeg

如果修改后仍能上传,说明只是前端限制。如果服务端返回“类型不允许”,再继续看后端策略。

3. 测试后缀黑名单

黑名单常见绕过方向包括大小写、复合后缀、特殊解析后缀等,但是否有效取决于服务器配置。

可以按下面顺序低成本测试:

a.php
a.pHp
a.php5
a.phtml
a.php.jpg
a.jpg.php

判断标准不是“上传成功”本身,而是访问后是否执行脚本。例如 PHP 环境可以上传:

<?php echo "upload_test_".md5(123); ?>

访问后如果页面显示 upload_test_202cb962ac59075b964b07152d234b70,才说明被解析执行。若直接下载或显示源码,则只是存储成功。

4. 测试 MIME 和文件头校验

如果服务端检查 Content-Type,请求里把它改成图片类型即可测试:

Content-Type: image/jpeg

如果检查文件头,可以构造图片马。例如 GIF 头相对简单:

GIF89a
<?php echo "ok_".md5(1); ?>

如果后端只通过 getimagesize() 或简单魔数判断,这类文件可能通过。但注意:通过内容校验不代表会执行,仍需要服务器按 PHP 解析该文件。

5. 判断上传目录是否解析脚本

很多靶场会把上传目录配置为禁止脚本执行,这时即使上传 .php 成功,也可能只会下载或返回 403。可以尝试以下判断:

  • 访问 /uploads/a.php 是否返回 PHP 执行结果。
  • 访问图片马 /uploads/a.jpg 是否只显示图片内容。
  • 观察响应头 Content-Type 和状态码。
  • 尝试已知解析后缀,如 .phtml、.php5。

如果所有脚本后缀都不执行,思路应转向目录解析漏洞、包含漏洞组合、配置错误或二次处理绕过,而不是继续盲目换木马。

代码示例

下面是一个用于本地靶场测试上传策略的小脚本。它会批量尝试不同文件名和 MIME,方便快速判断过滤点。使用前确认目标是自己搭建或已授权的环境。

import requests

url = "
field_name = "file"

payload = b'GIF89a\n<?php echo "ctf_".md5(233); ?>'

candidates = [
    ("test.php", "application/octet-stream"),
    ("test.php", "image/jpeg"),
    ("test.phtml", "image/jpeg"),
    ("test.php5", "image/jpeg"),
    ("test.pHp", "image/jpeg"),
    ("test.jpg.php", "image/jpeg"),
    ("test.php.jpg", "image/jpeg"),
    ("test.gif", "image/gif"),
]

for filename, mime in candidates:
    files = {
        field_name: (filename, payload, mime)
    }
    try:
        r = requests.post(url, files=files, timeout=8)
        print("=" * 60)
        print("filename:", filename)
        print("mime:", mime)
        print("status:", r.status_code)
        print("resp:", r.text[:300])
    except requests.RequestException as e:
        print(filename, "error:", e)

如果返回里有文件路径,可以再写一段访问验证。但不同靶场返回格式差异较大,建议先人工确认返回内容,避免脚本误判。

常见组合思路

1. 上传 + 文件包含

如果上传目录不解析 PHP,但站点存在本地文件包含,例如:

/index.php?page=uploads/test.jpg

那么图片马里的 PHP 代码可能通过包含执行。这类题重点不在上传后直接访问,而在找到包含点。

2. 上传 + .htaccess

在 Apache 且允许 .htaccess 生效时,可以尝试上传配置文件改变解析规则。示例:

AddType application/x-httpd-php .jpg

然后再上传 shell.jpg。但现实环境里 AllowOverride 通常关闭,靶场题会明确给出条件或存在可测迹象。

3. 双写后缀与截断问题

老环境或特殊代码中可能出现截断问题,例如对文件名处理不严谨。现代 PHP 版本里经典空字节截断基本不可用,遇到这类题需要结合题目环境版本判断,不建议机械套用。

注意事项

  • 不要只看上传成功。上传成功、可访问、可执行是三个不同阶段。
  • 优先阅读响应。很多靶场会在 JSON 里直接返回 url、path、msg,这是定位落点的关键。
  • 注意随机文件名。如果后端重命名,原文件名绕过后缀可能没有意义,需要看最终保存后缀。
  • 区分 Nginx 和 Apache。解析规则不同,Apache 的 .htaccess 思路不要直接套到 Nginx。
  • 控制测试 payload。靶场验证建议使用 echo md5() 这类无副作用代码,避免破坏环境。
  • 别忽略二次渲染。头像上传常会经过压缩、裁剪,PHP 代码可能被图片库重写清除。

总结

文件上传题的核心不是“背绕过字典”,而是按链路拆解:先确认请求,再定位过滤点,之后判断存储路径和解析行为。比较稳的节奏是:

  1. 正常图片上传,确认路径。
  2. 改包测试前端和后端校验边界。
  3. 分别测试后缀、MIME、文件头。
  4. 访问验证是否执行,而不是只看是否上传成功。
  5. 如果不解析,考虑文件包含、解析配置、二次处理等组合点。

按这个流程走,绝大多数靶场里的文件上传题都能快速缩小范围,避免在无效 payload 上浪费时间。

网络流量与边界分析示意图
网络请求、日志与边界流量分析示意

这个题目里最容易混淆的是“文件上传成功”和“WebShell 可执行”并不是同一个问题。排查时建议把链路拆成四段:上传入口、落盘位置、访问路径、解释器执行。

在靶场里可以先上传一个无害标记文件,而不是直接使用可执行脚本,例如内容只写入固定字符串,然后确认服务端实际保存的位置:

find /var/www -type f -mmin -5 -printf '%TY-%Tm-%Td %TH:%TM %p\n'
grep -R "upload" /var/log/nginx /var/log/apache2 2>/dev/null

重点记录上传接口返回的原始文件名、服务端重命名后的文件名、响应中的路径,以及文件最终所在目录。接着分别验证:

  • 是否可以通过 HTTP 直接访问该文件;
  • 上传目录是否位于 Web 根目录下;
  • 该目录是否继承了 PHP、JSP 等脚本处理规则;
  • 服务端是否仅按扩展名判断,还是同时校验 MIME、文件头和内容;
  • 是否存在二次处理,例如图片压缩、解压、转码或异步搬运。

例如 Nginx + PHP-FPM 环境中,真正危险的不是“能访问上传文件”,而是上传目录被 PHP location 处理。应确保上传目录只允许静态文件,并且不要把用户可控路径交给脚本处理:

location ^~ /uploads/ {
    try_files $uri =404;
    location ~ \.(php|phtml|phar)$ {
        return 403;
    }
}

更稳妥的做法是把上传目录放到 Web 根目录之外,下载时由应用读取文件并设置正确的 Content-Type,文件名使用随机值,扩展名使用服务端白名单映射,而不是直接沿用用户输入。

如果靶场要定位“落点”而不是只验证现象,可以同时看访问日志和进程行为:

tail -f /var/log/nginx/access.log /var/log/nginx/error.log
ps -ef --forest
lsof -p <php-fpm-pid> 2>/dev/null

这样能区分是上传接口本身存在校验绕过,还是上传目录配置错误,或者是后续解析链路导致的执行。复现报告最好把这几个时间点分别写清楚:上传成功、文件落盘、HTTP 可达、解释器执行;否则很容易把“文件可访问”误判成“已经形成 WebShell 落点”。

创建帐户或登录后发表意见

最近浏览 0

  • 没有会员查看此页面。