参考文章:从0到1CTFer成长之路-第二章-Web文件上传漏洞-icode9专业技术文章分享
先观察题目
文件上传章节练习题
选择文件:
<?php
header("Content-Type:text/html; charset=utf-8");
// 每5分钟会清除一次目录下上传的文件
require_once('pclzip.lib.php');
if(!$_FILES){
echo '
<!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>
<div class="container">
<div class="login-box col-md-12">
<form class="form-horizontal" method="post" enctype="multipart/form-data" >
<h1>文件上传章节练习题</h1>
<hr />
<div class="form-group">
<label class="col-sm-2 control-label">选择文件:</label>
<div class="input-group col-sm-10">
<div >
<label for="">
<input type="file" name="file" />
</label>
</div>
</div>
</div>
<div class="col-sm-8 text-right">
<input type="submit" class="btn btn-success text-right btn1" />
</div>
</form>
</div>
</div>
</body>
</html>
';
show_source(__FILE__);
}else{
$file = $_FILES['file'];
if(!$file){
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
$path = $dir.$name;
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(in_array($ext, array('zip', 'jpg', 'gif', 'png'))){
if($ext == 'zip'){
$archive = new PclZip($file['tmp_name']);
foreach($archive->listContent() as $value){
$filename = $value["filename"];
if(preg_match('/\.php$/', $filename)){
exit("压缩包内不允许含有php文件!");
}
}
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
exit("解压失败");
check_dir($dir);
}
check_dir($dir);
exit('上传成功!');
}else{
move_uploaded_file($file['tmp_name'], $temp_dir.'/'.$file['name']);
check_dir($dir);
exit('上传成功!');
}
}else{
exit('仅允许上传zip、jpg、gif、png文件!');
}
}
先进行代码审计
<?php
header("Content-Type:text/html; charset=utf-8");
// 每5分钟会清除一次目录下上传的文件
require_once('pclzip.lib.php'); //加载一个php文件,百度查发现和文件压缩有关。
if(!$_FILES){ //如果没有提交数据则展示输出前端页面和PHP代码
echo "前端代码";
show_source(__FILE__); //展示PHP代码
}else{
$file = $_FILES['file']; //获取上传文件
if(!$file){ //如果上传文件为空,则输出'请勿上传空文件',并结束程序;
exit("请勿上传空文件");
}
$name = $file['name']; //获取文件名
$dir = 'upload/'; //dir表示目录
$ext = strtolower(substr(strrchr($name, '.'), 1)); //获取文件名后缀
/*
strrchr($name,'.')表示获取字符串中点出现的最后位置,并返回点后面的字符串(包括点),例如, $name= abc.dce.php ,则返回 .php
substr函数用作截取字符串, substr($string,$start,$length), substr(strrchr($name,'.'),1)) 表示strrchr处理后,截取字符串下标1之后的数据。例如:$name=abc.dce.php,则经过第两次函数处理后为 PHP
strtolower函数表示将字符串全部转化为小写
*/
$path = $dir.$name; //将目录名与文件名拼接
//这个函数的作用是检查$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);
}
}
}
}
}
/*
使用opendir打开指定的目录。
使用while循环和readdir迭代目录的内容。
对于目录中的每个项目,检查它是否不是'.'或'..'(表示当前目录和上级目录)。
如果项目是子目录(使用is_dir检查),则递归调用check_dir函数处理该子目录。
如果项目是文件,提取文件扩展名并转换为小写。
如果文件扩展名不在数组('jpg'、'gif'、'png')中,则使用unlink删除该文件。
需要注意的几点:
函数假设传递给它的目录路径以目录分隔符("/") 结尾。这可以从在递归调用check_dir时使用$dir.$f.%
*/
//是否存在$dir变量,有则创建以$dir为名字的目录
//$dir="upload/",猜测应该是操作系统把"/"删去,所以目录名应该为upload
if(!is_dir($dir)){
mkdir($dir);
}
//获取一个随机数与$dir变量拼接,并创建一个目录
$temp_dir = $dir.md5(time(). rand(1000,9999));
if(!is_dir($temp_dir)){
mkdir($temp_dir);
}
/* 这两行代码的作用:在当前目录下创建一个upload的目录,
接着在upload这个目录下创建一个的目录(目录名未知)
*/
if(in_array($ext, array('zip', 'jpg', 'gif', 'png'))){
//判断获取的文件后缀名是否为zip,jpg,gif,png
if($ext == 'zip'){ //如果为zip文件则检查压缩包中是否含有php文件,如果存在则退出程序
$archive = new PclZip($file['tmp_name']);
foreach($archive->listContent() as $value){ //加强for遍历zip中每一个文件
$filename = $value["filename"];
if(preg_match('/\.php$/', $filename)){ //正则检查文件后缀名是否为php
exit("压缩包内不允许含有php文件!");
}
}
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) { //尝试将zip文件解压到$temp_dir这个目录下
exit("解压失败");
check_dir($dir); //检查$dir目录,即检查upload这个目录下是否有非法文件
}
check_dir($dir); //检查upload目录
exit('上传成功!');
}else{ //如果不为zip文件,则将其上传到$temp_dir这个目录下
move_uploaded_file($file['tmp_name'], $temp_dir.'/'.$file['name']);
check_dir($dir); //检查upload目录
exit('上传成功!');
}
}else{
exit('仅允许上传zip、jpg、gif、png文件!');
}
}
代码审计后,大致上传过程:
1.先在当前目录下创建一个upload目录,紧接在upload目录下创建一个目录(目录名未知)
2.之后对上传文件进行白名单检查:
(1)如果为zip文件,则检查zip文件下是否存在php文件,有则退出程序,没有则zip文件解压,并把解压之后的文件上传到$tmp_dir这个目录下,上传完成后,再检查uplad这个目录下是否存在非法文件(白名单)。
(2)如果为jpg,gif,png文件,则进行上传,上传完成后同样检查upload这个目录。
这里可以发现,上传过程中一直进行的白名单过滤。
一般白名单过滤绕过方法:
1.MIME绕过
2.%00截断 (有可控上传路径)
3.图片木马 (需要配合文件包含漏洞)
简单观察之后发现以上方法均没有用
再看看代码有没有逻辑漏洞。
在上传zip文件时,他会检查文件后缀名,但是是黑名单过滤,这里很好绕过。但是zip文件会被上传到$tmp_dir这个目录下,我们并不知道目录路径。但是但是我们可以使用目录穿越将文件上传到upload目录。
简单举个例子:
目录穿越:通过目录控制序列 ../ 或者文件绝对路径来访问存储在文件系统上的任意文件和目录的一种漏洞。 ( ../在此处表示返回上一级目录 )
假设我们上传之后的文件保存在upload这个目录下,程序员设置upload目录下文件不能被php解析
,那么我们可以构造 文件名为 ../1.php 将文件上传到www这个目录下( ../ 表示上级目录 ./ 表示当前目录) ,这样我们的文件就能被解析
但是很快我们发现每次上传成功后,都会检查upload目录下是否有非法文件,所以不能上传到upload目录。emmm,那我们只能上传到upload的上一个目录,即当前目录。
很好接着我们有很多种办法绕过黑名单绕过.
方法1;我们可以使用特殊后缀(例如php5,php4,php6)
方法2.使用大小写绕过(window操作系统)
方法3.点绕过(window操作系统)
方法4. ::DATA绕过(window操作系统)
方法5.空格绕过(window操作系统)
方法6:使用 .htaccess 或者 .user.ini绕过 (服务器特性)
方法7:apache解析漏洞
这里先尝试使用apache解析漏洞。
先简单介绍一下apache解析漏洞:
Apache默认一个文件可以有多个以点分隔的后缀,当右边的后缀无法识别(不在mime.types内),则继续向左识别。
当我们请求一个文件:1.php.xxx.yyy
yyy -> 无法识别,向左
xxx -> 无法识别,向左
php -> 可以识别,交给php处理该文件。
所以我们创建一个zip文件,并创建一个文件名为 /../../abc.php.abc的文件名。当程序解压到这个文件时,会将文件解压到upload的上一级目录,解压之后文件名为abc.php.abc ,当我们访问这个文件时,利用apache解析漏洞就能将文件解析为php进行执行。
在window下我们无法创建含有一个 /和 . 的文件
这里我们就要用到一款工具010 editor
010 Editor汉化破解版 v9.0下载(附注册机及汉化补丁) - zd423
010editor是一款十六进制编辑器,和 winhex 相比支持更灵活的脚本语法,可以对文件、内存、磁盘进行操作,是二进制分析中十分强力的工具,能够解析多种文件格式并以友好的界面呈现。其强大的内部引擎使得任何人都可以定制所需的解析脚本或解析模板。
使用这款工具我们可以对文件名进行修改。
我们先创建一个 文件名为 "123456789123456789" 的文件(要与修改之后的文件名字节数相同),文件中包含一句话木马,并将其打包成zip文件。
接着使用我们的工具打开这个zip文件
接着我们找到修改他的文件名
注意是修改start不为0的那个(这里不太清楚原因)
接着保存并将zip文件上传
可以直接访问,也可以使用蚁剑连接。
这里先用蚁剑连接。
连接不上!!!
直接访问一下试试
flag直接就显示出来了。
看看什么情况。
这里应该是对上传的文件进行了处理。
试试上传一个将文件名改成 /../../abc.txt.abc 文件。
什么都没有显示
再试试上传一个文件为空的 /../../abc.php.abc 文件
访问发现也是直接显示flag。
这里大致猜测只要上传一个php(php5,php4,jsp没有尝试)文件 到upload的上一个目录就会获得flag。
这题的主要难点是代码审计,
还考了目录穿越,apache解析漏洞绕过黑名单(也可以尝试试试其它方式绕过)。