代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>文件上传章节练习题</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<style type="text/css">
.login-box{
margin-top: 100px;
height: 500px;
border: 1px solid #000;
}
body{
background: white;
}
.btn1{
width: 200px;
}
.d1{
display: block;
height: 400px;
}
</style>
</head>
<body>
<form method="post" action="upload.php" enctype="multipart/form-data">
<input type="file" name="file" value=""/>
<input type="submit" name="submit" value="upload"/>
</form>
</body>
</html>
<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
function check_dir($dir)
{
$handle = opendir($dir);
while (($f = readdir($handle)) !== false) {
if (!in_array($f, array('.', '..'))) {
$ext = strtolower(substr(strrchr($f, '.'), 1));
if (!in_array($ext, array('jpg', 'gif', 'png'))) {
unlink($dir . $f);
}
}
}
}
if (!is_dir($dir)) {
mkdir($dir);
}
$temp_dir = $dir . 'member/1/';
if (!is_dir($temp_dir)) {
mkdir($temp_dir);
}
// 判断文件是否为压缩包
if (in_array($ext, array('zip', 'jpg', 'gif', 'png'))) {
if ($ext == 'zip') {
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}
// 判断压缩包里面是不是除了有
check_dir($temp_dir);
exit('上传成功!');
} else {
move_uploaded_file($file['tmp_name'], $temp_dir . '/' . $file['name']);
check_dir($temp_dir);
exit('上传成功!');
}
} else {
exit('仅允许上传zip、jpg、gif、png文件!');
}
解析代码
先上传文件,在建一个上传文件的目录upload,在解压文件,解压完文件后在对解压完的文件进行判断,判断文件里面是不是有除了zip、jpg、gif、png之外的文件,有的话就直接删除。只能上传压缩包,因为若上传png文件的话也会被压缩,然后在进行解压缩。
代码问题
1、存在时间竞争型漏洞:若上传的是php文件,是先被解压,再被删除。
2、没有进行递归删除。
第一次绕过
新建一个压缩包,压缩包里面有一张png图片和一个php文件。
web.php文件内容
aaaaaaa
开始上传
查看上传结果
发现php文件已经被删除,只有png文件保留了下来。
解决方案:
代码未进行递归删除,导致文件夹内的webshell得以保留。
只需要将上传的压缩文件中,递归创建一个文件夹,即可上传成功。
建一个新的文件aaa,在文件aaa里面放一张png图片和一个php文件,再对文件aaa进行压缩。
再次上传,上传压缩包aaa。
上传结果:
这就是phpcms最早的头像上传漏洞。这个漏洞影响的不只是phpcms,也包括抄袭其代码的finecms。
在phpcms出问题以后,finecms偷偷将漏洞修复了,当然修复方法就是直接拷贝了phpcms的补丁。
发现php文件并没有被删除,因为程序没有进行递归删除,导致该问题的存在。
修复方法:
增加递归删除,但是又造成了新的问题。
第二次绕过
针对第一次的绕过,进行了递归删除的补丁修复
得到以下PHP代码
<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
//递归删除 zip 1 web.php
function check_dir($dir){
$handle = opendir($dir);
while(($f = readdir($handle)) !== false){
if(!in_array($f, array('.', '..'))){
if(is_dir($dir.$f)){
check_dir($dir.$f.'/');
}else{
$ext = strtolower(substr(strrchr($f, '.'), 1));
if(!in_array($ext, array('jpg', 'gif', 'png'))){
unlink($dir.$f);
}
}
}
}
}
if (!is_dir($dir)) {
mkdir($dir);
}
$temp_dir = $dir . 'member/1/';
if (!is_dir($temp_dir)) {
mkdir($temp_dir);
}
if (in_array($ext, array('zip', 'jpg', 'gif', 'png'))) {
if ($ext == 'zip') {
$archive = new PclZip($file['tmp_name']);
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}
check_dir($temp_dir);
exit('上传成功!');
} else {
move_uploaded_file($file['tmp_name'], $temp_dir . '/' . $file['name']);
check_dir($temp_dir);
exit('上传成功!');
}
} else {
exit('仅允许上传zip、jpg、gif、png文件!');
}
从第二版的代码可以看出,对目录中的所有次级目录进行了递归删除,但是它还是先解压压缩包到本地再进行删除,所以,可以利用这个时间竞争型漏洞来绕过,只要能在被删除之前访问到一次php文件,就能够成功在它不会进行检查和删除的上级目录中生成一个新的php文件。
在文件上传解压到被删除这个时间差里访问,就能在网站根目录下生成新的php文件,那么新生成的php文件是不会被删除的。
所以修改我们之前所搭建的环境,将php代码中的第一个function递归删除的函数替换成以下代码。
重写一个php文件
web.php
<?php fputs(fopen('../../../payload.php','w'),'<?php phpinfo(); ?>');?>
../
可以让文件向上跳一层,../../
可以让文件向上跳两层,依次类推。
使用burp进行抓包,然后将这个包投放到intruder模块中,反复进行发送,访问的数据包同样用intruder进行。
再次尝试上传一下
因为这里使用了递归删除,所以上传文件夹不能绕过了。
上传后发现文件夹中只剩图片了,php代码被删除了。但是这里上传文件夹后代码名字未修改,并且这里是先上传再删除,所以存在一个时间竞争漏洞。
查看我们之前上传的路径,为
http://127.0.0.1/xss_location/upload.php/member/1/1/web.php
因此需要跳跃三次才能到达跟目录,则代码为
<?php fputs(fopen('../../../payload.php','w'),'<?php phpinfo(); ?>');
用时间竞争型漏洞直接在根目录下写入我们的一句话木马,这样他递归删除只是删除当前文件夹的,不会检测根目录。尝试一下
打开burpsuit,上传web.zip并重发到Intruder模块中,准备模拟多次上传。
用时间竞争型漏洞直接在根目录下写入我们的一句话木马,这样他递归删除只是删除当前文件夹的,不会检测根目录。尝试一下
使用burp进行抓包,然后将这个包投放到intruder模块中,反复进行发送,访问的数据包同样用intruder进行。
设置访问次数为5000次
在抓取的要访问的目录代码,上传到Intruder模块
payload设置成6000次
两个同时开始start attack。
可以看到,访问成功了,时间竞争漏洞利用成功
解决方案:
由于先解压,再删除,导致暴力getshell
时间竞争拿下webshell
第三次绕过
针对第二次的绕过,进行了递归删除的补丁修复
得到以下PHP代码
<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
function check_dir($dir){
$handle = opendir($dir);
while(($f = readdir($handle)) !== false){
if(!in_array($f, array('.', '..'))){
if(is_dir($dir.$f)){
check_dir($dir.$f.'/');
}else{
$ext = strtolower(substr(strrchr($f, '.'), 1));
if(!in_array($ext, array('jpg', 'gif', 'png'))){
unlink($dir.$f);
}
}
}
}
}
if (!is_dir($dir)) {
mkdir($dir);
}
$temp_dir = $dir.md5(time(). rand(1000,9999)).'/';
if (!is_dir($temp_dir)) {
mkdir($temp_dir);
}
if ($ext == 'zip') {
$zip = new ZipArchive;
if(!$zip->open($file['tmp_name'])) {
echo "fail";
return false;
}
if(!$zip->extractTo($temp_dir)) {
exit("fail to extract");
}
check_dir($temp_dir);
exit('上传成功!');
} else {
move_uploaded_file($file['tmp_name'], $temp_dir . '/' . $file['name']);
check_dir($temp_dir);
exit('上传成功!');
}
} else {
exit('仅允许上传zip、jpg、gif、png文件!');
}
通过观察得到,第三版和第二版的区别在于,使用时间随机生成了临时文件夹,我们不知道文件夹名称的情况下,我们就无法访问到我们的php文件,就算使用时间竞争也没办法了。
因此我们需要利用一个新的技巧来绕过它。
可以看到源码中的如果解压失败了,那就会直接退出程序,而不对文件夹进行检测,我们只需要将压缩包中的第一个php文件解压出来,第二个正常文件不被解压出来就可以绕过了。
如果压缩包是.7z格式,而7zip的容忍度很低,只需要修改第二个文件的crc,就可以让它解压出错了。
使用010editor打开web.7z文件。
为了让ZipArchive出错,比如,Windows下不允许文件名中包含冒号(:)等特殊符号,
可以在010editor中将1.txt的deFileName属性的值改成“1.tx:”。
此时解压就会出错,但1.php被保留了下来。
尝试上传,提示fail to extract
查看服务端,发现php代码成功上传