返回介绍

5.1.3 文件上传漏洞

发布于 2024-10-11 22:07:44 字数 5035 浏览 0 评论 0 收藏 0

文件上传漏洞是出现最早的漏洞,也是最容易理解的漏洞,应用程序都是代码写的,代码都是写在文件里面执行的,如果能把文件上传到管理员或者应用程序不想让你上传的目录,那就是存在文件上传漏洞。注意这里并不是说一定是上传一个 WebServer 可以解析的代码文件到可以解析的目录,漏洞的定义是做攻击目标不想让你做的事情,而你又发现可以做到。

文件上传漏洞跟 SQL 注入一样丰富精彩,有很多漏洞场景和利用方式。在早期 Web 安全不太普及的时候,文件上传漏洞大多是没有限制文件格式导致可以直接上传文件,到近几年这类例子已经很少见,目前存在较多的是黑名单过滤存在绕过导致文件上传漏洞。

挖掘经验:

文件上传漏洞比较好理解,同样挖掘起来也比较简单,一般应用可以上传文件的点比较少,其次是目前大多 Web 应用都是基于框架来写,上传的点都是调用的同一个上传类,上传函数又只有 move_uploaded_file() 这一个,所以文件上传漏洞在代码审计的时候,最快的方法就是直接去搜索 move_uploaded_file() 函数,再去看调用这个函数上传文件的代码存不存在未限制上传格式或者可以绕过,其中问题比较多的是黑名单限制文件格式以及未更改文件名的方式,没有更改文件名的情况下,在 Apache 利用其向前寻找解析格式和 IIS6 的分号解析 bug 都可以执行代码。

1.未过滤或本地过滤

未过滤和本地过滤共同点是在服务器端都未过滤,这个未过滤指的是没限制任何格式的文件上传,就是一个最简单的文件上传功能,上传的时候直接上传 PHP 格式的文件即可利用,它的代码简化之后就直接是下面这样:

<?php

move_uploaded_file ( $_FILES["file"]["tmp_name"] , $_FILES["file"]["name"] );? >

move_uploaded_file 函数直接把上传的临时文件 copy 到了新文件。

2.黑名单扩展名过滤

黑名单扩展名是前几年用得比较多的验证方式,后来因为绕过多了,就慢慢改用了白名单。

黑名单的缺点有以下几个。

1)限制的扩展名不够全,上传文件格式不可预测的性质导致了可能会有漏网之鱼。PHP 能够在大多数的 WebServer 上配置解析,不同的 WebServer 默认有不同的可以解析的扩展名,典型的 IIS 默认是支持解析 ASP 语言的,不过在 IIS 下执行 ASP 的代码可不止.asp 这个扩展名,还有 cdx、asa、cer 等,如果代码里面没有把这些写全,一旦漏掉一个就相当于没做限制。我们来看看 PHPCMSv9 里面限制的:

$savefile = preg_replace ( "/ ( php|phtml|php3|php4|jsp|exe|dll|asp|cer|asa|shtml|shtm |aspx|asax|cgi|fcgi|pl )( \.|$ ) /i" , "_\\1\\2" , $savefile );

很明显我们上面说的 cdx 不在这个列表里面。

2)验证扩展名的方式存在问题可以直接绕过,另外是结合 PHP 和系统的特性,导致了可以截断文件名来绕过黑名单限制。下面先看一段代码:

<?php

function getExt ( $filename ) {  

    return substr ( $filename , strripos ( $filename , '.' ) +1 ); 

}

$disallowed_types = array ( "php" , "asp" , "aspx" ); 

// 获取文件扩展名 

$FilenameExt = strtolower ( getExt ( $_FILES["file"]["name"] )); 

# 判断是否在被允许的扩展名里 

if ( in_array ( $FilenameExt , $disallowed_types )) {

    die ( "disallowed type" ); 

}

else

{

    $filename = time () .".".$FilenameExt ; 

    // 移动文件 

    move_uploaded_file ( $_FILES["file"]["tmp_name"] , "upload/" . $FileName ); 

}

这段代码的问题在获取文件扩展名与验证扩展名,如果我们上传文件的时候文件名为“1.php”,注意后面有一个空格,则这里$FilenameExt 的值为“php”,后面有一个空格,这时候 in_array($FilenameExt,$disallowed_types)是返回 false 的,最终成功上传文件。

