47 lines
2.3 KiB
Markdown
47 lines
2.3 KiB
Markdown
|
|
# SQL 注入:format! 拼 SQL 的连锁风险
|
|||
|
|
|
|||
|
|
## 问题描述
|
|||
|
|
|
|||
|
|
`src/routes/charge_orders.rs` 的 `build_count_query` 和 `build_list_query` 两个辅助函数存在 SQL 注入漏洞。`battery_id` 参数通过 `format!` 直接拼入 SQL 字符串,攻击者可注入任意 SQL。
|
|||
|
|
|
|||
|
|
## 根因分析
|
|||
|
|
|
|||
|
|
**核心问题**:代码演进过程中出现了"双重 SQL 构建"模式。
|
|||
|
|
|
|||
|
|
1. `list_charge_orders` 主函数已经正确使用 `?` 占位符拼接了 `query` 和 `count_query`
|
|||
|
|
2. 但 `build_count_query` / `build_list_query` **完全忽略**了主函数预拼接的 SQL 字符串
|
|||
|
|
3. 它们自行用 `format!` 重建查询,导致所有 `format!` 拼接的参数(`cabinet_id`、`compartment_id`、`battery_id`、`status`、`LIMIT/OFFSET`)都存在注入风险
|
|||
|
|
4. 两个函数还包含大量重复的分支逻辑(~100 行),不同分支的绑定顺序不一致,难以维护
|
|||
|
|
|
|||
|
|
**根本原因**:重构时只改了 `list_charge_orders` 主函数,没同步改造辅助函数,导致新旧模式并存。
|
|||
|
|
|
|||
|
|
## 解决方案
|
|||
|
|
|
|||
|
|
1. **删除重复逻辑**:让 `build_count_query` / `build_list_query` 直接使用主函数预拼接的 SQL 字符串
|
|||
|
|
2. **统一绑定顺序**:按固定顺序(`cabinet_id → compartment_id → battery_id → status → start_time → end_time → LIMIT/OFFSET`)bind 参数,与主函数追加 `?` 占位符的顺序一致
|
|||
|
|
3. **LIKE 值处理**:百分号在 bind 值中拼接(`format!("%{}%", bid)`),不进 SQL 文本
|
|||
|
|
4. **消除未使用变量**:`if let Some(x)` 改为 `.is_some()`,避免未使用变量告警
|
|||
|
|
|
|||
|
|
## 模式/反模式
|
|||
|
|
|
|||
|
|
### 反模式 ❌:format! + SQL 字符串拼接
|
|||
|
|
```rust
|
|||
|
|
// 注入风险
|
|||
|
|
query.push_str(&format!(" AND battery_id LIKE '%{}%'", bid));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 模式 ✅:主函数拼占位符,辅助函数按序 bind
|
|||
|
|
```rust
|
|||
|
|
// 主函数:统一追加 ? 占位符
|
|||
|
|
if params.battery_id.is_some() {
|
|||
|
|
query.push_str(" AND battery_id LIKE ?");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 辅助函数:按固定顺序 bind 值
|
|||
|
|
if let Some(ref bid) = params.battery_id {
|
|||
|
|
q = q.bind(format!("%{}%", bid));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 模式 ✅:SQL 构建统一入口
|
|||
|
|
所有的 SQL 构建逻辑放在一个地方(主函数),辅助函数只负责 bind 参数,不做 WHERE 条件判断。这样改了主函数就改了所有查询路径,不会出现新旧模式不一致。
|