PHP代码审计系列基础文章(三)之文件上传漏洞篇
字数 1502 2025-08-11 21:26:06
PHP文件上传漏洞审计与防御指南
一、文件上传漏洞基本原理
1.1 核心概念
文件上传漏洞是指攻击者通过Web应用的上传功能,将恶意文件(如Webshell)上传到服务器并成功解析执行的安全漏洞。
漏洞存在的三个前提条件:
- 存在文件上传功能点
- 上传内容可由攻击者控制
- 上传的文件能被服务器解析执行
1.2 文件上传业务流程
- 攻击者通过前端页面上传恶意文件
- 服务器接收文件并存储为临时文件
- 服务器将临时文件移动到指定位置
- 攻击者访问上传的文件
- 服务器解析执行恶意文件
1.3 利用条件
- Web站点具有上传功能点
- 上传的目标文件可被Web服务器解析
- 已知上传的目标文件路径及文件名
- 目标文件可被访问(无访问控制限制)
二、PHP文件上传实现机制
2.1 前端代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传小demo</title>
</head>
<body>
<form action="demo.php" method="post" enctype="multipart/form-data">
<p>请选择上传的文件:</p>
<input class="upload_file" type="file" name="uploadFile"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
</body>
</html>
关键属性说明:
action: 指定处理上传的后端文件method: 必须为POSTenctype: 必须为multipart/form-datatype="file": 定义文件上传控件
2.2 后端处理代码示例
<?php
if(is_uploaded_file($_FILES["uploadFile"]["tmp_name"])) {
$upFile = $_FILES["uploadFile"];
$name = $upFile["name"];
$type = $upFile["type"];
$size = $upFile["size"];
$tmpName = $upFile["tmp_name"];
var_dump($upFile);
$path = "./".$name;
move_uploaded_file($tmpName,$path);
echo "文件上传成功";
} else {
echo "文件上传失败";
}
?>
2.3 PHP关键函数与变量
$_FILES超全局变量
$_FILES['uploadFile']['name']: 客户端文件的原名称$_FILES['uploadFile']['type']: 文件的MIME类型$_FILES['uploadFile']['size']: 已上传文件的大小(字节)$_FILES['uploadFile']['tmp_name']: 服务器端存储的临时文件名$_FILES['uploadFile']['error']: 上传相关的错误代码
关键函数
is_uploaded_file(): 检查文件是否通过HTTP POST上传move_uploaded_file(): 将临时文件移动到新位置
重要注意事项:
- 上传的文件默认会被存储为临时文件(如phpBD6C.tmp)
- 必须使用
move_uploaded_file()保存文件,否则脚本执行完毕后临时文件会被删除
三、文件上传检测类型与绕过方法
3.1 前端JS检测示例
<script type="text/javascript">
function checkFile() {
var file = document.getElementsByName('uploadFile')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
var allow_ext = ".jpg|.png|.gif";
var ext_name = file.substring(file.lastIndexOf("."));
if (allow_ext.indexOf(ext_name) == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
</script>
3.2 绕过前端检测的方法
- 修改文件后缀名为允许的类型(如.jpg)
- 使用Burp Suite等工具拦截请求
- 将文件名改回恶意后缀(如.php)
- 放行请求绕过前端验证
四、文件上传漏洞审计要点
4.1 重点审计对象
-
文件类型检测代码
- 前端JS验证
- 后端MIME类型检查
- 文件扩展名检查
- 文件内容检查
-
关键PHP函数
move_uploaded_file()$_FILES超全局变量的使用
-
文件存储处理
- 文件名生成逻辑
- 存储路径控制
- 权限设置
4.2 常见漏洞模式
- 无任何检测的直接上传
- 仅前端检测无后端验证
- 黑名单机制不完善
- 文件类型检测可被绕过
- 文件内容未进行安全检查
五、防御措施
5.1 基础防御方案
- 使用白名单限制文件扩展名
- 同时在前端和后端进行验证
- 重命名上传文件(避免使用用户提供的文件名)
- 限制上传文件大小
- 设置适当的文件权限
5.2 进阶防御措施
- 对文件内容进行检测(如检查图片文件头)
- 将上传文件存储在非Web可访问目录
- 使用单独的子域名托管用户上传内容
- 定期扫描上传目录中的可疑文件
- 禁用危险的文件执行权限(如PHP文件)
5.3 安全代码示例
<?php
$allowed_ext = array('jpg', 'png', 'gif');
$max_size = 1024 * 1024; // 1MB
if(is_uploaded_file($_FILES["uploadFile"]["tmp_name"])) {
$file = $_FILES["uploadFile"];
// 检查文件大小
if($file['size'] > $max_size) {
die("文件大小超过限制");
}
// 获取文件扩展名
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
// 检查扩展名
if(!in_array($ext, $allowed_ext)) {
die("不允许的文件类型");
}
// 生成随机文件名
$new_name = uniqid().'.'.$ext;
$path = "./uploads/".$new_name;
// 移动文件
if(move_uploaded_file($file['tmp_name'], $path)) {
echo "文件上传成功";
} else {
echo "文件保存失败";
}
} else {
echo "文件上传失败";
}
?>
六、总结
文件上传漏洞是Web应用中最常见也最危险的安全问题之一。审计时需重点关注文件类型检测的实现方式、文件存储处理逻辑以及相关函数的使用情况。防御时应采用多层次的安全措施,包括但不限于白名单验证、文件重命名、内容检测和权限控制等。