mzurain/RAG-security-research
GitHub: mzurain/RAG-security-research
一个基于 RAG 的安全研究 PDF 助手,利用向量数据库与 LLM 提供上下文感知的安全洞察。
Stars: 0 | Forks: 0
# IntelliDocs — 安全漏洞案例研究
## 项目概述
IntelliDocs 是一个基于以下技术构建的 RAG(检索增强生成)PDF 助手:
- **FastAPI** — 处理所有 HTTP 路由的 Python Web 框架
- **ChromaDB** — 存储文档嵌入的向量数据库
- **Sentence Transformers** — 用于生成嵌入的 `all-MiniLM-L6-v2`
- **Groq API** — 用于回答问题的 `llama-3.1-8b-instant` LLM
- **Docker** — 容器化部署于 Hugging Face Spaces
**代码仓库**:https://github.com/mzurain/IntelliDocs
**在线演示**:https://huggingface.co/spaces/mzurain/IntelliDocs
## 漏洞概要
| # | 漏洞类型 | 文件 | 严重程度 | OWASP 类别 | 状态 |
|---|---------|------|----------|------------|--------|
| 1 | 日志注入(CWE-117) | `main.py` 第 93、98、102、112 行 | 🔴 高 | A09 - 安全日志记录失败 | 开放 |
| 2 | 日志注入(CWE-117) | `services/vector_store.py` 第 61、118 行 | 🔴 高 | A09 - 安全日志记录失败 | 开放 |
| 3 | NoSQL 注入(CWE-943) | `main.py` 第 145–146 行 | 🔴 高 | A03 - 注入 | 开放 |
| 4 | 任何端点均无身份验证 | `main.py` | 🔴 高 | A01 - 失效的访问控制 | 开放 |
| 5 | 通配符 CORS 策略 | `main.py` 第 31 行 | 🟠 中 | A05 - 安全配置错误 | 开放 |
| 6 | 无速率限制 | `main.py` | 🟠 中 | A05 - 安全配置错误 | 开放 |
| 7 | 完整异常信息暴露给客户端 | `main.py` 第 167 行 | 🟠 中 | A05 - 安全配置错误 | 开放 |
| 8 | 无限制文件上传 | `main.py` 第 71–81 行 | 🟠 中 | A04 - 不安全设计 | 开放 |
| 9 | 未经验证加载 API 密钥 | `services/llm_service.py` 第 15 行 | 🟠 中 | A02 - 加密失败 | 开放 |
| 10 | 朴素日期时间使用(CWE-) | `services/vector_store.py` 第 56 行 | 🟡 低 | A04 - 不安全设计 | 开放 |
| 11 | 上传路由中函数耦合度过高 | `main.py` 第 58–59 行 | 🟡 低 | 代码质量 | 开放 |
## 详细发现
### 发现 1 — 日志注入(CWE-117、CWE-93)
**严重程度**:🔴 高
**OWASP**:A09:2021 — 安全日志记录与监控失效
**文件**:`main.py`(第 93、98、102、112 行)、`services/vector_store.py`(第 61、118 行)
#### 问题描述
当应用程序将未经过滤的用户可控输入直接写入日志时,就会发生日志注入。攻击者可以构造包含换行符(`\n`)或 ANSI 转义序列的输入,从而伪造日志条目、隐藏恶意行为或破坏日志解析器。
#### 代码中的具体位置
在 `main.py` 中:
```
# 第 93 行 — 文件名直接来自上传的文件,由用户控制
logger.info("Saved '%s' (%d bytes)", upload.filename, len(content))
# 第 98 行
logger.info("'%s' → %d chunks", upload.filename, len(chunks))
# 第 102 行
logger.info("Stored '%s' as doc_id=%s in session=%s", upload.filename, doc_id, sid)
```
在 `services/vector_store.py` 中:
```
# 第 61 行 — 文件名由用户提供
logger.info("Upserted %d vectors for '%s' in session '%s'", len(chunks), filename, session_id)
# 第 118 行 — session_id 来自 HTTP 请求
logger.info("Deleted session '%s'", session_id)
```
#### 攻击场景
攻击者上传一个名为以下内容的文件:
```
malicious.pdf\n[CRITICAL] Admin login successful from 192.168.1.1
```
这会注入一条伪造的严重日志条目,可能绕过安全监控或将正常用户污名化。
#### 计划修复
- 在记录日志之前,移除或替换所有用户输入中的 `\n`、`\r` 及其他控制字符
- 创建 `sanitize_for_log(value: str) -> str` 工具函数
- 在任何日志调用之前,对所有文件名、会话 ID 和问题字符串应用该函数
---
### 发现 2 — NoSQL 注入(CWE-943)
**严重程度**:🔴 高
**OWASP**:A03:2021 — 注入
**文件**:`main.py`(第 145–146 行)
#### 问题描述
当用户可控输入未经清理直接传递给数据库查询时,就会发生 NoSQL 注入。尽管 ChromaDB 是向量数据库,但它使用的集合名称和查询参数若未经验证仍可能被操纵。
#### 代码中的具体位置
```
# main.py 第 145 行 — req.session_id 直接来自 HTTP 请求体
context_chunks = vector_store.search(req.session_id, req.question, k=req.top_k)
```
`req.session_id` 来自用户 JSON 体的原始字符串,未经格式或内容验证就直接传递给 ChromaDB 的 `get_collection(session_id)` 和 `query()` 调用。
#### 攻击场景
恶意用户发送一个精心构造的 `session_id`,例如 `../../etc` 或针对 ChromaDB 内部 SQLite 层(`chroma.sqlite3`)的特制字符串,可能访问或破坏其他用户的会话数据。
#### 计划修复
- 在执行任何数据库操作前,使用正则表达式验证 `session_id` 是否符合严格的 UUID 格式
- 拒绝不符合 `^[a-f0-9-]{36}$` 格式的会话 ID
- 对 `/upload` 端点中的 `session_id` 也应用相同的验证
---
### 发现 3 — 任何端点均无身份验证
**严重程度**:🔴 高
**OWASP**:A01:2021 — 失效的访问控制
**文件**:`main.py`
#### 问题描述
IntelliDocs 的每个 API 端点都完全开放 —— 不需要 API 密钥、令牌或会话验证。任何知道 URL 的人都可以上传文档、查询任意会话、列出所有会话或删除任意会话。
#### 代码中的具体位置
```
# 所有路由完全公开 — 没有任何身份验证依赖
@app.post("/upload", response_model=UploadResponse)
async def upload_pdfs(...):
@app.post("/query", response_model=QueryResponse)
async def query(req: QueryRequest):
@app.get("/sessions")
async def list_sessions():
@app.delete("/session/{session_id}")
async def delete_session(session_id: str):
```
#### 攻击场景
- 任何人可调用 `GET /sessions` 枚举所有活跃会话 ID
- 任何人可调用 `DELETE /session/{id}` 删除其他用户的上传文档
- 任何人可对 `POST /upload` 发起洪水攻击,上传大型 PDF 耗尽内存和存储(DoS)
- 任何人可调用 `POST /query` 查询其枚举出的任意会话
#### 计划修复
- 通过 FastAPI 的 `HTTPBearer` 或 `APIKeyHeader` 依赖项添加 API 密钥身份验证
- 在所有敏感路由上注入 `Authorization` 头检查
- 将会话作用域限定为认证用户,防止跨会话访问
---
### 发现 4 — 通配符 CORS 策略
**严重程度**:🟠 中
**OWASP**:A05:2021 — 安全配置错误
**文件**:`main.py`(第 31 行)
#### 问题描述
CORS(跨域资源共享)用于控制哪些域名可以通过浏览器向你的 API 发起请求。将 `allow_origins=["*"]` 设置为允许互联网上的任何网站都能从用户浏览器向 IntelliDocs 发起请求。
#### 代码中的具体位置
```
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # ← any origin allowed
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
```
注意:`allow_origins=["*"]` 与 `allow_credentials=True` 结合使用实际上违反 CORS 规范,会被浏览器拒绝 —— 但这仍然代表配置错误。
#### 攻击场景
恶意网站可在用户浏览器中嵌入 JavaScript,悄无声息地调用 IntelliDocs 端点,泄露其上传的文档数据(类似 CSRF 的攻击)。
#### 计划修复
- 将 `["*"]` 替换为明确允许的域名列表(例如 HF Space 的 URL)
- 除非实际使用 Cookie 会话,否则移除 `allow_credentials=True`
---
### 发现 5 — 无速率限制
**严重程度**:🟠 中
**OWASP**:A05:2021 — 安全配置错误
**文件**:`main.py`
#### 问题描述
没有任何端点具备速率限制。`/upload` 和 `/query` 端点尤其危险:`/upload` 触发 PDF 处理、嵌入生成和 ChromaDB 写入;`/` 调用 Groq API,该 API 有自己的速率限制和成本。
#### 攻击场景
- 攻击者向 `/query` 发起数千个请求,瞬间耗尽 Groq API 的免费额度
- 攻击者向 `/upload` 发起洪水攻击,上传大型 PDF 耗尽 Docker 容器的内存并导致 Space 崩溃
- Groq API 密钥因滥用而被限流或封禁
#### 计划修复
- 使用 `slowapi`(FastAPI 速率限制库)为 `/upload` 添加 `5/分钟` 限制,为 `/query` 添加 `30/分钟` 限制
- 增加请求大小验证,超出 50MB 文件限制
---
### 发现 6 — 完整异常信息暴露给客户端
**严重程度**:🟠 中
**OWASP**:A05:2021 — 安全配置错误
**文件**:`main.py`(第 167 行)
#### 问题描述
当查询过程中发生未处理的异常时,完整的 Python 异常信息(包括内部堆栈细节)会直接返回给客户端。
#### 代码中的具体位置
```
# main.py 第 167 行
except Exception as exc:
logger.exception("Query failed")
raise HTTPException(status_code=500, detail=f"Query error: {exc}")
# ^^^^
# Raw exception string sent to the user
```
#### 攻击场景
如果 ChromaDB 抛出的错误包含文件路径(如 `/app/chroma_db/...`),或 Groq 客户端抛出的错误包含 API 密钥或内部配置的一部分,这些信息会直接通过 500 响应体暴露给攻击者。
#### 计划修复
- 向客户端返回通用消息:`"An internal error occurred. Please try again."`
- 仅在服务器端日志中保留完整异常(`logger.exception` 已正确实现)
---
### 发现 7 — 无限制文件上传(内容验证缺失)
**严重程度**:🟠 中
**OWASP**:A04:2021 — 不安全设计
**文件**:`main.py`(第 71–81 行)
#### 问题描述
上传端点仅检查文件扩展名(`.pdf`)和文件大小,未验证文件实际是否为 PDF(通过魔数/文件签名)。攻击者可将任意文件重命名为 `.pdf` 并上传。
#### 代码中的具体位置
```
# 仅检查扩展字符串 — 可以轻松绕过
if not f.filename.lower().endswith(".pdf"):
raise HTTPException(...)
```
#### 攻击场景
- 攻击者上传一个重命名为 `exploit.pdf` 的恶意文件
- `pdfplumber` 或 `pypdf` 尝试解析该文件时可能触发库漏洞
- 重命名为 `.pdf` 的 zip 炸弹可能耗尽内存
#### 计划修复
- 读取文件前 4 字节并检查 PDF 魔数:`%PDF`(`25 50 44 46`)
- 无论扩展名如何,不匹配则拒绝文件
- 设置最大分块数限制,防止 zip 炸弹式 DoS
---
### 发现 8 — API 密钥加载时未经验证
**严重程度**:🟠 中
**OWASP**:A02:2021 — 加密失败
**文件**:`services/llm_service.py`(第 15 行)
#### 问题描述
Groq API 密钥在启动时从环境变量加载,但未检查其是否存在。若 `GROQ_API_KEY` 未设置,`os.getenv()` 返回 `None`,Groq 客户端将以 `api_key=None` 初始化。应用会成功启动,但在首次查询时静默失败并返回模糊的 401 错误。
#### 代码中的具体位置
```
def __init__(self):
self.client = Groq(api_key=os.getenv("GROQ_API_KEY")) # None if not set
```
#### 攻击场景
- 应用在缺少密钥的情况下部署,用户收到模糊的 500 错误且无明确原因
- 在配置错误的环境中,`None` 可能被传递给 API 客户端并回退到不安全默认值
#### 计划修复
- 启动时添加检查:若 `GROQ_API_KEY` 为 `None` 或空,立即抛出 `RuntimeError` 并显示清晰信息
- 使用 FastAPI 的 `lifespan` 事件在接收流量前验证所有必需环境变量
---
### 发现 9 — 朴素日期时间使用
**严重程度**:🟡 低
**OWASP**:A04:2021 — 不安全设计
**文件**:`services/vector_store.py`(第 56 行)
#### 问题描述
`datetime.utcnow()` 返回一个没有时区信息的“朴素” datetime 对象。在 Python 3.12+ 中已弃用,可能导致时间比较错误、审计日志不一致以及后续添加的会话过期逻辑出现细微错误。
#### 代码中的具体位置
```
# 第 56 行
"created_at": datetime.utcnow().isoformat()
```
#### 计划修复
```
from datetime import datetime, timezone
"created_at": datetime.now(timezone.utc).isoformat()
```
---
### 发现 10 — 上传路由中函数耦合度过高
**严重程度**:🟡 低
**OWASP**:代码质量 / 可维护性
**文件**:`main.py`(第 58–130 行)
#### 问题描述
`upload_pdfs` 路由函数内部调用了 17 个不同函数。这种高耦合使得代码难以测试和维护,且任一函数变更都可能意外破坏上传流程。
#### 计划修复
- 将文件验证提取为 `validate_upload_file(file)` 辅助函数
- 将每个文件的处理流程提取为 `process_single_file(upload, sid)` 辅助函数
- 路由处理程序应仅负责协调,而非具体实现
## OWASP Top 10 — 覆盖映射
| OWASP 2021 | 类别 | IntelliDocs 中是否受影响 |
|------------|------|--------------------------|
| A01 | 失效的访问控制 | ✅ 是 — 任何端点均无身份验证 |
| A02 | 加密失败 | ✅ 是 — API 密钥启动时未验证 |
| A03 | 注入 | ✅ 是 — 通过 session_id 实现的 NoSQL 注入 |
| A04 | 不安全设计 | ✅ 是 — 无限制文件上传、朴素日期时间 |
| A05 | 安全配置错误 | ✅ 是 — 通配符 CORS、无速率限制、异常信息泄露 |
| A06 | 易受攻击和过时的组件 | ⚠️ 未扫描 — 需进行依赖项审计 |
| A07 | 身份识别与认证失败 | ✅ 是 — 不存在任何身份验证机制 |
| A08 | 软件与数据完整性失败 | ⚠️ 部分 — 无 Webhook/输入签名 |
| A09 | 安全日志记录与监控失败 | ✅ 是 — 多个文件中存在日志注入 |
| A10 | 服务器端请求伪造 (SSRF) | ✅ 是 — Groq/Hugging Face API 调用使用用户影响内容 |
## 计划安全路线图
### 阶段 1 — 紧急(优先执行)
- [ ] 为所有端点添加 API 密钥身份验证
- [ ] 在记录日志前清理所有用户输入
- [ ] 在数据库操作前验证 session_id 格式(仅限 UUID)
- [ ] 向客户端返回通用错误信息,仅在服务器端保留完整异常日志
### 阶段 2 — 重要
- [ ] 使用 `slowapi` 添加速率限制
- [ ] 上传时验证 PDF 魔数
- [ ] 限制 CORS 为已知来源
- [ ] 启动时验证 `GROQ_API_KEY` 是否存在
### 阶段 3 — 强化
- [ ] 将 `datetime.utcnow()` 替换为带时区的等效方法
- [ ] 重构上传路由以降低函数耦合
- [ ] 运行 `pip-audit` 检查存在漏洞的依赖项
- [ ] 添加安全响应头(X-Content-Type-Options、X-Frame-Options、CSP)
## 所用工具
| 工具 | 用途 |
|------|------|
| Amazon Q Developer(代码审查) | SAST 扫描 — 检测 CWE-117、CWE-93、CWE-943 |
| 手动代码分析 | 架构审查、身份验证缺口、CORS、速率限制 |
| OWASP Top 10(2021) | 漏洞分类框架 |
## 文档信息
- **版本**:1.0 — 初始真实发现
- **扫描日期**:2025
- **作者**:mzurain
- **状态**:漏洞开放 — 修复计划中,待与 Amazon Q Developer 协作
- **下一步**:实施阶段 1 修复并重新扫描以验证修复
标签:AES-256, all-MiniLM-L6-v2, API密钥安全, AV绕过, ChromaDB, CORS配置, DLL 劫持, Docker, FastAPI, Groq API, Hugging Face Spaces, IntelliDocs, LLM推理, NoSQL注入, OWASP A01, OWASP A03, OWASP A04, OWASP A05, OWASP A09, PDF解析, Python Web框架, RAG系统, Rego, Sentence Transformers, Sysdig, 向量数据库, 大语言模型, 威胁情报, 安全开发生命周期, 安全日志注入, 安全防御评估, 容器化部署, 开发者工具, 开放策略代理, 异常信息泄露, 文件上传漏洞, 检索增强生成, 漏洞分析案例, 漏洞评估, 网络安全, 认证绕过, 请求拦截, 隐私保护