另外一种情况是正确的黑名单方式验证了扩展名,但是文件名没有修改,导致可以在上传时使用“%00”来截断写入,如“1.php%00.jpg”,验证扩展名时拿到的扩展名是 jpg,写入的时候被%00 截断,最终写入文件 1.php,这里不再给出案例。

3.文件头、content-type 验证绕过

这两种方式也是早期出现得比较多的,早期搞过渗透的人可能遇到过,上传文件的时候,如果直接上传一个非图片文件会被提示不是图片文件,但是在文件头里面加上“GIF89a”后上传,则验证通过,这是因为程序用了一些不可靠的函数去判断是不是图片文件,比如 getimagesize() 函数,只要文件头是“GIF89a”,它就会正常返回一个图片的尺寸数组,我们来验证一下,测试代码:

<?php

print_r ( getimagesize ( '1.gif' ));? >

测试结果截图如图 5-6 所示。

图 5-6

content-type 是在 http request 的请求头里面,所以这个值是可以由请求者自定义修改的,而早期的一些程序只是单纯验证了这个值,笔者在写这段文字的时候还专门去 w3school 等网站看了上面的 PHP 教程就存在这个问题。找了一段存在这个漏洞的代码如下:

<?php

$type = $_FILES['img']['type'] ; 

if (( $type == "image/pjpeg" ) || ( $type == "image/jpg" ) || ( $type == "image/jpeg" ) || ( $type == "image/gif" ) || ( $type == "image/bmp" ) || ( $type == "image/png" ) || ( $type == "image/x-png" )) 

{

     //uploading

}?>

4.phpcms 任意文件上传分析

这里我们以 PHPCMSv9 在 2014 年公开的一个会员投稿处文件上传漏洞,漏洞作者 felixk3y,漏洞乌云编号:wooyun-2014-062881,漏洞在文件/phpcms/libs/classes/attachment.class.php 的 upload() 函数,为了易于理解,这里省略部分代码,代码如下:

function upload ( $field , $alowexts = '' , $maxsize = 0 , $overwrite = 0 , $thumb_setting = array (), $watermark_enable = 1 ) {

    /*** 省略 ***/

    $this->alowexts = $alowexts ;   // 获取允许上传的类型 

    /*** 省略 ***/

    foreach ( $uploadfiles as $k=>$file ) {  // 多文件上传,循环读取文件上传表单 

    $fileext = fileext ( $file['name'] ); // 获取文件扩展名 

    /*** 省略 ***/

    // 检查上传格式,不过 $alowexts 是从表单提交的,可绕过     if (! preg_match ( "/^ ( ".$this->alowexts." ) $/" , $fileext )) {

    $this->error = '10' ; 

    return false ; 

     }

     /*** 省略 ***/

     $temp_filename = $this->getname ( $fileext ); 

     $savefile = $this->savepath.$temp_filename ; 

     $savefile = preg_replace ( "/ ( php|phtml|php3|php4|jsp|exe|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl )( \.|$ ) /i" , "_\\1\\2" , $savefile );   // 最需要绕过的地方在这里 

     /*** 保存文件 ***/

     if ( @$upload_func ( $file['tmp_name'] , $savefile )) {

从上面的代码我们可以看出,这个漏洞最有意思的地方在:

$savefile = preg_replace ( "/ ( php|phtml|php3|php4|jsp|exe|dll|asp|cer|asa| shtml|shtm |aspx|asax|cgi|fcgi|pl )( \.|$ ) /i" , "_\\1\\2" , $savefile );

而获取文件扩展名的函数内容为:

function fileext ( $filename ) {

    return strtolower ( trim ( substr ( strrchr ( $filename , '.' ), 1 , 10 ))); 

}

这里用了 trim() 函数去掉了空格,我们之前举例用空格绕过的方式在这里就不好使了,那有没有其他字符一样可以达到空格的效果呢,即“1.phpX”,X 代表某个字符?仔细看正则会把如“1.php”替换为“1._php”,把“1.php.jpg”替换为“1._php.jpg”,作者利用 fuzz 的方式找到了%81-%99 是可行的,仅在 Windows 下。利用时修改文件上传表单里的 filename,在文件名后面利用十六进制修改原预留的空格 20 为 81~99 中的一个。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文