## 路径穿越 + SSRF（High）

### 触发信号

- 文件路径来自用户输入（`open`、`readFile`、`send_file`、`fs.readFile` 参数可控）
- URL 来自用户输入且发起请求（`fetch`、`http.get`、`requests.get`、`urllib.open`）
- 重定向目标来自用户输入（`redirect(req.query.url)`、`Location: user_input`）

### 关键 Sink

| 漏洞类型 | Sink 函数 |
|---------|----------|
| 路径穿越 | `open(path)`、`readFile(path)`、`writeFile(path)`、`send_file(path)`、`fs.stat(path)` |
| SSRF | `fetch(url)`、`http.get(url)`、`requests.get(url)`、`axios(url)`、`url.openConnection()` |
| 开放重定向 | `redirect(url)`、`res.redirect(url)`、`response.sendRedirect(url)`、`Location: url` |

### 判定流程

**路径穿越**：
1. **识别 Sink**：`Read` 提取文件操作函数调用，标记文件路径参数
2. **反向追踪 Source**：`find_references` 追踪路径参数来源，判断是否来自用户输入
3. **检查路径验证**：`Read` 检查路径参数是否经过：
   - 路径规范化后校验（`realpath`/`resolve` 后检查是否在允许目录内）
   - 扩展名白名单（仅允许特定文件类型）
   - 路径拼接基目录（`path.join(BASE_DIR, filename)` 后校验）
4. **验证绕过风险**：`Read` 判断是否可绕过（如 `../` 被过滤但 `..\/` 未过滤、URL 编码绕过）

**SSRF**：
1. **识别 Sink**：`Read` 提取 HTTP 请求函数调用，标记 URL 参数
2. **反向追踪 Source**：`find_references` 追踪 URL 来源，判断是否来自用户输入
3. **检查 URL 验证**：`Read` 确认是否有：
   - URL 白名单/域名白名单
   - 协议限制（仅允许 http/https，禁止 file/gopher/dict）
   - 内网 IP 过滤（禁止 127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16）
   - DNS 重绑定防护

**开放重定向**：
1. **识别 Sink**：`Read` 提取重定向操作，标记目标 URL 参数
2. **检查 URL 验证**：`Read` 确认是否校验了目标 URL 的域名（白名单或相对路径限制）

### 证据构造

确认缺陷后，输出需包含：
- Source（用户输入来源，含文件:行号）
- Sink（危险操作位置，含文件:行号）
- 缺失的验证（路径规范化、URL 白名单、内网过滤）
- 利用场景（`../../etc/passwd`、`http://169.254.169.254/metadata`、`https://evil.com?redirect=trustedsite`）

### 排除

- 路径已使用 `realpath` + `startsWith(BASE_DIR)` 校验
- URL 已有域名白名单 + 协议限制 + 内网 IP 过滤
- 重定向仅允许相对路径（`/path` 而非完整 URL）
- 已使用 UUID/自增 ID 替代用户提供的文件名
