@comate/zulu 1.3.3 → 1.3.4-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/comate-engine/assets/skills/auto-commit-comate/SKILL.md +42 -62
- package/comate-engine/assets/skills/auto-commit-comate/references/data_structures.md +69 -75
- package/comate-engine/assets/skills/auto-commit-comate/references/interaction_instruction.md +220 -0
- package/comate-engine/assets/skills/auto-commit-comate/scripts/build_git_commit_payload.py +195 -0
- package/comate-engine/assets/skills/auto-commit-comate/scripts/build_icafe_cards_payload.py +80 -0
- package/comate-engine/assets/skills/auto-commit-comate/scripts/cache_manager.py +69 -0
- package/comate-engine/assets/skills/auto-commit-comate/scripts/git_diff_cli.py +5 -0
- package/comate-engine/assets/skills/auto-commit-comate/scripts/match_card_cli.py +5 -1
- package/comate-engine/assets/skills/auto-commit-comate/scripts/payload_validators.py +309 -0
- package/comate-engine/assets/skills/code-security-comate/SKILL.md +2 -1
- package/comate-engine/assets/skills/code-security-comate/references/vul_repair-go_sql_injection.md +627 -5
- package/comate-engine/assets/skills/code-security-comate/references/vul_repair-java_sql_injection.md +545 -21
- package/comate-engine/assets/skills/code-security-comate/references/vul_repair-php_sql_injection.md +596 -13
- package/comate-engine/assets/skills/code-security-comate/references/vul_repair-python_sql_injection.md +480 -82
- package/comate-engine/assets/skills/code-security-comate/scripts/http_client.py +10 -2
- package/comate-engine/assets/skills/code-security-comate/scripts/repair_vulnerability.py +12 -10
- package/comate-engine/assets/skills/code-security-comate/scripts/report_chat.py +1 -1
- package/comate-engine/assets/skills/comate-docs-comate/SKILL.md +70 -105
- package/comate-engine/assets/skills/comate-docs-comate/references/doc-map-extended.md +52 -7
- package/comate-engine/assets/skills/comate-docs-comate/references/models-and-billing.md +45 -26
- package/comate-engine/assets/skills/comate-docs-comate/references/product-overview.md +60 -14
- package/comate-engine/assets/skills/create-image-comate/SKILL.md +7 -7
- package/comate-engine/assets/skills/get-ugate-token-comate/SKILL.md +2 -0
- package/comate-engine/node_modules/@comate/plugin-engine/dist/index.js +1 -1
- package/comate-engine/node_modules/@comate/plugin-host/dist/index-7HvPXRep.js +1 -0
- package/comate-engine/node_modules/@comate/plugin-host/dist/index.js +1 -1
- package/comate-engine/node_modules/@comate/plugin-host/dist/main.js +1 -1
- package/comate-engine/node_modules/@comate/plugin-host/dist/user-DVjAG3wH.js +44 -0
- package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.js +8 -8
- package/comate-engine/server.js +378 -266
- package/dist/bundle/index.js +3 -3
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
python sqli 漏洞修复知识
|
|
2
2
|
|
|
3
3
|
# python sqli 漏洞修复最佳实践
|
|
4
|
-
|
|
4
|
+
采用先分析漏洞报告->分析数据传递链路->判断是否存在依赖->分析修复方式(判断是否可以修复、使用哪种修复方式)->修复漏洞->验证修复代码可编译且逻辑正确
|
|
5
5
|
|
|
6
6
|
注意:**不要做与漏洞修复无关的代码改动**。
|
|
7
7
|
|
|
@@ -14,16 +14,37 @@ python sqli 漏洞修复知识
|
|
|
14
14
|
3. 无法确认修复位置是否在真实数据流 sink 路径上(此时应先回到数据流定位,不盲改)。
|
|
15
15
|
|
|
16
16
|
### 2. 可修复场景
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
#### 修复方式选择
|
|
18
|
+
1. 污点变量作为「值」(如 `WHERE id = {id}`)→ 使用预编译(占位符 + params 参数)。
|
|
19
|
+
2. 污点变量作为「结构」(如 `ORDER BY` / 表名 / 列名)→ 定义过滤函数 safeFilter,使用正则过滤。
|
|
20
|
+
3. `IN` 子句参数 → 按元素数量展开占位符(如 `%s,%s,%s`),不允许单占位符绑定数组。
|
|
21
|
+
4. DDL/DCL 语句(`CREATE` / `DROP` / `SHOW` / `GRANT`)→ 不支持预编译,必须使用正则过滤。
|
|
22
|
+
5. 同一参数只采用一种修复方式,不要叠加"过滤 + 预编译 + 转义"。
|
|
23
|
+
|
|
24
|
+
#### 修复位置选择
|
|
25
|
+
6. Sink 是通用封装函数(如 `execute_sql`、`db.query`)→ 改造该函数支持参数化。
|
|
26
|
+
7. Sink 是业务代码直接调用底层 API → 在当前位置直接修复。
|
|
27
|
+
8. 若 SQL 在多个函数复用,需一并分析其他调用点,避免只修一处。
|
|
28
|
+
9. 同一污点变量流向多个 SQL → 每个 SQL 都要修复。
|
|
29
|
+
|
|
30
|
+
#### 修复规范
|
|
31
|
+
10. 仅在当前数据流相关文件内修复,不创建新的代码文件。
|
|
32
|
+
11. 同文件已有过滤函数/正则时优先复用。
|
|
33
|
+
12. 需要改函数签名时,要保证现有调用方兼容(加默认参数或同步改调用方)。
|
|
34
|
+
13. 修复前先确认当前 DB API 的占位符风格(`%s` / `?` / `:name`),禁止猜测。
|
|
35
|
+
14. 禁止直接对完整 SQL 做"整体过滤",应对拼接进 SQL 的污点参数做防护。
|
|
36
|
+
15. 修复后必须重新扫描验证,确认漏洞数归零。
|
|
37
|
+
|
|
38
|
+
#### 参数类型速查表
|
|
39
|
+
|场景|推荐策略|原因|
|
|
40
|
+
|-|-|-|
|
|
41
|
+
|SQL 标识符(表名/列名/数据库名/索引名)|正则 + 白名单|格式验证 + 值验证,双重保障|
|
|
42
|
+
|SELECT 列表|正则 + 白名单|允许通配符 `*` 和多列,列名需白名单验证|
|
|
43
|
+
|ORDER BY/GROUP BY|正则 + 白名单|格式验证 + 列名白名单 + 方向关键字(ASC/DESC)|
|
|
44
|
+
|LIMIT/OFFSET|严格正则|只允许数字,不需要白名单|
|
|
45
|
+
|WHERE 条件|**参数化查询**|正则过滤仍有风险,参数化最安全|
|
|
46
|
+
|IN 子句|参数化查询优先|优先参数化,其次严格正则|
|
|
47
|
+
|DDL/DCL(CREATE/DROP/SHOW)|正则过滤|不支持预编译|
|
|
27
48
|
|
|
28
49
|
---
|
|
29
50
|
|
|
@@ -34,46 +55,12 @@ python sqli 漏洞修复知识
|
|
|
34
55
|
|
|
35
56
|
### 2. 使用规范
|
|
36
57
|
1. 过滤结果必须由临时变量承接,后续拼接 SQL 只能使用过滤后的变量。
|
|
37
|
-
2.
|
|
58
|
+
2. 禁止"先赋值污点变量,再覆盖为安全值"的写法。
|
|
38
59
|
3. 非法输入必须抛错/返回错误,不能静默放行。
|
|
39
60
|
4. 对对象参数,建议返回安全对象后再向下传递。
|
|
40
61
|
|
|
41
|
-
|
|
42
|
-
```python
|
|
43
|
-
import re
|
|
44
|
-
|
|
45
|
-
SAFE_SQL_PATTERN = r'^[\w\s\[\]"`$.,*]+$'
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def safeFilterOrderBy(order_by: str) -> str:
|
|
49
|
-
if not isinstance(order_by, str) or not re.fullmatch(SAFE_SQL_PATTERN, order_by):
|
|
50
|
-
raise ValueError("invalid order by")
|
|
51
|
-
return order_by
|
|
52
|
-
|
|
62
|
+
具体过滤函数实现见第 IV.4 节。
|
|
53
63
|
|
|
54
|
-
safe_order = safeFilterOrderBy(request.GET.get("sort", "id DESC"))
|
|
55
|
-
sql = f"SELECT * FROM users ORDER BY {safe_order}"
|
|
56
|
-
cursor.execute(sql)
|
|
57
|
-
```
|
|
58
|
-
### 4. 对象示例
|
|
59
|
-
```python
|
|
60
|
-
import re
|
|
61
|
-
|
|
62
|
-
SAFE_SQL_PATTERN = r'^[\w\s\[\]"`$.,*]+$'
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def safeValidateQuery(query: dict) -> dict:
|
|
66
|
-
safe_query = dict(query)
|
|
67
|
-
sort_value = safe_query.get("sort")
|
|
68
|
-
if sort_value is not None and not re.fullmatch(SAFE_SQL_PATTERN, sort_value):
|
|
69
|
-
raise ValueError("invalid sort")
|
|
70
|
-
return safe_query
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
safe_query = safeValidateQuery(query)
|
|
74
|
-
sql = f"SELECT * FROM users ORDER BY {safe_query.get('sort', 'id DESC')}"
|
|
75
|
-
cursor.execute(sql)
|
|
76
|
-
```
|
|
77
64
|
---
|
|
78
65
|
|
|
79
66
|
## III. Python 常见修复方案
|
|
@@ -101,22 +88,8 @@ cursor.execute(sql, tuple(ids))
|
|
|
101
88
|
rows = cursor.fetchall()
|
|
102
89
|
```
|
|
103
90
|
### 3. 结构化片段过滤(表名/列名/排序)
|
|
104
|
-
|
|
105
|
-
import re
|
|
106
|
-
|
|
107
|
-
SAFE_SQL_PATTERN = r'^[\w\s\[\]"`$.,*]+$'
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def safeFilterIdentifier(value: str) -> str:
|
|
111
|
-
if not re.fullmatch(SAFE_SQL_PATTERN, value):
|
|
112
|
-
raise ValueError("invalid identifier")
|
|
113
|
-
return value
|
|
91
|
+
结构化 SQL 片段(表名、列名、ORDER BY 等)不支持参数化,必须使用正则过滤。具体实现见第 IV.4 节。
|
|
114
92
|
|
|
115
|
-
|
|
116
|
-
sort_col = safeFilterIdentifier(request.GET.get("sort", "id DESC"))
|
|
117
|
-
sql = f"SELECT id, username FROM users ORDER BY {sort_col}"
|
|
118
|
-
cursor.execute(sql)
|
|
119
|
-
```
|
|
120
93
|
### 4. Django 原生 SQL 场景
|
|
121
94
|
```python
|
|
122
95
|
from django.db import connection
|
|
@@ -151,17 +124,358 @@ import pandas as pd
|
|
|
151
124
|
sql = "SELECT * FROM users WHERE name = ?"
|
|
152
125
|
df = pd.read_sql(sql, conn, params=[user_input])
|
|
153
126
|
```
|
|
127
|
+
### 7. 公共 DB 封装优先改造(批量修复首选)
|
|
128
|
+
适用条件:同一项目存在多个 `cursor.execute(sql)` sink,且调用方式高度相似。
|
|
129
|
+
|
|
130
|
+
执行步骤:
|
|
131
|
+
|
|
132
|
+
1. 先改公共数据库封装方法签名,统一支持参数化(如 `query(sql, params=None)` / `execute(sql, params=None)`)。
|
|
133
|
+
2. 在封装内部统一走参数化执行(有 `params` 时 `cursor.execute(sql, params)`)。
|
|
134
|
+
3. 批量替换调用点:将 f-string / `%` / `.format()` 拼接改为占位符 + `params`。
|
|
135
|
+
4. `IN` 子句按元素数量展开占位符,不允许列表绑定到单占位符。
|
|
136
|
+
5. 保持最小改动:不改业务语义、不改返回结构、不改事务边界。
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
def query(self, sql: str, params=None):
|
|
140
|
+
with self.conn.cursor() as cursor:
|
|
141
|
+
if params is None:
|
|
142
|
+
cursor.execute(sql)
|
|
143
|
+
else:
|
|
144
|
+
cursor.execute(sql, params)
|
|
145
|
+
return cursor.fetchall()
|
|
146
|
+
```
|
|
147
|
+
```python
|
|
148
|
+
# Bad
|
|
149
|
+
sql = f"SELECT * FROM users WHERE id = '{user_id}'"
|
|
150
|
+
rows = db.query(sql)
|
|
151
|
+
|
|
152
|
+
# Fix
|
|
153
|
+
sql = "SELECT * FROM users WHERE id = %s"
|
|
154
|
+
rows = db.query(sql, (user_id,))
|
|
155
|
+
```
|
|
154
156
|
---
|
|
155
157
|
|
|
156
|
-
## IV.
|
|
157
|
-
|
|
158
|
+
## IV. 正则过滤规范与白名单策略
|
|
159
|
+
> 本节用于指导修复 SQL 注入漏洞时,如何选择白名单或正则过滤,确保**最小化改动范围**。
|
|
160
|
+
### 1. 核心决策树
|
|
161
|
+
```
|
|
162
|
+
用户输入需要过滤?
|
|
163
|
+
│
|
|
164
|
+
├─ 是 SQL 标识符(表名/列名/数据库名/索引名)?
|
|
165
|
+
│ └─ → 正则验证格式 + 白名单验证(双重保障)
|
|
166
|
+
│
|
|
167
|
+
├─ 是 SELECT 列表?
|
|
168
|
+
│ └─ → 正则验证格式 + 提取列名后白名单验证
|
|
169
|
+
│
|
|
170
|
+
├─ 是 WHERE 条件/IN 子句?
|
|
171
|
+
│ └─ → 使用参数化查询(强烈推荐)
|
|
172
|
+
│
|
|
173
|
+
├─ 是 ORDER BY/GROUP BY?
|
|
174
|
+
│ └─ → 正则验证格式 + 提取列名后白名单验证
|
|
175
|
+
│
|
|
176
|
+
├─ 是 LIMIT/OFFSET?
|
|
177
|
+
│ └─ → 正则验证纯数字
|
|
178
|
+
│
|
|
179
|
+
└─ 其他场景
|
|
180
|
+
└─ → 根据具体需求选择白名单或精确正则
|
|
181
|
+
```
|
|
182
|
+
### 2. 白名单数据来源与处理策略
|
|
183
|
+
#### 白名单数据从哪里来?
|
|
184
|
+
|来源|说明|示例|
|
|
185
|
+
|-|-|-|
|
|
186
|
+
|**数据库元数据查询**|从数据库系统表查询实际存在的表/列名|`SELECT column_name FROM information_schema.columns WHERE table_name = 'users'`|
|
|
187
|
+
|**代码静态分析**|扫描代码中定义的实体类、ORM 模型|JPA `@Entity`、MyBatis mapper、SQLAlchemy Model|
|
|
188
|
+
|**配置文件/注解**|从配置文件或注解中提取允许的字段列表|`@AllowedFields({"id", "name", "email"})`|
|
|
189
|
+
|**API 文档/接口定义**|从 Swagger/OpenAPI 等文档中提取字段定义||
|
|
190
|
+
|
|
191
|
+
#### 白名单不完整时的处理策略
|
|
192
|
+
```
|
|
193
|
+
白名单验证失败?
|
|
194
|
+
│
|
|
195
|
+
├─ 白名单数据来源明确(如元数据查询、ORM 模型)?
|
|
196
|
+
│ └─ → 拒绝输入,记录日志,提示用户
|
|
197
|
+
│
|
|
198
|
+
├─ 白名单数据来源不明确(如硬编码、不完整)?
|
|
199
|
+
│ └─ → 降级为仅正则验证,记录警告日志
|
|
200
|
+
│
|
|
201
|
+
└─ 无法获取白名单(如动态表名)?
|
|
202
|
+
└─ → 仅正则验证格式,加强输入长度限制
|
|
203
|
+
```
|
|
204
|
+
**修复决策原则**:
|
|
205
|
+
|
|
206
|
+
1. **优先获取白名单**:如果代码中有 ORM 模型或实体类定义,优先从中提取字段列表
|
|
207
|
+
2. **正则兜底**:当白名单获取困难或不完整时,使用严格正则作为安全底线
|
|
208
|
+
3. **记录决策原因**:在代码注释中说明为什么选择正则而非白名单
|
|
209
|
+
|
|
210
|
+
### 3. 不同数据库的差异说明
|
|
211
|
+
|数据库|标识符引用符|参数占位符|特殊注意事项|
|
|
212
|
+
|-|-|-|-|
|
|
213
|
+
|**MySQL**|反引号 ```|`?` 或 `:name`|支持反引号包裹标识符,如 ``user_name``|
|
|
214
|
+
|**PostgreSQL**|双引号 `"`|`$1, $2, ...` 或 `?`|支持双引号包裹标识符,如 `"user_name"`|
|
|
215
|
+
|**SQL Server**|方括号 `[` `]`|`@param` 或 `?`|支持方括号包裹标识符,如 `[user_name]`|
|
|
216
|
+
|**Oracle**|双引号 `"`|`:name` 或 `?`|标识符默认大写,双引号区分大小写|
|
|
217
|
+
|**SQLite**|双引号 `"` 或方括号 `[` `]`|`?` 或 `:name`|兼容多种引用符|
|
|
158
218
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
219
|
+
**修复原则**:根据当前使用的数据库类型,选择对应的引用符和参数占位符。
|
|
220
|
+
|
|
221
|
+
### 4. 各场景正则与实现
|
|
222
|
+
#### 5.1 SELECT 列名列表 `__SELECT_FILTER__`
|
|
223
|
+
**适用场景**:SELECT 后面的列名列表(如 `SELECT id,name,email FROM users` 或 `SELECT users.id,users.name FROM users`)
|
|
224
|
+
|
|
225
|
+
**正则(通用版,支持引用符)**:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
^\*|((`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(,\s*(`[^`]+`|"[^"]+"|\w+\.\w+|\w+))*$
|
|
229
|
+
```
|
|
230
|
+
**支持格式**:
|
|
163
231
|
|
|
164
|
-
|
|
232
|
+
* `*` (通配符)
|
|
233
|
+
* `id` (单列)
|
|
234
|
+
* `users.id` (表名.列名)
|
|
235
|
+
* ``user_name`` (MySQL 反引号)
|
|
236
|
+
* `"user_name"` (PostgreSQL/Oracle 双引号)
|
|
237
|
+
* `id,name,email` (多列,逗号分隔)
|
|
238
|
+
* `users.id, users.name, users.email` (带表名前缀的多列)
|
|
239
|
+
* `id, name, email` (允许逗号前后空格)
|
|
240
|
+
|
|
241
|
+
**Python 修复示例**:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
import re
|
|
245
|
+
|
|
246
|
+
SELECT_PATTERN = r'^\*|((`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(,\s*(`[^`]+`|"[^"]+"|\w+\.\w+|\w+))*$'
|
|
247
|
+
ALLOWED_COLUMNS = {"id", "name", "email", "created_at"} # 白名单,可选
|
|
248
|
+
|
|
249
|
+
def safeFilterSelect(select: str) -> str:
|
|
250
|
+
select = select.strip()
|
|
251
|
+
if not re.fullmatch(SELECT_PATTERN, select):
|
|
252
|
+
raise ValueError("invalid select")
|
|
253
|
+
if select == "*":
|
|
254
|
+
return select
|
|
255
|
+
# 提取列名并验证白名单(可选)
|
|
256
|
+
if ALLOWED_COLUMNS:
|
|
257
|
+
for part in select.split(","):
|
|
258
|
+
clean = part.strip().strip('`"')
|
|
259
|
+
col = clean.split(".")[-1] if "." in clean else clean
|
|
260
|
+
if col.lower() not in ALLOWED_COLUMNS:
|
|
261
|
+
raise ValueError(f"column {col} not allowed")
|
|
262
|
+
return select
|
|
263
|
+
```
|
|
264
|
+
#### 5.2 SQL 标识符(表名/列名/数据库名/索引名)`__IDENTIFIER_FILTER__`
|
|
265
|
+
**适用场景**:表名、列名、数据库名、索引名、视图名等 SQL 标识符
|
|
266
|
+
|
|
267
|
+
**正则(通用版,支持引用符)**:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
^[A-Za-z_][A-Za-z0-9_]{0,127}$|^`[^`]{1,128}`$|^"[^"]{1,128}"$
|
|
271
|
+
```
|
|
272
|
+
**支持格式**:
|
|
273
|
+
|
|
274
|
+
* `user` (小写字母开头)
|
|
275
|
+
* `User123` (字母数字)
|
|
276
|
+
* `user_name` (下划线)
|
|
277
|
+
* `_private` (下划线开头,部分数据库支持)
|
|
278
|
+
* ``user_name`` (MySQL 反引号)
|
|
279
|
+
* `"user_name"` (PostgreSQL/Oracle 双引号)
|
|
280
|
+
* `[user_name]` (SQL Server 方括号)
|
|
281
|
+
* 长度限制:1-128 字符
|
|
282
|
+
|
|
283
|
+
**Python 修复示例**:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
import re
|
|
287
|
+
|
|
288
|
+
IDENTIFIER_PATTERN = r'^[A-Za-z_][A-Za-z0-9_]{0,127}$|^`[^`]{1,128}`$|^"[^"]{1,128}"$|^\[[^\]]{1,128}\]$'
|
|
289
|
+
ALLOWED_TABLES = {"users", "orders", "products"} # 白名单,可选
|
|
290
|
+
|
|
291
|
+
def safeFilterIdentifier(identifier: str) -> str:
|
|
292
|
+
identifier = identifier.strip()
|
|
293
|
+
if not re.fullmatch(IDENTIFIER_PATTERN, identifier):
|
|
294
|
+
raise ValueError("invalid identifier")
|
|
295
|
+
# 去除引用符后验证白名单(可选)
|
|
296
|
+
clean = re.sub(r'^[`"\[]|[`"\]]$', '', identifier)
|
|
297
|
+
if ALLOWED_TABLES and clean.lower() not in ALLOWED_TABLES:
|
|
298
|
+
raise ValueError(f"identifier {clean} not allowed")
|
|
299
|
+
return identifier
|
|
300
|
+
```
|
|
301
|
+
#### 5.3 ORDER BY `__ORDER_FILTER__`
|
|
302
|
+
**适用场景**:ORDER BY 后的字段(如 `ORDER BY name ASC, id DESC` 或 `ORDER BY users.name ASC, users.id DESC`)
|
|
303
|
+
|
|
304
|
+
**正则(通用版,支持引用符)**:
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
^((`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(\s+(ASC|DESC))?(,\s*(`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(\s+(ASC|DESC))?)*$
|
|
308
|
+
```
|
|
309
|
+
**支持格式**:
|
|
310
|
+
|
|
311
|
+
* `name` (单列)
|
|
312
|
+
* `users.name` (表名.列名)
|
|
313
|
+
* ``user_name`` (MySQL 反引号)
|
|
314
|
+
* `"user_name"` (PostgreSQL/Oracle 双引号)
|
|
315
|
+
* `name ASC` / `name DESC` (带方向)
|
|
316
|
+
* `users.name ASC` / `users.name DESC` (带表名前缀和方向)
|
|
317
|
+
* `name ASC, id DESC` (多列)
|
|
318
|
+
|
|
319
|
+
**Python 修复示例**:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
import re
|
|
323
|
+
|
|
324
|
+
ORDER_PATTERN = r'^((`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(\s+(ASC|DESC))?(,\s*(`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(\s+(ASC|DESC))?)*$'
|
|
325
|
+
|
|
326
|
+
def safeFilterOrderBy(order: str) -> str:
|
|
327
|
+
order = order.strip()
|
|
328
|
+
if not re.fullmatch(ORDER_PATTERN, order, re.IGNORECASE):
|
|
329
|
+
raise ValueError("invalid order by")
|
|
330
|
+
return order
|
|
331
|
+
```
|
|
332
|
+
#### 5.4 GROUP BY `__GROUP_FILTER__`
|
|
333
|
+
**适用场景**:GROUP BY 后的字段(如 `GROUP BY name, age` 或 `GROUP BY users.name, users.age`)
|
|
334
|
+
|
|
335
|
+
**正则(通用版,支持引用符)**:
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
^((`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(,\s*(`[^`]+`|"[^"]+"|\w+\.\w+|\w+))*$
|
|
339
|
+
```
|
|
340
|
+
**支持格式**:
|
|
341
|
+
|
|
342
|
+
* `name` (单列)
|
|
343
|
+
* `users.name` (表名.列名)
|
|
344
|
+
* ``user_name`` (MySQL 反引号)
|
|
345
|
+
* `"user_name"` (PostgreSQL/Oracle 双引号)
|
|
346
|
+
* `name, age` (多列)
|
|
347
|
+
* `users.name, users.age` (带表名前缀的多列)
|
|
348
|
+
|
|
349
|
+
#### 5.5 LIMIT/OFFSET `__LIMIT_FILTER__`
|
|
350
|
+
**适用场景**:LIMIT、OFFSET 后的数值(如 `LIMIT 10`、`LIMIT 1,10`)
|
|
351
|
+
|
|
352
|
+
**正则**:`^\d+\s*,\s*\d+$|^\d+$`
|
|
353
|
+
|
|
354
|
+
**支持格式**:
|
|
355
|
+
|
|
356
|
+
* `10` (单数字)
|
|
357
|
+
* `1,10` (带偏移量)
|
|
358
|
+
* `1, 10` (允许逗号前后空格)
|
|
359
|
+
|
|
360
|
+
**Python 修复示例**:
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
import re
|
|
364
|
+
|
|
365
|
+
def safeFilterLimit(limit: str) -> str:
|
|
366
|
+
limit = limit.strip()
|
|
367
|
+
if not re.fullmatch(r'^\d+\s*,\s*\d+$|^\d+$', limit):
|
|
368
|
+
raise ValueError("invalid limit")
|
|
369
|
+
return limit
|
|
370
|
+
```
|
|
371
|
+
#### 5.6 WHERE 条件 `__WHERE_FILTER__`
|
|
372
|
+
**适用场景**:WHERE 子句、条件判断
|
|
373
|
+
|
|
374
|
+
**策略**:**放弃正则,使用参数化查询**
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
# ❌ 错误:字符串拼接
|
|
378
|
+
sql = f"SELECT * FROM users WHERE name = '{name}'"
|
|
379
|
+
|
|
380
|
+
# ✅ 正确:参数化查询
|
|
381
|
+
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
|
|
382
|
+
```
|
|
383
|
+
#### 5.7 IN 子句 `__IN_FILTER__`
|
|
384
|
+
**适用场景**:IN 后面的值列表(如 `IN (1,2,3)`)
|
|
385
|
+
|
|
386
|
+
**策略**:优先参数化查询,其次严格正则
|
|
387
|
+
|
|
388
|
+
**正则**:
|
|
389
|
+
|
|
390
|
+
* 数字:`^\(\s*\d+(\s*,\s*\d+)*\s*\)$`
|
|
391
|
+
* 字符串:`^\(\s*'[^']*'(\s*,\s*'[^']*')*\s*\)$`
|
|
392
|
+
|
|
393
|
+
**支持格式**:
|
|
394
|
+
|
|
395
|
+
* `(1,2,3)` (数字列表)
|
|
396
|
+
* `('a','b','c')` (字符串列表)
|
|
397
|
+
* `(1, 2, 3)` (允许逗号前后空格)
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
# ✅ 优先:参数化查询
|
|
401
|
+
ids = [1, 2, 3]
|
|
402
|
+
placeholders = ",".join(["%s"] * len(ids))
|
|
403
|
+
cursor.execute(f"SELECT * FROM users WHERE id IN ({placeholders})", tuple(ids))
|
|
404
|
+
|
|
405
|
+
# ⚠️ 备选:严格正则验证(仅当无法参数化时)
|
|
406
|
+
import re
|
|
407
|
+
if not re.fullmatch(r'^\(\s*\d+(\s*,\s*\d+)*\s*\)$', in_clause):
|
|
408
|
+
raise ValueError("invalid IN clause")
|
|
409
|
+
```
|
|
410
|
+
#### 5.8 方向关键字 `__DIRECTION_FILTER__`
|
|
411
|
+
**适用场景**:ORDER BY 后的 ASC/DESC
|
|
412
|
+
|
|
413
|
+
**正则**:`^(asc|desc)$`
|
|
414
|
+
|
|
415
|
+
**支持格式**:
|
|
416
|
+
|
|
417
|
+
* `ASC` / `DESC` / `asc` / `desc` (不区分大小写)
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
import re
|
|
421
|
+
|
|
422
|
+
def safeFilterDirection(direction: str) -> str:
|
|
423
|
+
if not re.fullmatch(r'^(asc|desc)$', direction, re.IGNORECASE):
|
|
424
|
+
raise ValueError("invalid direction")
|
|
425
|
+
return direction
|
|
426
|
+
```
|
|
427
|
+
#### 5.9 纯字母数字标识符 `__W_FILTER__`
|
|
428
|
+
**适用场景**:简单的表名、列名(无特殊字符)
|
|
429
|
+
|
|
430
|
+
**正则**:`^[\w-]+$`
|
|
431
|
+
|
|
432
|
+
**支持格式**:
|
|
433
|
+
|
|
434
|
+
* `user_name` (下划线)
|
|
435
|
+
* `User123` (字母数字)
|
|
436
|
+
* `table-1` (连字符)
|
|
437
|
+
|
|
438
|
+
#### 5.10 路径过滤 `__PATH_FILTER__`
|
|
439
|
+
**适用场景**:文件路径、URL 路径
|
|
440
|
+
|
|
441
|
+
**正则**:`^[\w/-]+$`
|
|
442
|
+
|
|
443
|
+
**支持格式**:
|
|
444
|
+
|
|
445
|
+
* `/path/to/file` (绝对路径)
|
|
446
|
+
* `path/to/file` (相对路径)
|
|
447
|
+
|
|
448
|
+
#### 5.11 运算符 `__OP_FILTER__`
|
|
449
|
+
**适用场景**:算术运算符(如 `score+1`)
|
|
450
|
+
|
|
451
|
+
**正则**:`[+\-*/%]`
|
|
452
|
+
|
|
453
|
+
**支持格式**:
|
|
454
|
+
|
|
455
|
+
* `+` `-` `*` `/` `%` (基本算术运算符)
|
|
456
|
+
|
|
457
|
+
### 5. 正则速查表
|
|
458
|
+
|过滤器|正则|说明|
|
|
459
|
+
|-|-|-|
|
|
460
|
+
|`__IDENTIFIER_FILTER__`|`^[A-Za-z_][A-Za-z0-9_]{0,127}$|^`[^`]{1,128}`$\|^"[^"]{1,128}"$`|SQL 标识符,支持反引号/双引号,1-128字符|
|
|
461
|
+
|`__SELECT_FILTER__`|`^\*|((`[^`]+`|"[^"]+"|\w+.\w+|\w+)(,\s*(`[^`]+`|"[^"]+"|\w+.\w+|\w+))*$`|允许通配符 `*` 和逗号分隔的列名,支持引用符和 `表名.列名`|
|
|
462
|
+
|`__ORDER_FILTER__`|`^((`[^`]+`|"[^"]+"|\w+.\w+|\w+)(\s+(ASC|DESC))?(,\s*(`[^`]+`|"[^"]+"|\w+.\w+|\w+)(\s+(ASC|DESC))?)*$`|列名 + 方向关键字,支持引用符和 `表名.列名`|
|
|
463
|
+
|`__GROUP_FILTER__`|`^((`[^`]+`|"[^"]+"|\w+.\w+|\w+)(,\s*(`[^`]+`|"[^"]+"|\w+.\w+|\w+))*$`|多列,逗号分隔,支持引用符和 `表名.列名`|
|
|
464
|
+
|`__LIMIT_FILTER__`|`^\d+\s*,\s*\d+$|^\d+$`|纯数字,允许逗号|
|
|
465
|
+
|`__WHERE_FILTER__`|**参数化查询**|不要用正则|
|
|
466
|
+
|`__IN_FILTER__`|`^\(\s*\d+(\s*,\s*\d+)*\s*\)$` 或字符串版本|数字或字符串列表|
|
|
467
|
+
|`__DIRECTION_FILTER__`|`^(asc|desc)$`|ASC 或 DESC|
|
|
468
|
+
|`__W_FILTER__`|`^[\w-]+$`|字母数字+下划线+连字符(通用场景)|
|
|
469
|
+
|`__PATH_FILTER__`|`^[\w/-]+$`|路径字符|
|
|
470
|
+
|`__OP_FILTER__`|`[+\-*/%]`|算术运算符|
|
|
471
|
+
|
|
472
|
+
### 6. 正则过滤重要提示
|
|
473
|
+
1. **所有正则验证前,务必先 **`trim()`** 去除首尾空白字符**
|
|
474
|
+
2. **白名单优先**:列名/表名等固定值优先用白名单
|
|
475
|
+
3. **白名单不完整时降级**:当白名单获取困难或不完整时,使用严格正则作为安全底线
|
|
476
|
+
4. **参数化优先**:WHERE 条件、IN 子句优先用参数化查询
|
|
477
|
+
5. **数据库适配**:根据使用的数据库类型选择对应的引用符和参数占位符
|
|
478
|
+
6. **最小化改动**:只修复漏洞点,不重构其他代码
|
|
165
479
|
|
|
166
480
|
---
|
|
167
481
|
|
|
@@ -171,28 +485,112 @@ df = pd.read_sql(sql, conn, params=[user_input])
|
|
|
171
485
|
3. `IN` 子句占位符数量是否与参数数量一致。
|
|
172
486
|
4. 空数组场景是否有兜底(避免 `IN ()` 语法错误)。
|
|
173
487
|
5. 是否混用了不兼容的占位符风格(`%s` / `?` / `:name`)。
|
|
174
|
-
6. 是否修复了真实 sink
|
|
488
|
+
6. 是否修复了真实 sink 所在函数/文件(不是"改错位置")。
|
|
175
489
|
7. 改动函数签名后,调用方是否同步兼容。
|
|
176
|
-
8.
|
|
490
|
+
8. 是否出现"新旧 SQL 并存"导致旧拼接逻辑覆盖新逻辑。
|
|
177
491
|
9. 是否存在变量作用域问题(分支变量未定义)。
|
|
178
492
|
10. 修复代码是否保持原业务语义(避免把 `UPDATE/DELETE` 改成 `SELECT`)。
|
|
493
|
+
11. 过滤正则是否兼容所有合法业务数据格式(如日期含 `-`、路径含 `/` 等),避免正则过严导致合法请求被拒。
|
|
494
|
+
12. 公共 DB 封装是否已支持 `params` 且被调用点实际使用(不是只改签名)。
|
|
495
|
+
13. 真实 sink(`cursor.execute`)是否仍存在外部可控字符串拼接路径。
|
|
496
|
+
14. 是否完成同文件/同模块同类拼接点批量替换,而非只修单个样例点。
|
|
179
497
|
|
|
180
498
|
---
|
|
181
499
|
|
|
182
500
|
## VI. 高频失败反模式(修复时必须规避)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
501
|
+
### 反模式 1:仍使用字符串拼接 SQL
|
|
502
|
+
```python
|
|
503
|
+
# ❌ 错误:用 f-string / % / format 拼接
|
|
504
|
+
sql = f"SELECT * FROM users WHERE id = {user_id}"
|
|
505
|
+
sql = "SELECT * FROM users WHERE id = %s" % user_id
|
|
506
|
+
sql = "SELECT * FROM users WHERE id = {}".format(user_id)
|
|
507
|
+
|
|
508
|
+
# ✅ 正确:参数化查询
|
|
509
|
+
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
|
|
510
|
+
```
|
|
511
|
+
### 反模式 2:IN 子句直接绑定列表
|
|
512
|
+
```python
|
|
513
|
+
# ❌ 错误:单占位符绑定列表
|
|
514
|
+
ids = [1, 2, 3]
|
|
515
|
+
cursor.execute("SELECT * FROM users WHERE id IN (%s)", (ids,))
|
|
516
|
+
|
|
517
|
+
# ✅ 正确:展开占位符
|
|
518
|
+
ids = [1, 2, 3]
|
|
519
|
+
placeholders = ",".join(["%s"] * len(ids))
|
|
520
|
+
cursor.execute(f"SELECT * FROM users WHERE id IN ({placeholders})", tuple(ids))
|
|
521
|
+
```
|
|
522
|
+
### 反模式 3:修复一个分支,遗漏其他分支
|
|
523
|
+
```python
|
|
524
|
+
# ❌ 错误:只修了 if 分支,else 分支遗漏
|
|
525
|
+
if action == "select":
|
|
526
|
+
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) # 已修复
|
|
527
|
+
else:
|
|
528
|
+
cursor.execute(f"DELETE FROM users WHERE id = {user_id}") # 遗漏!
|
|
529
|
+
|
|
530
|
+
# ✅ 正确:所有分支都修复
|
|
531
|
+
if action == "select":
|
|
532
|
+
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
|
|
533
|
+
else:
|
|
534
|
+
cursor.execute("DELETE FROM users WHERE id = %s", (user_id,))
|
|
535
|
+
```
|
|
536
|
+
### 反模式 4:只改调用层,未改 Sink 层通用函数
|
|
537
|
+
```python
|
|
538
|
+
# ❌ 错误:在调用层加校验,但 insert_helper 仍用拼接
|
|
539
|
+
def insert_helper(table, data):
|
|
540
|
+
sql = f"INSERT INTO {table} ..." # Sink 层仍是拼接!
|
|
541
|
+
cursor.execute(sql)
|
|
542
|
+
|
|
543
|
+
def add_user(name):
|
|
544
|
+
if not re.match(r'^\w+$', name): # 调用层校验
|
|
545
|
+
raise ValueError("invalid")
|
|
546
|
+
insert_helper("users", {"name": name}) # 无效,sink 层未改
|
|
547
|
+
|
|
548
|
+
# ✅ 正确:改造 Sink 层支持参数化
|
|
549
|
+
def insert_helper(table, data, params=None):
|
|
550
|
+
# ... 使用 cursor.execute(sql, params)
|
|
551
|
+
```
|
|
552
|
+
### 反模式 5:新增参数化方法但调用方仍用旧方法
|
|
553
|
+
```python
|
|
554
|
+
# ❌ 错误:新增了 query_param 但调用方还用旧的 query
|
|
555
|
+
class DB:
|
|
556
|
+
def query(self, sql): # 旧方法,拼接
|
|
557
|
+
cursor.execute(sql)
|
|
558
|
+
|
|
559
|
+
def query_param(self, sql, params): # 新方法,参数化
|
|
560
|
+
cursor.execute(sql, params)
|
|
561
|
+
|
|
562
|
+
# 调用方仍使用旧方法
|
|
563
|
+
db.query(f"SELECT * FROM users WHERE id = {user_id}") # 错误!
|
|
564
|
+
|
|
565
|
+
# ✅ 正确:调用方也要改用新方法
|
|
566
|
+
db.query_param("SELECT * FROM users WHERE id = %s", (user_id,))
|
|
567
|
+
```
|
|
568
|
+
### 反模式 6:先使用污点变量,后补过滤(数据流已放行)
|
|
569
|
+
```python
|
|
570
|
+
# ❌ 错误:先用了 user_id,后面才过滤
|
|
571
|
+
sql = f"SELECT * FROM users WHERE id = {user_id}" # 污点已进入 SQL!
|
|
572
|
+
safe_id = safeFilter(user_id) # 过滤太晚,无效
|
|
573
|
+
|
|
574
|
+
# ✅ 正确:先过滤,再使用
|
|
575
|
+
safe_id = safeFilter(user_id) # 先过滤
|
|
576
|
+
sql = f"SELECT * FROM users WHERE id = {safe_id}" # 用过滤后的变量
|
|
577
|
+
```
|
|
578
|
+
### 其他反模式(简述)
|
|
579
|
+
7. 只加引号不做参数化/过滤(引号可被闭合绕过)
|
|
580
|
+
8. 修复一个 sink,遗漏同参数的其他 sink
|
|
581
|
+
9. 改造了 `query` 方法,遗漏了 `update`/`insert`/`execute` 方法
|
|
582
|
+
10. 修复后未重新扫描验证漏洞是否消除
|
|
583
|
+
11. 过滤正则过严拒绝合法输入(如日期 `2024-03-15` 含 `-`,但正则不含 `-`)
|
|
584
|
+
12. 调用不存在的 DB API 或错误假设驱动能力
|
|
585
|
+
13. 仅做黑名单/前缀校验/`strip()`,未落实参数化或结构化白名单
|
|
191
586
|
|
|
192
587
|
---
|
|
193
588
|
|
|
194
589
|
## VII. 最小修复策略(执行建议)
|
|
195
|
-
1. 先定位真实数据流 sink
|
|
590
|
+
1. 先定位真实数据流 sink,并确认是否可优先下沉到公共 DB 封装层修复。
|
|
196
591
|
2. 值参数优先参数化;结构化片段用正则过滤。
|
|
197
|
-
3.
|
|
198
|
-
4.
|
|
592
|
+
3. 若存在多处同类 sink,先改 `query/execute` 封装签名支持 `params`,再批量替换调用点。
|
|
593
|
+
4. 使用 `safeFilter/safeValidate` + 临时变量承接,禁止先用污点后补过滤。
|
|
594
|
+
5. 完成后按 V 节 14 条逐项自检,再提交。
|
|
595
|
+
|
|
596
|
+
---
|
|
@@ -95,15 +95,23 @@ def _request_curl(url, method="GET", headers=None, json_body=None, timeout=120):
|
|
|
95
95
|
for k, v in headers.items():
|
|
96
96
|
cmd += ["-H", "{}: {}".format(k, v)]
|
|
97
97
|
|
|
98
|
+
stdin_bytes = None
|
|
98
99
|
if json_body is not None:
|
|
99
100
|
cmd += ["-H", "Content-Type: application/json; charset=utf-8"]
|
|
100
|
-
|
|
101
|
+
# 通过 stdin 传递请求体,避免命令行参数超长 (OSError: Argument list too long)
|
|
102
|
+
cmd += ["--data-binary", "@-"]
|
|
103
|
+
stdin_bytes = json.dumps(json_body).encode("utf-8")
|
|
101
104
|
|
|
102
105
|
cmd.append(url)
|
|
103
106
|
|
|
104
107
|
logger.info("[curl] %s %s headers=%s", method, url, json.dumps(_log_safe_headers(headers)))
|
|
105
108
|
try:
|
|
106
|
-
proc = subprocess.run(
|
|
109
|
+
proc = subprocess.run(
|
|
110
|
+
cmd,
|
|
111
|
+
input=stdin_bytes,
|
|
112
|
+
capture_output=True,
|
|
113
|
+
timeout=timeout + 10,
|
|
114
|
+
)
|
|
107
115
|
except FileNotFoundError:
|
|
108
116
|
raise RuntimeError(
|
|
109
117
|
"ssl 模块不可用且未找到 curl 命令。"
|