首先搭建测试环境,测试网站是否成功
首页代码
<!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');
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);
}
}
}
}
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';//将上传的文件放到upload文件夹下面,后面查找文件的时候可以在upload文件夹下进行查找
$ext = strtolower(substr(strrchr($name, '.'), 1));//截取.后面的后缀名称。
$temp_dir = $dir . 'member/1/';
if (!is_dir($dir)) {
mkdir($dir);
}
$temp_dir = $dir . 'member/1/';//这里是默认了创建目录为member下面,这个1实际上代表的是用户id,也就是每个用户都有一个自己的id
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的文件删除,
这个2的文件夹里放的东西为:
将他进行压缩为zip的压缩包,然后进行上传。
上传成功后,我们在xss_location\upload\member\1的路径下可以看到,它是只有一个图片的,而那个php后缀的文件已经被删除了。
但是我们如果在文件夹中在进行一个嵌套,也就是在文件夹中在创建一个名为aaa的文件夹,将php文件放到aaa的文件夹当中,它就不会将php文件进行删除了。
也就是说当我们上传完文件之后,upload\member\1路径下中是存在aaa的文件夹,并且点进去之后,它是没有将咱们上传的php文件进行删除。
因为上面的漏洞被很多人发现,所以它想办法弥补,也就是说,我创建一个递归,即便有在多的文件,我也进去给你删除,所以代码就变成了:
<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.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);
}
}
}
}
}
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
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文件里面的内容
修改过后后,重新压缩,然后打开burp suite来进行抓包,然后时间竞争。
首先配置代理,
打开burp,在这里配置和刚刚在设置里面一样的ip和端口号。然后选择running,
先将抓包进行关闭,然后我们上传好文件的时候就可以进行开启抓包,抓包成功后,我们右键将他放到intruder里面,然后先清除,然后在随意找到一个数字添加。
之后在在paylods里面设置形式为数字,从1次到1000次,step为1 的访问。并且开始运行。
运行结束后可以在upload\member\1下去找是否成功。
因为上一个漏洞使用时间竞争就可以进行绕过,所以它对这个又进行了一次修改,也就是说将名称用一段随机数来替代,这样的话你就没有办法去寻找文件位置。那样的话就没有办法进行时间竞争。
所以将代码改成了:
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.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);
}
}
}
}
}
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
$temp_dir = $dir.md5(time(). rand(1000,9999)).'/';//以随机数的方式来创建文件名称。
if (!is_dir($dir)) {
mkdir($dir);
}
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文件!');
}
但是,这里也是存在一个bug,也就是所,如果我们进行解压缩,但是解压到一半的时候,让他解压出错,那么不就可以直接退出了吗,也就是说,解压一个错误的压缩包,那样就可以进行解压,但是因为它解压失败,所以就直接跳过了删除部分,直接结束,文件也就不会被删除。
那么如何让压缩文件解压出错?
首先:在window下的解压软件7zip;
7zip的容忍度很低,只要压缩包中某一个文件的CRC校验码出错,就会报错退出;所以我们就可以通过将校验码改错,它就不能正常解压缩。
这样的话我们在解压缩的时候它就会报错。
在php下使用自带的ZipArchive
以前我们在改文件的名字的时候,它会有一个提示:就是不允许出现一些符号,其中有一个是冒号(😃,那我们就可以将文件名字上加上那个,这样的话就可以解压失败。
我在那个D:\phpstudy_pro\Extensions\php\php7.3.4nts的路径下写了一个zip.php的文档,文档内容如下:
function unzip($zipname,$path) {
$zip = new ZipArchive;
if(!$zip->open($zipname)) {//如果无法打开,报错
echo "fail";
return false;
}
if(!$zip->extractTo($path)) {//解压失败的话显示fail to extract
echo "fail to extract";
return false;
}
$zip->close();
return true;
}
if(unzip('./web.zip','./')) {//这个位置的web.zip是你要解压的文件名字,你要解压啥就写啥
echo "success zip";
}else {
echo "failt to unzip";
}
首先我们通过010 Editor这个软件来进行修改,将bbbb的后缀名从txt改成tx: ,
然后通过cmd来进行解压查看
我们回到文件中进行查找,可以看到php文件加载出来了,至于那个bbbb的文档就无所谓了,因为主要就是要用的这个。
至于LINUX下的错误解压方式我就不演示了,就只要记住,当文件名字是"\\"–也就是5个反斜杠的话,就会造成解压失败
所以刚刚那个漏洞就可以通过一个这样的错误的解压方式就可以进行绕过
它发现又有人绕过了,它就又写出了一种方式,就是即便我解压失败了,我也要在进行一次递归查询,如果你这还有的话,我还是会给你删了。
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
$dir = 'upload/';
if (!file_exists($dir)) {
mkdir($dir);
}
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);
}
}
}
}
}
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
if (!is_dir($dir)) {
mkdir($dir);
}
$temp_dir = $dir.md5(time(). rand(1000,9999)).'/';
if (!is_dir($temp_dir)) {
mkdir($temp_dir);
}
if (in_array($ext, array('zip', 'jpg', 'gif', 'png'))) {
if ($ext == 'zip') {
$zip = new ZipArchive;
if(!$zip->open($file['tmp_name'])) {
echo "fail";
return false;
}
if(!$zip->extractTo($temp_dir)) {
check_dir($temp_dir);
exit("fail to extract");
}
$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文件!');
}
那这种的话如何绕过呢?
这样的话我们就可以通过一个小技巧来进行绕过:制造一个特殊名称的webshell,…/…/…/…/web.php,当解压时,直接解压到根目录 随后,无论如何删除,也不能影响我们getshell
但是我们可以和上一个漏洞进行结合,它先解压失败,然后进行删除,而留下来的就是我们需要的php文件,这样的话我们就可以将使用刚刚说到的小技巧来将剩下的那个php留到上一个目录去,那样就可以了。
所以和上面的一样,我们通过010 editor来进行修改
然后上传文件进行测试,可以看到,它确实解压到前一个文件下面了。也就是保留了咱么的php文件。