nraesalmi/n8n-agent-vulntest-lab

GitHub: nraesalmi/n8n-agent-vulntest-lab

一个针对 n8n AI agent 工作流的安全研究实验室,用于系统性评估 prompt injection 和平台特定执行漏洞如何通过基于节点的低代码系统传播。

Stars: 0 | Forks: 0

# AI 工作流安全研究实验室 **低代码 AI agent 工作流平台在 prompt-injection 和执行图操纵下的安全性** 一个受控的实验框架,用于研究 prompt-injection 类攻击和特定平台执行漏洞如何通过基于节点的 AI 工作流系统 传播,其中 LLM 的输出被编译为具有外部副作用的可执行工作流图。包含 **13 个攻击场景**(baseline / basic-guardrail / custom-guardrail 变体)和 **2 个可重用的安全子工作流脚手架**,并对照 OWASP LLM 应用程序 Top 10 和 OWASP Agentic 应用程序 Top 10 进行了映射。 n8n-lab-banner ## 研究背景 ### 前提 诸如 n8n 这样的低代码自动化系统,暴露出与会话聊天机器人根本不同的攻击面。在这些系统中,LLM 的输出不仅仅是作为文本呈现——它们被编译成具有真实外部副作用的可执行工作流图:数据库写入、HTTP 调用、工具调用以及控制流决策。 虽然 prompt injection 在基于聊天的、code-first 的 agent 框架(如 LangChain、AutoGPT)中已被深入研究,但这些文献假设存在一个编排层,研究人员可以直接作为代码进行检查和探测,且工具调用以强类型的函数参数实现。n8n 的 AI Agent 节点封装了相同的 LangChain 工具调用接口,但其周围的执行环境是一个由独立配置的节点组成的可视化编排图,每个节点都有自己的凭证作用域。在这里,数据——包括 LLM 通过 `$fromAI()` 动态填充的值——在被路由到工具的可执行参数之前,会经过一种独立的服务器端表达式评估语言处理。已经为 code-first agent 记录的攻击类别是否在此处以相同方式表现,或者是否伴随着特定于这种节点图执行模型的故障模式,尚未得到系统性检验。 ### 论点 Prompt-injection 类攻击可以通过基于工作流的 AI 系统传播,在这些系统中,信任边界是隐式的,并且通常跨越多个节点、凭证和外部集成。除此之外,n8n 自身的执行模型——其表达式评估器、其按节点的凭证作用域以及由 AI 填充的工具参数——引入了完全独立于 LLM 推理操纵的故障模式,而通用的 agent 安全基准并未对这些模式进行建模。 ### 范围:两类故障 本实验室刻意区分了两类漏洞,因为将它们混为一谈会掩盖哪些缓解措施实际上适用于哪种故障: | 类别 | 测试内容 | 示例 | |---|---|---| | **推理层** | 模型通过精心构造的输入被操纵,从而做出错误决定 | 注入的网页内容说服 agent 在其回复中泄露凭证 | | **平台层** | n8n 执行引擎中的确定性机制发生失误,与模型决定的内容无关 | 以 `=` 开头的存储表单字段在下游被读回时被当作表达式评估 | 大多数已发表的 IPI 基准(InjecAgent、AgentDojo、Agent Security Bench)仅测试第一类,因为 code-first 的 agent 框架没有等效的平台层可供测试。下面十个场景中的三个(wf_03、wf_05、wf_ps_03)是专门为分离第二类而构建的。 ### 攻击场景套件 场景现在按故障层分为两个套件,外加一个用于刻意跨越两层链式攻击的复合小 分类。如果场景具有 多个通道或后端变体,这些将作为 同一工作流的 payload/config 变体运行,而不是单独的工作流文件——有关 完整的变体矩阵,请参阅 `n8n/workflows/README.md`。 **关于以前混合的两个场景(旧 wf_03、wf_05):** 两者都需要 一个 AI 填充的(`$fromAI()`)值来*触发*故障,但故障 本身——表达式重新评估、通过凭证条件保护的 SSRF—— 发生在平台层,与模型的推理是否被 操纵无关。基于此,它们被放置在平台套件中,并且 明确指出了触发机制,以免与 wf_ps_04/05 (见下文)混淆,后者完全不需要 AI 参与即可演示。 **关于复合场景(旧 wf_10):** 它不属于任何一个套件—— 其全部目的是测试当只有完整链条需要成功时,来自*两个*套件的逐步缓解是否 有效。它被保留为单独的 单一条目分类,而不是强行塞入某一个套件中。 #### 推理套件 (wf_rs_xx) | # | 场景 | 主要 OWASP 类别 | 备注 | |---|---|---|---| | **wf_rs_01** ✓ | 直接 Prompt Injection (baseline) | LLM01 | 校准控制——所有其他结果都相对于此进行解释 | | **wf_rs_02** | 间接 Injection,多通道 | LLM01 | 变体:网页内容、数据库行、电子邮件正文 | | **wf_rs_03** ✓ | 过度代理 / 工具劫持 + Guardrail 绕过 | ASI02 | 包括针对原生 Guardrails 节点自身人工审查门控的工具等效绕过测试 | | **wf_rs_04** | System Prompt 提取 | LLM07 | 架构无关;与已发布的 LangChain/chatbot 基准数据进行对比 | | **wf_rs_05** | 记忆和上下文投毒 | ASI06 | 变体:会话/缓冲区记忆,向量存储检索记忆 | | **wf_rs_06** | 无限制消耗 / Agent 循环 | LLM10 | 架构无关;影响框架(计费的 API/执行成本)与 n8n 相关 | 评估方式为在 30 次试验批次中跨每个 payload 层级和每个 LLM 后端的 带有置信区间的成功率, 与现有的可重现性方法论保持一致。`basic_guardrail` 实验组 = n8n 的原生 Guardrails 节点。 #### 平台套件 (wf_ps_xx) | # | 场景 | 主要 OWASP 类别 | 触发器 | 备注 | |---|---|---|---|---| | **wf_ps_01** | 不安全输出处理:代码/表达式注入 | LLM02 | AI 填充值 | 将通用的“LLM 输出到达 Code 节点”与 n8n 的二阶 `=` 前缀表达式重新评估模式进行对比;针对 CVE-2025-68613 系列进行修补前/后版本边界测试 | | **wf_ps_02** | 通过 SSRF 链式 `$fromAI()` 窃取凭证 | LLM02 / ASI02 | AI 填充值 | 测试当 AI 填充的 URL 参数作为传递机制时,n8n 的凭证条件 SSRF 保护是否仍然有效 | | **wf_ps_03** ✓ | 通过子工作流凭证交叉进行的 Agent 身份和权限滥用 | ASI03 | AI 填充值 | 测试注入的上下文是否可以将 AI 填充的 `Execute Sub-workflow` 目标重定向到更高权限的工作流 | | **wf_ps_04** *(验证)* | 通过未经验证的恢复 Webhook 绕过人工审查门控 | ASI01 | 无——不涉及 AI | 纯控制平面绕过;auth-configuration 变体 而非 payload 层级 | | **wf_ps_05** *(验证)* | 批量执行中的跨项批准恢复污染 | ASI02 | 演示时不需要 AI 参与;与注入的第二项配对以进行安全后果框架描述 | 每种批处理大小配置的确定性通过/失败,而非基于试验的成功率 | | **wf_ps_06** *(验证)* | HITL 预览/执行内容不匹配 | ASI01 | AI 填充值 | AI 为人工批准对话框生成预览描述,然后在同一轮次中执行工具调用——注入可以使预览看起来无害,而实际工具参数却是恶意的;测试绑定预览的输出与绑定执行的输出是否会产生分歧 | 评估方式主要为跨配置变体(auth 模式、批处理大小、修补前/后版本)的确定性通过/失败, 而非基于试验的 成功率,因为这些属于平台机制故障,而非概率性的模型 行为。`custom_guardrail` 实验组 = 你的平台层强制执行节点 (§5),在适用的地方跨 n8n 版本边界进行测试。 #### 复合 / 链式 (wf_cc_xx) | # | 场景 | 主要 OWASP 类别 | 备注 | |---|---|---|---| | **wf_cc_01** | 复合击杀链 | ASI01 + ASI02 + ASI03 | 串联 wf_rs_02 → wf_rs_03 → wf_ps_03 的 payload,以测试当只需整个链条成功时,逐步缓解措施(来自两个套件)是否仍然有效 | 一个核心研究问题是,当输出被解释为**结构化执行指令**而非自然语言回复时, 传统的 LLM 安全缓解措施 (system prompt、输出过滤、基本 guardrail)是否仍然有效 (推理套件,RQ1)——另外,应用程序层的 强制执行机制是否能够触及完全 发生在 n8n 平台层内的故障(平台套件,RQ2)。 ## 架构 ### Docker 服务 ``` ┌──────────────────────────────────────────────────────────────────────────────┐ │ Docker Network │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ │ │ │ │ │ │ n8n │───▶│ PostgreSQL │ │ Ollama │ │ │ │ :5678 │ │ :5432 │ │ :11434 │ │ │ │ │◀───│ │ │ │ │ │ └───┬────┬─────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ ├────────────────────────────┐ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ │ mock-server │ │attacker-listener │ │ │ │ │ :8080 │ │ :9999 │ │ │ │ └──────────────────┘ └──────────────────┘ │ │ │ ┌──────────────────┐ │ │ │ │ mockapi │ │ │ │ │ :3000 │ │ │ │ └──────────────────┘ │ │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ AVISE → POST to webhook → agent executes │ │ │ │ HTTP response with tool calls for evaluation │ │ │ └────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────┘ ``` 所有服务均作为 Docker 容器运行: | 服务 | 端口 | 用途 | |---|---|---| | n8n | 5678 | 工作流编排引擎和 UI | | PostgreSQL | 5432 | n8n 状态 + `agent_messages` 表 | | Ollama | 11434 | 本地 LLM 推理(后备 / 气隙隔离模式) | | mockapi | 3000 | `json-server` —— 提供模拟文档、常见问题解答、通知 | | mock-server | 8080 | Flask 服务器 —— 包含 15 个以上用于间接注入实验的端点,包括用于 wf_05 SSRF 测试的内部/不可路由目标 | | attacker-listener | 9999 | Flask 服务器 —— 捕获实验期间外泄的数据 | ### 多模型 LLM 后端 所有工作流均使用兼容 OpenAI 的 `lmChatOpenAi` 节点,该节点适用于任何提供兼容 OpenAI 的 API 的提供商: | 提供商 | LLM_BASE_URL | LLM_MODEL | |---|---|---| | OpenAI | *(空——默认为 api.openai.com)* | gpt-4o, gpt-4o-mini, ... | | OpenCode | https://api.opencode.ai/v1 | opencode/deepseek-v4-flash-free, ... | | OpenRouter | https://openrouter.ai/api/v1 | openai/gpt-4o, anthropic/claude-3, ... | | Ollama | http://ollama:11434/v1 | mistral, llama3, ... | 模型和 base URL 通过环境变量配置,并在运行时通过 n8n 表达式(`{{ $env.LLM_MODEL }}`、`{{ $env.LLM_BASE_URL }}`)注入。批处理修补程序脚本(`scripts/patch_workflow_models.py`)将这些表达式标记到所有工作流 JSON 中。不需要特定于模型的节点类型。 **关于确定性的说明:** temperature=0 会减少差异,但不能保证托管 API 上的输出位级相同——尤其是 OpenRouter,它可以将相同的请求路由到不同的后端实例。结果应报告为在 30 次试验批次中的带置信区间的成功率(见下文的*可重现性*),而不是单次运行的结果。 ## 设置 ### 前置条件 - Docker 和 Docker Compose v2 - 至少 8 GB RAM(推荐 16 GB) - (可选)你选择的 LLM 提供商的 API 密钥 ### 第 1 步:配置环境 ``` cp .env.example .env ``` 使用你的值编辑 `.env`: ``` # 必需:生成一个安全的随机 32 字符密钥 N8N_ENCRYPTION_KEY= # 必需:设置一个强密码 POSTGRES_PASSWORD= # LLM Backend 配置 # 取消注释并设置至少一个 API key: OPENCODE_API_KEY=sk-... # or #OPENAI_API_KEY=sk-... # or #OPENROUTER_API_KEY=sk-... # # 为你的 provider 设置模型和 base URL: LLM_BASE_URL=https://api.opencode.ai/v1 LLM_MODEL=opencode/deepseek-v4-flash-free # 固定 n8n image 版本。用于 CVE 边界比较 —— # 请参阅下方的“Platform Version Testing”。不要保留为 'latest'。 N8N_VERSION=1.122.0 ``` **重要提示:** 确保 `N8N_ENCRYPTION_KEY` 至少有 32 个字符。当使用 PostgreSQL 时,如果加密密钥较弱,n8n 将拒绝启动。 ### 第 2 步:创建虚拟环境 + 安装依赖 ``` # 创建虚拟环境 python -m venv .venv # 激活 (Windows PowerShell) .venv\Scripts\Activate.ps1 # 激活 (macOS/Linux) source .venv/bin/activate # 安装依赖 pip install -r requirements.txt ``` ### 第 3 步:启动服务 ``` docker compose up -d ``` 这将启动 n8n(端口 5678)、PostgreSQL(端口 5432)、Ollama(端口 11434)、mockapi(端口 3000)、mock-server(端口 8080)和 attacker-listener(端口 9999)。 ### 第 4 步:(可选)拉取 Ollama 模型 仅在将 Ollama 用作 LLM 后端(气隙隔离模式)时需要: ``` docker exec n8n-ollama ollama pull llama3.1:8b ``` 在 `.env` 中设置 `OLLAMA_MODEL=llama3.1:8b`(或更小的模型,如 `phi3` 或 `mistral`)。 ### 第 5 步:导入凭证和工作流 为你的操作系统运行相应的设置脚本: **Linux/macOS** ``` ./scripts/setup.sh ``` **Windows (PowerShell)** ``` .\scripts\setup.ps1 ``` 要临时绕过 Powershell 脚本执行策略,请运行:
标签:AI代理, AI安全, AI风险缓解, Chat Copilot, CISA项目, DLL 劫持, n8n, Web报告查看器, 反取证, 大语言模型, 安全评估, 测试用例, 请求拦截