@comate/zulu 1.4.0-beta.5 → 1.4.0-beta.6
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/SKILL.md +2 -0
- package/comate-engine/assets/skills/auto-commit-sandbox-comate/SKILL.md +2 -2
- package/comate-engine/assets/skills/code-security/SKILL.md +110 -41
- package/comate-engine/assets/skills/code-security/references/credential_hosting.md +190 -28
- package/comate-engine/assets/skills/code-security/references/vul_analysis-go_sql_injection.md +149 -0
- package/comate-engine/assets/skills/code-security/references/vul_analysis-java_sql_injection.md +185 -0
- package/comate-engine/assets/skills/code-security/references/vul_analysis-php_sql_injection.md +147 -0
- package/comate-engine/assets/skills/code-security/references/vul_analysis-python_sql_injection.md +143 -0
- package/comate-engine/assets/skills/code-security/references/vul_repair-go_sql_injection.md +2 -2
- package/comate-engine/assets/skills/code-security/references/vul_repair-sca.md +225 -0
- package/comate-engine/assets/skills/code-security/scripts/credential_hosting.py +12 -10
- package/comate-engine/assets/skills/code-security/scripts/credential_open_page.py +125 -0
- package/comate-engine/assets/skills/code-security/scripts/credential_poll.py +12 -9
- package/comate-engine/assets/skills/code-security/scripts/credential_url.py +81 -0
- package/comate-engine/assets/skills/code-security/scripts/ducc/get_claude_session_id.sh +33 -0
- package/comate-engine/assets/skills/code-security/scripts/ducc/open_browser.py +191 -0
- package/comate-engine/assets/skills/code-security/scripts/parse_scan_result.py +99 -16
- package/comate-engine/assets/skills/code-security/scripts/repair_vulnerability.py +66 -13
- package/comate-engine/assets/skills/code-security/scripts/scan_vulnerability.py +44 -12
- package/comate-engine/assets/skills/create-automation/SKILL.md +3 -0
- package/comate-engine/assets/skills/create-subagent/SKILL.md +16 -4
- package/comate-engine/server.js +137 -77
- package/dist/bundle/index.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# 漏洞复核流程
|
|
2
|
+
|
|
3
|
+
你是一个资深的安全审计专家,请对 Go 代码的 SQL 注入漏洞扫描结果进行复核,判断是否为误报。
|
|
4
|
+
|
|
5
|
+
漏洞复核可分为三个步骤:
|
|
6
|
+
|
|
7
|
+
1. 数据读取:仔细阅读污点追踪信息(trace)及相关的代码文件。
|
|
8
|
+
2. 漏洞分析:根据「漏洞分析」的要求判断漏洞是否为误报。
|
|
9
|
+
3. 结果输出:按照格式要求输出复核结果。
|
|
10
|
+
|
|
11
|
+
## 数据读取
|
|
12
|
+
|
|
13
|
+
阅读扫描报告 parsed_result.json,其中包含了详情的漏洞位置、数据流信息以及漏洞类型说明。
|
|
14
|
+
|
|
15
|
+
## 漏洞分析
|
|
16
|
+
|
|
17
|
+
### 分析步骤(请严格按顺序执行,满足终止条件时立即停止)
|
|
18
|
+
**Step 1 - 追踪校验逻辑**
|
|
19
|
+
从污点来源到危险函数,逐行检查是否存在对该参数的过滤/校验代码。
|
|
20
|
+
参考「误报特征」中列举的保护类型进行识别。
|
|
21
|
+
**重要**:校验逻辑可能是间接的,以下两种模式都属于有效校验:
|
|
22
|
+
- **map 查找**:如果污点变量被用作 map 的 key 来查找值,且查找失败会中断执行,则该查找本身就是对污点变量的白名单校验——因为只有 key 恰好等于白名单中某个固定值时才能继续执行。
|
|
23
|
+
- **参数化查询存在性校验**:如果污点变量通过参数化查询(GORM 等 ORM 的 Where 占位符)去数据库查询是否存在匹配记录,且查询无结果会中断执行(return error),则该查询等价于隐式白名单校验。推理链条为:参数化查询本身不会产生 SQL 注入 → 查询要求数据库中存在与污点变量精确匹配的记录 → SQL 注入 payload(如 `' OR 1=1--`)不可能作为合法业务数据存在于数据库中 → payload 无法通过校验 → 后续即使拼接 SQL 也是安全的。**这种模式应判定为 false_positive**。
|
|
24
|
+
|
|
25
|
+
**边界条件**:如果被查询字段是用户可写入的(如 username、title、备注等任意字符串字段)且无字符集限制,攻击者可能预先注册 `admin' OR 1=1--` 这样的合法记录,此时存在性校验不再可靠,**不能据此判定为误报**。仅当被查询字段是系统受控、字符集受限(如纯字母数字 ID、枚举码、内部字典表)时,该模式才成立。
|
|
26
|
+
|
|
27
|
+
如果 Step 1 未发现任何校验逻辑,跳过 Step 2,直接进入 Step 3。
|
|
28
|
+
|
|
29
|
+
**Step 2 - 验证中断有效性**
|
|
30
|
+
如果 Step 1 发现校验逻辑,检查校验失败时是否会中断后续执行。
|
|
31
|
+
需要找到对应的 return 语句(可能在被调用函数内部)。
|
|
32
|
+
**特别注意**:中断判断要考虑间接中断。例如 `value, ok := fixedMap[userInput]; if !ok { return error }`,校验的是 `ok`(查找结果)而非 `userInput`(污点变量本身),但如果 `userInput` 不在 `fixedMap` 的 key 中则 `ok` 必为 false,从而触发中断——这等价于对 `userInput` 的白名单校验。同理,`err := db.Where("name = ?", input).First(&record).Error; if err != nil { return err }` 校验的是 `err`(查询结果),但如果 `input` 不在数据库中则 `err` 不为 nil,从而触发中断——这同样等价于对 `input` 的白名单校验。
|
|
33
|
+
|
|
34
|
+
**提前终止决策(Step 2 完成后必须执行)**
|
|
35
|
+
如果同时满足以下两个条件,执行「终止验证」后立即输出结论,**禁止继续分析**:
|
|
36
|
+
- 条件 A:Step 1 发现了覆盖全部污点参数的校验逻辑
|
|
37
|
+
- 条件 B:Step 2 确认校验失败时会中断执行
|
|
38
|
+
|
|
39
|
+
「终止验证」:选取一个 SQL 注入 payload,
|
|
40
|
+
逐步写出代入校验函数的每一步输入和输出值,确认该 payload 确实无法通过校验。
|
|
41
|
+
验证通过后,判定为 **false_positive**,输出结论。
|
|
42
|
+
|
|
43
|
+
**Step 3 - 验证防御有效性**(仅当未触发「提前终止决策」时执行,即条件 A 或条件 B 任一不成立)
|
|
44
|
+
检查通过校验的值,其格式/范围是否能排除 SQL 注入所需的特殊字符(如引号、注释符 `--` 等)。
|
|
45
|
+
|
|
46
|
+
**Step 4 - 覆盖完整性检查**(仅当未触发「提前终止决策」时执行)
|
|
47
|
+
确认保护措施是否覆盖了进入危险函数的所有污点参数,任一参数未被保护则判定为 true_positive。
|
|
48
|
+
|
|
49
|
+
### true_positive 推演示例(参考)
|
|
50
|
+
扫描结果指出某 Go 文件中如下代码存在 SQL 注入:
|
|
51
|
+
|
|
52
|
+
```go
|
|
53
|
+
func ListUsers(c *gin.Context) {
|
|
54
|
+
orderBy := c.Query("order_by") // 污点来源
|
|
55
|
+
if orderBy == "" {
|
|
56
|
+
orderBy = "id"
|
|
57
|
+
}
|
|
58
|
+
db.Raw("SELECT * FROM users ORDER BY " + orderBy).Scan(&users) // 危险点
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- Step 1:仅有空字符串校验,未发现白名单/类型转换/正则等任何能限定取值范围的逻辑("误报特征注意 #1" 明确空校验不算)。
|
|
63
|
+
- Step 2:跳过(Step 1 未发现校验)。
|
|
64
|
+
- 提前终止决策:条件 A 不成立,不能终止。
|
|
65
|
+
- Step 3:`orderBy` 直接拼入 SQL 结构(ORDER BY 子句),可包含任意字符(如 `id; DROP TABLE users--`)。
|
|
66
|
+
- Step 4:唯一污点参数未被保护。
|
|
67
|
+
- 结论:**true_positive**,原因记录为"`order_by` 经空校验后直接拼入 ORDER BY 子句,未做白名单限制,可注入 `id; DROP TABLE users--` 等 payload"。
|
|
68
|
+
|
|
69
|
+
### 误报判断逻辑
|
|
70
|
+
1. 如果代码中存在有效的过滤、校验、清理逻辑,则为误报 (false_positive),有效的定义是指能够防御当前漏洞场景,具体可参考「误报特征」。
|
|
71
|
+
2. 只要存在能够防御漏洞的代码就算误报,比如强制类型转换、数字类型校验、字符串格式校验等,**不要求一定使用预编译或参数化查询等最佳实践**
|
|
72
|
+
3. 该路径在实际运行中无法触发(如死代码、测试代码等),则为误报 (false_positive)。
|
|
73
|
+
4. 其他的情况,则认为是真实存在的漏洞(true_positive)。
|
|
74
|
+
|
|
75
|
+
### 分析纪律
|
|
76
|
+
|
|
77
|
+
1. **禁止使用未验证假设**:对任何"可能存在绕过"的判断,必须附上具体 payload 代入每一步函数的输入/输出推演过程,否则该判断无效。
|
|
78
|
+
2. **找到误报证据即止步**:完成「终止验证」后,禁止继续寻找理论上的绕过方式。
|
|
79
|
+
3. **摇摆结论以推演为准**:分析过程中如结论出现反复,以最后一次有具体值代入推演支撑的结论为准,无推演支撑的推测性结论一律作废。
|
|
80
|
+
4. **禁止运行代码或编写测试**:但允许查阅标准库或第三方库的官方文档以确认函数行为(例如 `strconv.Atoi` 对带特殊字符的输入返回什么),不得通过实际执行代码来验证。
|
|
81
|
+
5. **证据不足时保守判定**:如果数据流不完整、关键代码缺失(如外部闭源依赖)、无法对所有污点参数进行完整推演,**判定为 true_positive**,并在 `reason` 中说明缺失的证据。误报需要正面证据,而真漏洞是默认假设。
|
|
82
|
+
|
|
83
|
+
### 误报特征
|
|
84
|
+
代码中存在以下特征可认为是安全的,可以判定为误报:
|
|
85
|
+
|
|
86
|
+
1. 输入数据在进入危险函数前,经过了强制类型转换,参数被转换为数字类型(如 `strconv.Atoi()`、`strconv.ParseInt()`、类型断言为数字类型)。
|
|
87
|
+
2. 输入数据在进入危险函数前,经过了白名单过滤,包括但不限于以下形式:
|
|
88
|
+
- **显式白名单**:通过 map 查找、slice 遍历等方式直接校验输入是否在允许列表中。
|
|
89
|
+
- **隐式白名单(map/slice 等值映射查找)**:污点变量作为 key 去查找固定 map,然后对查找结果做校验,失败则中断执行。此时污点变量的值域被限定为 map 中的固定 key,不可能包含 SQL 注入 payload。
|
|
90
|
+
```go
|
|
91
|
+
// 典型模式:input 作为 key 查找固定 map,映射失败则返回错误
|
|
92
|
+
value, ok := fixedMap[input]
|
|
93
|
+
if !ok {
|
|
94
|
+
return fmt.Errorf("invalid input")
|
|
95
|
+
}
|
|
96
|
+
// 此处 input 的值已被限定为 fixedMap 的 key,是安全的
|
|
97
|
+
db.Exec("... WHERE col = '" + input + "'")
|
|
98
|
+
```
|
|
99
|
+
- **参数化查询存在性校验**:污点变量通过参数化查询去数据库中查询是否存在匹配记录,查询无结果则中断执行。此时污点变量的值被限定为数据库表中已有的合法记录,SQL 注入 payload 不可能作为合法记录存在。
|
|
100
|
+
```go
|
|
101
|
+
// 典型模式:使用参数化查询校验 input 是否存在于数据库
|
|
102
|
+
var record Model
|
|
103
|
+
err := db.Where("name = ?", input).First(&record).Error
|
|
104
|
+
if err != nil {
|
|
105
|
+
return errors.New("记录不存在")
|
|
106
|
+
}
|
|
107
|
+
// 此处 input 的值已被限定为数据库中已有的合法记录值,是安全的
|
|
108
|
+
```
|
|
109
|
+
- **switch/case 匹配**:`switch input { case "a": ... case "b": ... default: return error }` 模式。
|
|
110
|
+
- 上述白名单校验的核心判定标准是:**通过校验后,污点变量的可能取值是否被限定为有限的、已知的安全字符串集合**。
|
|
111
|
+
3. 代码中使用了正则表达式进行输入验证,且表达式能够防御当前场景,比如仅允许数字类型的值。
|
|
112
|
+
4. 输入数据在进入危险函数前,被预编译为安全的代码,比如使用 ORM 框架。
|
|
113
|
+
|
|
114
|
+
### 证据优先级(用于减少误判)
|
|
115
|
+
- **强证据**:参数化查询绑定参数(`db.Where("name = ?", input)`、`db.Raw(sql, args...)`)且未拼接用户输入到 SQL 结构中。
|
|
116
|
+
- **中证据**:严格白名单/映射查找并在失败时中断执行。
|
|
117
|
+
- **弱证据**:仅依赖一般性清理(如仅移除空格)、过于宽松的正则、长度限制等**无法排除注入特殊字符**的措施。弱证据不能单独判定为 `false_positive`。
|
|
118
|
+
|
|
119
|
+
### 常见危险反例(命中则倾向 true_positive)
|
|
120
|
+
- 使用 `fmt.Sprintf("%s", input)` 或字符串拼接 `"..." + input` 将用户输入拼入 SQL 语句结构(列名、表名、ORDER BY 等)。
|
|
121
|
+
- 在 `Order`/`Select`/`Table` 等位置直接拼接用户输入。
|
|
122
|
+
- `Where` 使用 map 且 key 可被用户控制(即使 value 做了参数化)——这是 GORM 的常见陷阱:使用了参数化绑定不代表整条语句安全,key 仍会被拼到 SQL 结构中。
|
|
123
|
+
|
|
124
|
+
**注意**:仅校验参数是否为空(如 `if param == "" { return err }` 或 `if param == nil`),没有其他校验逻辑,不能认为是误报。
|
|
125
|
+
|
|
126
|
+
## 结果输出
|
|
127
|
+
|
|
128
|
+
将结果写入到 `analyze_report.json` 文件中,与 `parsed_result.json` 同目录。**输出必须是合法 JSON**(不允许 `//` 注释、不允许末尾逗号),可使用 `json.Valid` 或本地 `python -m json.tool` 自查。
|
|
129
|
+
|
|
130
|
+
字段说明:
|
|
131
|
+
- `vul_hash`:从 `parsed_result.json` 中拷贝的漏洞 hash,用于回写关联。
|
|
132
|
+
- `result`:取值仅限 `"true_positive"` 或 `"false_positive"`。
|
|
133
|
+
- `reason`:判定依据。误报必须给出"起到过滤作用的具体代码 + 推演链条";真漏洞必须给出"可达 payload + 拼接位置"。
|
|
134
|
+
|
|
135
|
+
文件示例(合法 JSON,可直接保存):
|
|
136
|
+
```json
|
|
137
|
+
[
|
|
138
|
+
{
|
|
139
|
+
"vul_hash": "7c3ecd91ff42281bd862b400f5b955b1",
|
|
140
|
+
"result": "false_positive",
|
|
141
|
+
"reason": "代码在第 45 行使用 strconv.Atoi 将 input 强制转换为 int,转换失败立即 return;payload \"1' OR 1=1--\" 经 strconv.Atoi 返回 err != nil,无法到达 db.Exec。清洗函数:strconv.Atoi"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"vul_hash": "a1b2c3d4e5f6...",
|
|
145
|
+
"result": "true_positive",
|
|
146
|
+
"reason": "order_by 参数仅做空校验后直接拼入 ORDER BY 子句(services/user.go:78),可注入 payload \"id; DROP TABLE users--\",未发现任何白名单/类型转换/参数化保护。"
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
```
|
package/comate-engine/assets/skills/code-security/references/vul_analysis-java_sql_injection.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# 漏洞复核流程
|
|
2
|
+
|
|
3
|
+
你是一个资深的安全审计专家,请对 Java 代码的 SQL 注入漏洞扫描结果进行复核,判断是否为误报。
|
|
4
|
+
|
|
5
|
+
漏洞复核可分为三个步骤:
|
|
6
|
+
|
|
7
|
+
1. 数据读取:仔细阅读污点追踪信息(trace)及相关的代码文件。
|
|
8
|
+
2. 漏洞分析:根据「漏洞分析」的要求判断漏洞是否为误报。
|
|
9
|
+
3. 结果输出:按照格式要求输出复核结果。
|
|
10
|
+
|
|
11
|
+
## 数据读取
|
|
12
|
+
|
|
13
|
+
阅读扫描报告 parsed_result.json,其中包含了详细的漏洞位置、数据流信息以及漏洞类型说明。
|
|
14
|
+
|
|
15
|
+
## 漏洞分析
|
|
16
|
+
|
|
17
|
+
### 分析步骤(请严格按顺序执行,满足终止条件时立即停止)
|
|
18
|
+
**Step 1 - 追踪校验逻辑**
|
|
19
|
+
从污点来源到危险函数,逐行检查是否存在对该参数的过滤/校验代码。
|
|
20
|
+
参考「误报特征」中列举的保护类型进行识别。
|
|
21
|
+
**重要**:校验逻辑可能是间接的,以下几种模式都属于有效校验:
|
|
22
|
+
- **字典/Map 查找**:如果污点变量被用作 Map 的 key 来查找值,且查找失败会中断执行,则该查找本身就是对污点变量的白名单校验——因为只有 key 恰好等于白名单中某个固定值时才能继续执行。
|
|
23
|
+
- **参数化查询存在性校验**:如果污点变量通过参数化查询(预编译/JPA/MyBatis 等 ORM)去数据库查询是否存在匹配记录,且查询无结果会中断执行(throw/return),则该查询等价于隐式白名单校验。推理链条为:参数化查询本身不会产生 SQL 注入 → 查询要求数据库中存在与污点变量精确匹配的记录 → SQL 注入 payload(如 `' OR 1=1--`)不可能作为合法业务数据存在于数据库中 → payload 无法通过校验 → 后续即使拼接 SQL 也是安全的。**这种模式应判定为 false_positive**。
|
|
24
|
+
|
|
25
|
+
**边界条件**:如果被查询字段是用户可写入的(如 username、title、备注等任意字符串字段)且无字符集限制,攻击者可能预先注册 `admin' OR 1=1--` 这样的合法记录,此时存在性校验不再可靠,**不能据此判定为误报**。仅当被查询字段是系统受控、字符集受限(如纯数字 ID、枚举码、内部字典表)时,该模式才成立。
|
|
26
|
+
- **复合变量的组成成分校验**:如果 SQL 中实际使用的变量是由多个污点字段拼接而成(如 `configId = field1 + field2 + CONST + field3`),且每个污点字段在拼接之前已分别通过参数化查询存在性校验(验证失败会中断执行),则整个复合变量等同于已被校验——合法业务 ID 不包含 SQL 注入所需的特殊字符,因此后续对复合变量的 SQL 拼接也应判定为 **false_positive**。注意:常量部分(如 `Const.FINANCE = "FINANCE"`)本身是安全的,不需要额外校验。
|
|
27
|
+
|
|
28
|
+
如果 Step 1 未发现任何校验逻辑,跳过 Step 2,直接进入 Step 3。
|
|
29
|
+
|
|
30
|
+
**Step 2 - 验证中断有效性**
|
|
31
|
+
如果 Step 1 发现校验逻辑,检查校验失败时是否会中断后续执行。
|
|
32
|
+
需要找到对应的 return / throw 语句(可能在被调用函数内部)。
|
|
33
|
+
**特别注意**:中断判断要考虑间接中断。例如 `String value = map.get(userInput); if (value == null) throw new Exception();`,校验的是 `value`(映射结果)而非 `userInput`(污点变量本身),但如果 `userInput` 不在 `map` 的 key 中则 `value` 必为 null,从而触发中断——这等价于对 `userInput` 的白名单校验。同理,`Entity record = repo.findByName(input); if (record == null) throw ...;` 校验的是 `record`(查询结果),但如果 `input` 不在数据库中则 `record` 必为 null,从而触发中断——这同样等价于对 `input` 的白名单校验。
|
|
34
|
+
|
|
35
|
+
**以下几种中断形式都应视为有效的流程终止:**
|
|
36
|
+
- 直接 `throw` 异常或 `return` 终止:`if (result == null) throw new NotFoundException()`
|
|
37
|
+
- `CollectionUtils.isEmpty(list)` 判断:`if (CollectionUtils.isEmpty(results)) return null;`(等价于对空集合的 null 检查)
|
|
38
|
+
- **两级间接中断**:方法 A 因查询为空返回 `null`,调用方方法 B 收到 `null` 后也 return 或 throw——这整个链条等价于直接中断,只要能追踪到最终必然终止执行即可。
|
|
39
|
+
**提前终止决策(Step 2 完成后必须执行)**
|
|
40
|
+
如果同时满足以下两个条件,执行「终止验证」后立即输出结论,**禁止继续分析**:
|
|
41
|
+
- 条件 A:Step 1 发现了覆盖全部污点参数的校验逻辑
|
|
42
|
+
- 条件 B:Step 2 确认校验失败时会中断执行
|
|
43
|
+
|
|
44
|
+
「终止验证」:选取一个 SQL 注入 payload,
|
|
45
|
+
逐步写出代入校验函数的每一步输入和输出值,确认该 payload 确实无法通过校验。
|
|
46
|
+
验证通过后,判定为 **false_positive**,输出结论。
|
|
47
|
+
|
|
48
|
+
**Step 3 - 验证防御有效性**(仅当未触发「提前终止决策」时执行,即条件 A 或条件 B 任一不成立)
|
|
49
|
+
检查通过校验的值,其格式/范围是否能排除 SQL 注入所需的特殊字符(如引号、注释符 `--` 等)。
|
|
50
|
+
|
|
51
|
+
**Step 4 - 覆盖完整性检查**(仅当未触发「提前终止决策」时执行)
|
|
52
|
+
确认保护措施是否覆盖了进入危险函数的所有污点参数,任一参数未被保护则判定为 true_positive。
|
|
53
|
+
|
|
54
|
+
### true_positive 推演示例(参考)
|
|
55
|
+
扫描结果指出某 Java 文件中如下代码存在 SQL 注入:
|
|
56
|
+
|
|
57
|
+
```java
|
|
58
|
+
public List<User> listUsers(String orderBy) {
|
|
59
|
+
if (orderBy == null) orderBy = "id";
|
|
60
|
+
String sql = "SELECT * FROM users ORDER BY " + orderBy;
|
|
61
|
+
return jdbcTemplate.queryForList(sql, User.class);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- Step 1:仅有 null 校验,未发现白名单/类型转换/正则等任何能限定取值范围的逻辑("误报特征注意 #1" 明确空校验不算)。
|
|
66
|
+
- Step 2:跳过(Step 1 未发现校验)。
|
|
67
|
+
- 提前终止决策:条件 A 不成立,不能终止。
|
|
68
|
+
- Step 3:`orderBy` 直接拼入 SQL 结构(ORDER BY 子句),可包含任意字符(如 `id; DROP TABLE users--`)。
|
|
69
|
+
- Step 4:唯一污点参数未被保护。
|
|
70
|
+
- 结论:**true_positive**,原因记录为"`orderBy` 经 null 校验后直接拼入 ORDER BY 子句,未做白名单限制,可注入 `id; DROP TABLE users--` 等 payload"。
|
|
71
|
+
|
|
72
|
+
### 误报判断逻辑
|
|
73
|
+
1. 如果代码中存在有效的过滤、校验、清理逻辑,则为误报 (false_positive),有效的定义是指能够防御当前漏洞场景,具体可参考「误报特征」。
|
|
74
|
+
2. 只要存在能够防御漏洞的代码就算误报,比如强制类型转换、数字类型校验、字符串格式校验等,**不要求一定使用预编译或参数化查询等最佳实践**
|
|
75
|
+
3. 该路径在实际运行中无法触发(如死代码、测试代码等),则为误报 (false_positive)。
|
|
76
|
+
4. 其他的情况,则认为是真实存在的漏洞(true_positive)。
|
|
77
|
+
|
|
78
|
+
### 分析纪律
|
|
79
|
+
|
|
80
|
+
1. **禁止使用未验证假设**:对任何"可能存在绕过"的判断,必须附上具体 payload 代入每一步函数的输入/输出推演过程,否则该判断无效。
|
|
81
|
+
2. **找到误报证据即止步**:完成「终止验证」后,禁止继续寻找理论上的绕过方式。
|
|
82
|
+
3. **摇摆结论以推演为准**:分析过程中如结论出现反复,以最后一次有具体值代入推演支撑的结论为准,无推演支撑的推测性结论一律作废。
|
|
83
|
+
4. **禁止运行代码或编写测试**:但允许查阅 JDK / 第三方库的官方文档以确认函数行为(例如 `Integer.parseInt` 对带特殊字符输入的行为),不得通过实际执行代码来验证。
|
|
84
|
+
5. **证据不足时保守判定**:如果数据流不完整、关键代码缺失(如外部闭源依赖、自定义 ORM 内部行为不明)、无法对所有污点参数进行完整推演,**判定为 true_positive**,并在 `reason` 中说明缺失的证据。误报需要正面证据,而真漏洞是默认假设。
|
|
85
|
+
|
|
86
|
+
### 误报特征
|
|
87
|
+
代码中存在以下特征可认为是安全的,可以判定为误报:
|
|
88
|
+
|
|
89
|
+
1. 输入数据在进入危险函数前,经过了强制类型转换,参数被转换为数字类型(如 `Integer.parseInt()`、`Long.parseLong()`、`BigDecimal` 后再做范围校验)。
|
|
90
|
+
2. 输入数据在进入危险函数前,经过了白名单过滤,包括但不限于以下形式:
|
|
91
|
+
- **显式白名单**:`whitelist.contains(input)` 或 `map.containsKey(input)` 等直接校验。
|
|
92
|
+
- **隐式白名单(数组/字典等值映射查找)**:污点变量作为 key 去查找固定 Map/数组,然后对查找结果做非空校验,失败则中断执行。此时污点变量的值域被限定为 Map 中的固定 key,不可能包含 SQL 注入 payload。
|
|
93
|
+
```java
|
|
94
|
+
// 典型模式:input 作为 key 查找固定映射,映射失败则抛出异常
|
|
95
|
+
String value = fixedMap.get(input);
|
|
96
|
+
if (value == null) throw new IllegalArgumentException("invalid");
|
|
97
|
+
// 此处 input 的值已被限定为 fixedMap 的 key,是安全的
|
|
98
|
+
jdbcTemplate.execute("... WHERE col = '" + input + "'");
|
|
99
|
+
```
|
|
100
|
+
- **参数化查询存在性校验**:污点变量通过参数化查询(预编译/ORM)去数据库中查询是否存在匹配记录,查询无结果则中断执行。此时污点变量的值被限定为数据库表中已有的合法记录,SQL 注入 payload 不可能作为合法记录存在。
|
|
101
|
+
```java
|
|
102
|
+
// 典型模式:使用参数化查询校验 input 是否存在于数据库
|
|
103
|
+
Entity record = repository.findByName(input); // JPA 参数化查询
|
|
104
|
+
if (record == null) {
|
|
105
|
+
throw new NotFoundException("记录不存在");
|
|
106
|
+
}
|
|
107
|
+
// 此处 input 的值已被限定为数据库中已有的合法记录值,是安全的
|
|
108
|
+
```
|
|
109
|
+
- **多参数 DTO 复合 ID 模式**:DTO 对象的多个污点字段(如 `sceneId`、`reportVersionId`、`dataVersionId`)作为命名参数传入参数化查询做存在性验证(SQL 使用 `:sceneId`、`:reportVersionId` 等占位符),验证失败(结果为 null 或 `CollectionUtils.isEmpty()` 为 true)则中断执行,之后这些字段被拼接为复合 ID 再用于 SQL 字符串拼接。应判定为 **false_positive**:每个字段已被隐式限定为数据库中的合法业务值,合法 ID 不含 SQL 注入特殊字符;注入 payload 无法通过存在性校验,因此永远无法到达 SQL 拼接点。
|
|
110
|
+
```java
|
|
111
|
+
// 典型模式:DTO 多字段通过命名参数参数化查询做存在性验证
|
|
112
|
+
// Service 层
|
|
113
|
+
List<TableQuart> quarts = dao.queryByVersion(action); // 内部使用 :sceneId/:reportVersionId/:dataVersionId
|
|
114
|
+
if (CollectionUtils.isEmpty(quarts)) {
|
|
115
|
+
return null; // 查询无结果 → 流程中断(调用方收到 null 也会终止)
|
|
116
|
+
}
|
|
117
|
+
// 后续(同一调用链的下游方法)
|
|
118
|
+
// 将已通过验证的 DTO 字段拼接为复合 ID
|
|
119
|
+
String configId = action.getSceneId() + action.getReportVersionId()
|
|
120
|
+
+ Const.FINANCE + action.getDataVersionId(); // Const.FINANCE 是安全常量
|
|
121
|
+
String sql = "ALTER TABLE ... ADD PARTITION " + configId + " VALUES ('" + configId + "')";
|
|
122
|
+
// 虽然此处是字符串拼接,但 configId 由已验证的合法业务字段组成 → false_positive
|
|
123
|
+
```
|
|
124
|
+
- **switch/case 匹配**:`switch(input) { case "a": ... case "b": ... default: throw new Exception(); }` 模式。
|
|
125
|
+
- **枚举白名单转换(Enum Whitelist Conversion)**:污点变量经过枚举静态工厂方法(如 `fromAlias`、`fromString`、`valueOf`、`of` 等)查找,再调用 `.name()` 获取枚举常量名。由于 `.name()` 返回的是 Java 枚举常量的名称(由代码预先定义),完全不受用户输入控制,因此该模式是有效的白名单净化。若工厂方法匹配失败返回 `null`,后续 `.name()` 调用将抛出 NPE,同样阻断 SQL 注入。
|
|
126
|
+
```java
|
|
127
|
+
// 典型模式:将用户输入映射为枚举常量名,输出值只能是预定义的枚举名之一
|
|
128
|
+
String privStr = privileges.stream()
|
|
129
|
+
.map(p -> Privilege.fromAlias(p).name()) // 输出固定为 SELECT_PRIV / GRANT_PRIV 等
|
|
130
|
+
.collect(Collectors.joining(", "));
|
|
131
|
+
// 此处 privStr 只包含预定义枚举名,不含 SQL 特殊字符,是安全的
|
|
132
|
+
```
|
|
133
|
+
- 上述白名单校验的核心判定标准是:**通过校验后,污点变量的可能取值是否被限定为有限的、已知的安全字符串集合**。
|
|
134
|
+
3. 代码中使用了正则表达式进行输入验证,且表达式能够防御当前场景,比如仅允许数字类型的值。典型形式包括:
|
|
135
|
+
- 调用 `Pattern.matcher(input).matches()` 或 `input.matches(regex)` 并在不匹配时中断执行。
|
|
136
|
+
- 调用自定义断言方法(如 `assertPattern(input, pattern, msg)`),该方法内部对输入做正则校验,不匹配则抛出异常。判定要点:必须确认正则本身能排除 SQL 注入所需的特殊字符(引号、分号、注释符等),例如 `^[a-zA-Z][a-zA-Z_]*$` 只允许字母和下划线,可有效阻断注入;而宽松的 `^.+$` 不能阻断注入,不算有效防御。
|
|
137
|
+
```java
|
|
138
|
+
// 典型模式:断言方法内做正则白名单校验,不通过则抛异常
|
|
139
|
+
assertPattern(input, VALID_PATTERN, "Invalid input: " + input);
|
|
140
|
+
// VALID_PATTERN = "^[a-zA-Z][a-zA-Z_]*$" — 只允许字母和下划线,不含 SQL 特殊字符
|
|
141
|
+
// 后续对 input 的 SQL 拼接是安全的
|
|
142
|
+
```
|
|
143
|
+
4. 输入数据在进入危险函数前,被预编译为安全的代码,比如使用 ORM 框架。
|
|
144
|
+
5. 数据流经过了以下框架或者方法的处理,则认为是安全的:
|
|
145
|
+
- 自定义ORM框架:包名 `baidu.crowdtest.mybatisorm`
|
|
146
|
+
- 自定义ORM框架:包名 `com.baidu.eop.framework.mybatis`,其中包含大量注解 `@EopSqlSharding` 可认为是安全的
|
|
147
|
+
- 自定义过滤方法:包名 `com.baidu.bce.crm.lib.utils`, 类名 `CheckUtil`,方法名 `checkOrderBySqlInject`
|
|
148
|
+
|
|
149
|
+
### 证据优先级(用于减少误判)
|
|
150
|
+
- **强证据**:参数化查询绑定参数(`PreparedStatement`、`JdbcTemplate query(sql, args)`、MyBatis `#{}`)且 SQL 结构未由用户输入控制。
|
|
151
|
+
- **中证据**:严格白名单/映射查找并在失败时中断执行。
|
|
152
|
+
- **弱证据**:仅依赖一般性转义、长度限制、宽松正则。弱证据不能单独判定为 `false_positive`。
|
|
153
|
+
|
|
154
|
+
### 常见危险反例(命中则倾向 true_positive)
|
|
155
|
+
- 通过字符串拼接构造 SQL:`"..." + input`。
|
|
156
|
+
- MyBatis 使用 `${}` 直接拼接用户输入。
|
|
157
|
+
- 在 `ORDER BY`、列名、表名等 SQL 结构位置直接使用用户输入。
|
|
158
|
+
|
|
159
|
+
**注意**:
|
|
160
|
+
1. 仅校验参数是否为空(如 `if (param != null)` 或仅做非空判断),没有其他校验逻辑,不能认为是误报。
|
|
161
|
+
|
|
162
|
+
## 结果输出
|
|
163
|
+
|
|
164
|
+
将结果写入到 `analyze_report.json` 文件中,与 `parsed_result.json` 同目录。**输出必须是合法 JSON**(不允许 `//` 注释、不允许末尾逗号),可使用 `python -m json.tool` 自查。
|
|
165
|
+
|
|
166
|
+
字段说明:
|
|
167
|
+
- `vul_hash`:从 `parsed_result.json` 中拷贝的漏洞 hash,用于回写关联。
|
|
168
|
+
- `result`:取值仅限 `"true_positive"` 或 `"false_positive"`。
|
|
169
|
+
- `reason`:判定依据。误报必须给出"起到过滤作用的具体代码 + 推演链条";真漏洞必须给出"可达 payload + 拼接位置"。
|
|
170
|
+
|
|
171
|
+
文件示例(合法 JSON,可直接保存):
|
|
172
|
+
```json
|
|
173
|
+
[
|
|
174
|
+
{
|
|
175
|
+
"vul_hash": "7c3ecd91ff42281bd862b400f5b955b1",
|
|
176
|
+
"result": "false_positive",
|
|
177
|
+
"reason": "代码在第 45 行使用 Integer.parseInt 将 input 强制转换为 int,转换失败立即抛 NumberFormatException;payload \"1' OR 1=1--\" 经 Integer.parseInt 抛异常,无法到达 jdbcTemplate.execute。清洗函数:Integer.parseInt"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"vul_hash": "a1b2c3d4e5f6...",
|
|
181
|
+
"result": "true_positive",
|
|
182
|
+
"reason": "orderBy 参数仅做 null 校验后直接拼入 ORDER BY 子句(service/UserService.java:78),可注入 payload \"id; DROP TABLE users--\",未发现任何白名单/类型转换/参数化保护。"
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
```
|
package/comate-engine/assets/skills/code-security/references/vul_analysis-php_sql_injection.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# 漏洞复核流程
|
|
2
|
+
|
|
3
|
+
你是一个资深的安全审计专家,请对 PHP 代码的 SQL 注入漏洞扫描结果进行复核,只需要判断是否为误报。
|
|
4
|
+
|
|
5
|
+
漏洞复核可分为三个步骤:
|
|
6
|
+
|
|
7
|
+
1. 数据读取:仔细阅读污点追踪信息(trace)及相关的代码文件。
|
|
8
|
+
2. 漏洞分析:根据「漏洞分析」的要求判断漏洞是否为误报。
|
|
9
|
+
3. 结果输出:按照格式要求输出复核结果。
|
|
10
|
+
|
|
11
|
+
## 数据读取
|
|
12
|
+
|
|
13
|
+
阅读扫描报告 parsed_result.json,其中包含了详细的漏洞位置、数据流信息以及漏洞类型说明。
|
|
14
|
+
|
|
15
|
+
## 漏洞分析
|
|
16
|
+
|
|
17
|
+
### 分析步骤(请严格按顺序执行,满足终止条件时立即停止)
|
|
18
|
+
**Step 1 - 追踪校验逻辑**
|
|
19
|
+
从污点来源到危险函数,逐行检查是否存在对该参数的过滤/校验代码。
|
|
20
|
+
参考「误报特征」中列举的保护类型进行识别。
|
|
21
|
+
**重要**:校验逻辑可能是间接的,以下两种模式都属于有效校验:
|
|
22
|
+
- **字典/数组查找**:如果污点变量被用作数组/字典的 key 来查找值,且查找失败会中断执行,则该查找本身就是对污点变量的白名单校验——因为只有 key 恰好等于白名单中某个固定值时才能继续执行。
|
|
23
|
+
- **参数化查询存在性校验**:如果污点变量通过参数化查询(预编译/ORM)去数据库查询是否存在匹配记录,且查询无结果会中断执行(throw/return/die),则该查询等价于隐式白名单校验。推理链条为:参数化查询本身不会产生 SQL 注入 → 查询要求数据库中存在与污点变量精确匹配的记录 → SQL 注入 payload(如 `' OR 1=1--`)不可能作为合法业务数据存在于数据库中 → payload 无法通过校验 → 后续即使拼接 SQL 也是安全的。**这种模式应判定为 false_positive**。
|
|
24
|
+
|
|
25
|
+
**边界条件**:如果被查询字段是用户可写入的(如 username、title、备注等任意字符串字段)且无字符集限制,攻击者可能预先注册 `admin' OR 1=1--` 这样的合法记录,此时存在性校验不再可靠,**不能据此判定为误报**。仅当被查询字段是系统受控、字符集受限(如纯数字 ID、枚举码、内部字典表)时,该模式才成立。
|
|
26
|
+
|
|
27
|
+
如果 Step 1 未发现任何校验逻辑,跳过 Step 2,直接进入 Step 3。
|
|
28
|
+
|
|
29
|
+
**Step 2 - 验证中断有效性**
|
|
30
|
+
如果 Step 1 发现校验逻辑,检查校验失败时是否会中断后续执行。
|
|
31
|
+
需要找到对应的 exit / die / return / throw 语句(可能在被调用函数内部)。
|
|
32
|
+
**特别注意**:中断判断要考虑间接中断。例如 `$rid = $map[$user_input]; if (empty($rid)) return error;`,校验的是 `$rid`(映射结果)而非 `$user_input`(污点变量本身),但如果 `$user_input` 不在 `$map` 的 key 中则 `$rid` 必为空,从而触发中断——这等价于对 `$user_input` 的白名单校验。同理,参数化查询 `$record = Model::find('col=:col', [':col'=>$input]); if (!$record) throw ...;` 校验的是 `$record`(查询结果),但如果 `$input` 不在数据库中则 `$record` 必为空/null,从而触发中断——这同样等价于对 `$input` 的白名单校验。
|
|
33
|
+
**提前终止决策(Step 2 完成后必须执行)**
|
|
34
|
+
如果同时满足以下两个条件,执行「终止验证」后立即输出结论,**禁止继续分析**:
|
|
35
|
+
- 条件 A:Step 1 发现了覆盖全部污点参数的校验逻辑
|
|
36
|
+
- 条件 B:Step 2 确认校验失败时会中断执行
|
|
37
|
+
|
|
38
|
+
「终止验证」:选取一个 SQL 注入 payload,
|
|
39
|
+
逐步写出代入校验函数的每一步输入和输出值,确认该 payload 确实无法通过校验。
|
|
40
|
+
验证通过后,判定为 **false_positive**,输出结论。
|
|
41
|
+
|
|
42
|
+
**Step 3 - 验证防御有效性**(仅当未触发「提前终止决策」时执行,即条件 A 或条件 B 任一不成立)
|
|
43
|
+
检查通过校验的值,其格式/范围是否能排除 SQL 注入所需的特殊字符(如引号、注释符 `--` 等)。
|
|
44
|
+
|
|
45
|
+
**Step 4 - 覆盖完整性检查**(仅当未触发「提前终止决策」时执行)
|
|
46
|
+
确认保护措施是否覆盖了进入危险函数的所有污点参数,任一参数未被保护则判定为 true_positive。
|
|
47
|
+
|
|
48
|
+
### true_positive 推演示例(参考)
|
|
49
|
+
扫描结果指出某 PHP 文件中如下代码存在 SQL 注入:
|
|
50
|
+
|
|
51
|
+
```php
|
|
52
|
+
public function listOrders() {
|
|
53
|
+
$orderBy = $_GET['order_by'] ?? 'id';
|
|
54
|
+
$sql = "SELECT * FROM orders ORDER BY " . $orderBy;
|
|
55
|
+
return Db::query($sql);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- Step 1:仅有默认值兜底,未发现白名单/类型转换/正则等任何能限定取值范围的逻辑("误报特征注意 #1" 明确空校验不算)。
|
|
60
|
+
- Step 2:跳过(Step 1 未发现校验)。
|
|
61
|
+
- 提前终止决策:条件 A 不成立,不能终止。
|
|
62
|
+
- Step 3:`$orderBy` 直接拼入 SQL 结构(ORDER BY 子句),可包含任意字符(如 `id; DROP TABLE orders--`)。
|
|
63
|
+
- Step 4:唯一污点参数未被保护。
|
|
64
|
+
- 结论:**true_positive**,原因记录为"`order_by` 经默认值兜底后直接拼入 ORDER BY 子句,未做白名单限制,可注入 `id; DROP TABLE orders--` 等 payload"。
|
|
65
|
+
|
|
66
|
+
### 误报判断逻辑
|
|
67
|
+
1. 如果代码中存在有效的过滤、校验、清理逻辑,则为误报 (false_positive),有效的定义是指能够防御当前漏洞场景,具体可参考「误报特征」。
|
|
68
|
+
2. 只要存在能够防御漏洞的代码就算误报,比如强制类型转换、数字类型校验、字符串格式校验等,**不要求一定使用预编译或参数化查询等最佳实践**
|
|
69
|
+
3. 该路径在实际运行中无法触发(如死代码、测试代码等),则为误报 (false_positive)。
|
|
70
|
+
4. 其他的情况,则认为是真实存在的漏洞(true_positive)。
|
|
71
|
+
|
|
72
|
+
### 分析纪律
|
|
73
|
+
|
|
74
|
+
1. **禁止使用未验证假设**:对任何"可能存在绕过"的判断,必须附上具体 payload 代入每一步函数的输入/输出推演过程,否则该判断无效。
|
|
75
|
+
2. **找到误报证据即止步**:完成「终止验证」后,禁止继续寻找理论上的绕过方式。
|
|
76
|
+
3. **摇摆结论以推演为准**:分析过程中如结论出现反复,以最后一次有具体值代入推演支撑的结论为准,无推演支撑的推测性结论一律作废。
|
|
77
|
+
4. **禁止运行代码或编写测试**:但允许查阅 PHP 标准库或第三方库的官方文档以确认函数行为(例如 `intval` 对带特殊字符输入的行为),不得通过实际执行代码来验证。
|
|
78
|
+
5. **证据不足时保守判定**:如果数据流不完整、关键代码缺失(如外部闭源依赖、自定义框架内部行为不明)、无法对所有污点参数进行完整推演,**判定为 true_positive**,并在 `reason` 中说明缺失的证据。误报需要正面证据,而真漏洞是默认假设。
|
|
79
|
+
|
|
80
|
+
### 误报特征
|
|
81
|
+
代码中存在但不局限于以下特征可认为是安全的,可以判定为误报:
|
|
82
|
+
|
|
83
|
+
1. 输入数据在进入危险函数前,经过了强制类型转换,参数被转换为数字类型(如 `intval()`、`(int)$input` 在 PHP 中)。
|
|
84
|
+
2. 输入数据在进入危险函数前,经过了白名单过滤,包括但不限于以下形式:
|
|
85
|
+
- **显式白名单**:`in_array($input, $whitelist)` 或 `array_key_exists($input, $map)` 等直接校验。
|
|
86
|
+
- **隐式白名单(数组/字典等值映射查找)**:污点变量作为 key 去查找固定数组/字典,然后对查找结果做非空校验,失败则中断执行。此时污点变量的值域被限定为数组中的固定 key,不可能包含 SQL 注入 payload。
|
|
87
|
+
```php
|
|
88
|
+
// 典型模式:$input 作为 key 查找固定映射,映射失败则返回/抛出错误
|
|
89
|
+
$result = $fixed_map[$input]; // 隐式白名单:$input 必须是 $fixed_map 的某个固定 key
|
|
90
|
+
if (empty($result)) return error; // 映射失败中断
|
|
91
|
+
// 此处 $input 的值已被限定为 $fixed_map 的 key,是安全的
|
|
92
|
+
do_query("... WHERE col = '$input'");
|
|
93
|
+
```
|
|
94
|
+
- **参数化查询存在性校验**:污点变量通过参数化查询(预编译)去数据库中查询是否存在匹配记录,查询无结果则中断执行。此时污点变量的值被限定为数据库表中已有的合法记录,SQL 注入 payload 不可能作为合法记录存在。
|
|
95
|
+
```php
|
|
96
|
+
// 典型模式:使用参数化查询校验 $input 是否存在于数据库
|
|
97
|
+
$record = Model::model()->find('col=:col', [':col' => $input]);
|
|
98
|
+
if (!$record) {
|
|
99
|
+
throw new Exception('记录不存在'); // 查询无结果则中断
|
|
100
|
+
}
|
|
101
|
+
// 此处 $input 的值已被限定为数据库中已有的合法记录值,是安全的
|
|
102
|
+
do_query("... WHERE col = '$input'");
|
|
103
|
+
```
|
|
104
|
+
- **switch/case 匹配**:`switch($input) { case 'a': ... case 'b': ... default: return error; }` 模式。
|
|
105
|
+
- 上述白名单校验的核心判定标准是:**通过校验后,污点变量的可能取值是否被限定为有限的、已知的安全字符串集合**。
|
|
106
|
+
3. 代码中使用了正则表达式进行输入验证,且表达式能够防御当前场景,比如仅允许数字类型的值。
|
|
107
|
+
4. 输入数据在进入危险函数前,被预编译为安全的代码,比如使用 ORM 框架。
|
|
108
|
+
5. 输入数据经过正则替换清理,如 `preg_replace('/[^a-zA-Z0-9_]/', '', $input)` 且规则可明确排除 SQL 注入所需字符。
|
|
109
|
+
|
|
110
|
+
### 证据优先级(用于减少误判)
|
|
111
|
+
- **强证据**:PDO/mysqli 预编译并使用参数绑定(`prepare + bindValue/bindParam/execute`),且 SQL 结构未由用户输入控制。
|
|
112
|
+
- **中证据**:严格白名单/映射查找并在失败时中断执行。
|
|
113
|
+
- **弱证据**:仅依赖 `addslashes()`、长度限制、宽松正则等**无法完全排除注入风险**的措施。弱证据不能单独判定为 `false_positive`。注:`mysqli_real_escape_string()` 在正确设置字符集且用于字符串值(非 SQL 结构)时属于中证据。
|
|
114
|
+
|
|
115
|
+
### 常见危险反例(命中则倾向 true_positive)
|
|
116
|
+
- 拼接 SQL:`"..." . $input`。
|
|
117
|
+
- 在 `ORDER BY`、列名、表名等 SQL 结构位置使用用户输入。
|
|
118
|
+
- 使用 `query($sql)` 执行动态字符串且未参数绑定。
|
|
119
|
+
|
|
120
|
+
**注意**:
|
|
121
|
+
1. 仅校验参数是否为空(如 `if ($param === null || $param === '')`),没有其他校验逻辑,不能认为是误报。
|
|
122
|
+
2. `htmlspecialchars()` 仅用于 HTML/XSS 编码,不能作为 SQL 注入防护依据。
|
|
123
|
+
|
|
124
|
+
## 结果输出
|
|
125
|
+
|
|
126
|
+
将结果写入到 `analyze_report.json` 文件中,与 `parsed_result.json` 同目录。**输出必须是合法 JSON**(不允许 `//` 注释、不允许末尾逗号),可使用 `python -m json.tool` 自查。
|
|
127
|
+
|
|
128
|
+
字段说明:
|
|
129
|
+
- `vul_hash`:从 `parsed_result.json` 中拷贝的漏洞 hash,用于回写关联。
|
|
130
|
+
- `result`:取值仅限 `"true_positive"` 或 `"false_positive"`。
|
|
131
|
+
- `reason`:判定依据。误报必须给出"起到过滤作用的具体代码 + 推演链条";真漏洞必须给出"可达 payload + 拼接位置"。
|
|
132
|
+
|
|
133
|
+
文件示例(合法 JSON,可直接保存):
|
|
134
|
+
```json
|
|
135
|
+
[
|
|
136
|
+
{
|
|
137
|
+
"vul_hash": "7c3ecd91ff42281bd862b400f5b955b1",
|
|
138
|
+
"result": "false_positive",
|
|
139
|
+
"reason": "代码在第 45 行使用 intval 将 $input 强制转换为 int,转换失败返回 0;payload \"1' OR 1=1--\" 经 intval 返回 1,注入字符已被丢弃。清洗函数:intval"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"vul_hash": "a1b2c3d4e5f6...",
|
|
143
|
+
"result": "true_positive",
|
|
144
|
+
"reason": "$orderBy 经默认值兜底后直接拼入 ORDER BY 子句(app/Service/OrderService.php:78),可注入 payload \"id; DROP TABLE orders--\",未发现任何白名单/类型转换/参数化保护。"
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
```
|
package/comate-engine/assets/skills/code-security/references/vul_analysis-python_sql_injection.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# 漏洞复核流程
|
|
2
|
+
|
|
3
|
+
你是一个资深的安全审计专家,请对 Python 代码的 SQL 注入漏洞扫描结果进行复核,判断是否为误报。
|
|
4
|
+
|
|
5
|
+
漏洞复核可分为三个步骤:
|
|
6
|
+
|
|
7
|
+
1. 数据读取:仔细阅读污点追踪信息(trace)及相关的代码文件。
|
|
8
|
+
2. 漏洞分析:根据「漏洞分析」的要求判断漏洞是否为误报。
|
|
9
|
+
3. 结果输出:按照格式要求输出复核结果。
|
|
10
|
+
|
|
11
|
+
## 数据读取
|
|
12
|
+
|
|
13
|
+
阅读扫描报告 parsed_result.json,其中包含了详细的漏洞位置、数据流信息以及漏洞类型说明。
|
|
14
|
+
|
|
15
|
+
## 漏洞分析
|
|
16
|
+
|
|
17
|
+
### 分析步骤(请严格按顺序执行,满足终止条件时立即停止)
|
|
18
|
+
**Step 1 - 追踪校验逻辑**
|
|
19
|
+
从污点来源到危险函数,逐行检查是否存在对该参数的过滤/校验代码。
|
|
20
|
+
参考「误报特征」中列举的保护类型进行识别。
|
|
21
|
+
**重要**:校验逻辑可能是间接的,以下两种模式都属于有效校验:
|
|
22
|
+
- **字典查找**:如果污点变量被用作字典的 key 来查找值,且查找失败会中断执行,则该查找本身就是对污点变量的白名单校验——因为只有 key 恰好等于白名单中某个固定值时才能继续执行。
|
|
23
|
+
- **参数化查询存在性校验**:如果污点变量通过参数化查询(Django ORM、SQLAlchemy 等)去数据库查询是否存在匹配记录,且查询无结果会中断执行(raise/return),则该查询等价于隐式白名单校验。推理链条为:参数化查询本身不会产生 SQL 注入 → 查询要求数据库中存在与污点变量精确匹配的记录 → SQL 注入 payload(如 `' OR 1=1--`)不可能作为合法业务数据存在于数据库中 → payload 无法通过校验 → 后续即使拼接 SQL 也是安全的。**这种模式应判定为 false_positive**。
|
|
24
|
+
|
|
25
|
+
**边界条件**:如果被查询字段是用户可写入的(如 username、title、备注等任意字符串字段)且无字符集限制,攻击者可能预先注册 `admin' OR 1=1--` 这样的合法记录,此时存在性校验不再可靠,**不能据此判定为误报**。仅当被查询字段是系统受控、字符集受限(如纯数字 ID、枚举码、内部字典表)时,该模式才成立。
|
|
26
|
+
|
|
27
|
+
如果 Step 1 未发现任何校验逻辑,跳过 Step 2,直接进入 Step 3。
|
|
28
|
+
|
|
29
|
+
**Step 2 - 验证中断有效性**
|
|
30
|
+
如果 Step 1 发现校验逻辑,检查校验失败时是否会中断后续执行。
|
|
31
|
+
需要找到对应的 return / raise 语句(可能在被调用函数内部)。
|
|
32
|
+
**特别注意**:中断判断要考虑间接中断。例如 `value = fixed_map.get(user_input); if value is None: raise ValueError()`,校验的是 `value`(映射结果)而非 `user_input`(污点变量本身),但如果 `user_input` 不在 `fixed_map` 的 key 中则 `value` 必为 None,从而触发中断——这等价于对 `user_input` 的白名单校验。同理,`record = Model.objects.filter(name=input).first(); if not record: raise Http404()` 校验的是 `record`(查询结果),但如果 `input` 不在数据库中则 `record` 必为 None,从而触发中断——这同样等价于对 `input` 的白名单校验。
|
|
33
|
+
**提前终止决策(Step 2 完成后必须执行)**
|
|
34
|
+
如果同时满足以下两个条件,执行「终止验证」后立即输出结论,**禁止继续分析**:
|
|
35
|
+
- 条件 A:Step 1 发现了覆盖全部污点参数的校验逻辑
|
|
36
|
+
- 条件 B:Step 2 确认校验失败时会中断执行
|
|
37
|
+
|
|
38
|
+
「终止验证」:选取一个 SQL 注入 payload,
|
|
39
|
+
逐步写出代入校验函数的每一步输入和输出值,确认该 payload 确实无法通过校验。
|
|
40
|
+
验证通过后,判定为 **false_positive**,输出结论。
|
|
41
|
+
|
|
42
|
+
**Step 3 - 验证防御有效性**(仅当未触发「提前终止决策」时执行,即条件 A 或条件 B 任一不成立)
|
|
43
|
+
检查通过校验的值,其格式/范围是否能排除 SQL 注入所需的特殊字符(如引号、注释符 `--` 等)。
|
|
44
|
+
|
|
45
|
+
**Step 4 - 覆盖完整性检查**(仅当未触发「提前终止决策」时执行)
|
|
46
|
+
确认保护措施是否覆盖了进入危险函数的所有污点参数,任一参数未被保护则判定为 true_positive。
|
|
47
|
+
|
|
48
|
+
### true_positive 推演示例(参考)
|
|
49
|
+
扫描结果指出某 Python 文件中如下代码存在 SQL 注入:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
def list_orders():
|
|
53
|
+
order_by = request.args.get('order_by') or 'id'
|
|
54
|
+
sql = f"SELECT * FROM orders ORDER BY {order_by}"
|
|
55
|
+
return cursor.execute(sql).fetchall()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- Step 1:仅有默认值兜底,未发现白名单/类型转换/正则等任何能限定取值范围的逻辑("误报特征注意 #1" 明确空校验不算)。
|
|
59
|
+
- Step 2:跳过(Step 1 未发现校验)。
|
|
60
|
+
- 提前终止决策:条件 A 不成立,不能终止。
|
|
61
|
+
- Step 3:`order_by` 经 f-string 直接拼入 SQL 结构(ORDER BY 子句),可包含任意字符(如 `id; DROP TABLE orders--`)。
|
|
62
|
+
- Step 4:唯一污点参数未被保护。
|
|
63
|
+
- 结论:**true_positive**,原因记录为"`order_by` 经默认值兜底后通过 f-string 拼入 ORDER BY 子句,未做白名单限制,可注入 `id; DROP TABLE orders--` 等 payload"。
|
|
64
|
+
|
|
65
|
+
### 误报判断逻辑
|
|
66
|
+
1. 如果代码中存在有效的过滤、校验、清理逻辑,则为误报 (false_positive),有效的定义是指能够防御当前漏洞场景,具体可参考「误报特征」。
|
|
67
|
+
2. 只要存在能够防御漏洞的代码就算误报,比如强制类型转换、数字类型校验、字符串格式校验等,**不要求一定使用预编译或参数化查询等最佳实践**
|
|
68
|
+
3. 该路径在实际运行中无法触发(如死代码、测试代码等),则为误报 (false_positive)。
|
|
69
|
+
4. 其他的情况,则认为是真实存在的漏洞(true_positive)。
|
|
70
|
+
|
|
71
|
+
### 分析纪律
|
|
72
|
+
|
|
73
|
+
1. **禁止使用未验证假设**:对任何"可能存在绕过"的判断,必须附上具体 payload 代入每一步函数的输入/输出推演过程,否则该判断无效。
|
|
74
|
+
2. **找到误报证据即止步**:完成「终止验证」后,禁止继续寻找理论上的绕过方式。
|
|
75
|
+
3. **摇摆结论以推演为准**:分析过程中如结论出现反复,以最后一次有具体值代入推演支撑的结论为准,无推演支撑的推测性结论一律作废。
|
|
76
|
+
4. **禁止运行代码或编写测试**:但允许查阅 Python 标准库或第三方库的官方文档以确认函数行为(例如 `int()` 对带特殊字符输入的行为),不得通过实际执行代码来验证。
|
|
77
|
+
5. **证据不足时保守判定**:如果数据流不完整、关键代码缺失(如外部闭源依赖、自定义 ORM 内部行为不明)、无法对所有污点参数进行完整推演,**判定为 true_positive**,并在 `reason` 中说明缺失的证据。误报需要正面证据,而真漏洞是默认假设。
|
|
78
|
+
|
|
79
|
+
### 误报特征
|
|
80
|
+
代码中存在以下特征可认为是安全的,可以判定为误报:
|
|
81
|
+
|
|
82
|
+
1. 输入数据在进入危险函数前,经过了强制类型转换,参数被转换为数字类型(如 `int()`、`float()`,或 `Decimal` 后再做范围校验)。
|
|
83
|
+
2. 输入数据在进入危险函数前,经过了白名单过滤,包括但不限于以下形式:
|
|
84
|
+
- **显式白名单**:`input in whitelist` 或 `input in dict_map` 等直接校验。
|
|
85
|
+
- **隐式白名单(字典/列表等值映射查找)**:污点变量作为 key 去查找固定字典,然后对查找结果做非空校验,失败则中断执行。此时污点变量的值域被限定为字典中的固定 key,不可能包含 SQL 注入 payload。
|
|
86
|
+
```python
|
|
87
|
+
# 典型模式:input 作为 key 查找固定字典,映射失败则抛出异常
|
|
88
|
+
value = fixed_map.get(input)
|
|
89
|
+
if value is None:
|
|
90
|
+
raise ValueError("invalid input")
|
|
91
|
+
# 此处 input 的值已被限定为 fixed_map 的 key,是安全的
|
|
92
|
+
cursor.execute("... WHERE col = '%s'" % input)
|
|
93
|
+
```
|
|
94
|
+
- **参数化查询存在性校验**:污点变量通过参数化查询(ORM)去数据库中查询是否存在匹配记录,查询无结果则中断执行。此时污点变量的值被限定为数据库表中已有的合法记录,SQL 注入 payload 不可能作为合法记录存在。
|
|
95
|
+
```python
|
|
96
|
+
# 典型模式:使用 ORM 参数化查询校验 input 是否存在于数据库
|
|
97
|
+
record = Model.objects.filter(name=input).first()
|
|
98
|
+
if not record:
|
|
99
|
+
raise Http404("记录不存在")
|
|
100
|
+
# 此处 input 的值已被限定为数据库中已有的合法记录值,是安全的
|
|
101
|
+
```
|
|
102
|
+
- **if/elif 链或字典映射**:类似 switch/case 的等值匹配模式,default 分支中断执行。
|
|
103
|
+
- 上述白名单校验的核心判定标准是:**通过校验后,污点变量的可能取值是否被限定为有限的、已知的安全字符串集合**。
|
|
104
|
+
3. 代码中使用了正则表达式进行输入验证,且表达式能够防御当前场景,比如仅允许数字类型的值。
|
|
105
|
+
4. 输入数据在进入危险函数前,被预编译为安全的代码,比如使用 ORM 框架或 DB-API 参数绑定。
|
|
106
|
+
|
|
107
|
+
### 证据优先级(用于减少误判)
|
|
108
|
+
- **强证据**:参数化查询(`cursor.execute(sql, params)`、ORM filter 绑定参数)且未拼接用户输入到 SQL 结构。
|
|
109
|
+
- **中证据**:严格白名单/字典映射查找并在失败时中断执行。
|
|
110
|
+
- **弱证据**:仅依赖 `replace` 清理、长度限制、宽松正则。弱证据不能单独判定为 `false_positive`。
|
|
111
|
+
|
|
112
|
+
### 常见危险反例(命中则倾向 true_positive)
|
|
113
|
+
- 使用 f-string、`%`、`.format()` 拼接 SQL。
|
|
114
|
+
- 在 `order by`、列名、表名等 SQL 结构位置使用用户输入。
|
|
115
|
+
- `cursor.execute(dynamic_sql)` 执行动态拼接 SQL 且无参数绑定。
|
|
116
|
+
|
|
117
|
+
**注意**:
|
|
118
|
+
1. 仅校验参数是否为空(如 `if param is None` 或 `if not param`),没有其他校验逻辑,不能认为是误报。
|
|
119
|
+
|
|
120
|
+
## 结果输出
|
|
121
|
+
|
|
122
|
+
将结果写入到 `analyze_report.json` 文件中,与 `parsed_result.json` 同目录。**输出必须是合法 JSON**(不允许 `//` 注释、不允许末尾逗号),可使用 `python -m json.tool` 自查。
|
|
123
|
+
|
|
124
|
+
字段说明:
|
|
125
|
+
- `vul_hash`:从 `parsed_result.json` 中拷贝的漏洞 hash,用于回写关联。
|
|
126
|
+
- `result`:取值仅限 `"true_positive"` 或 `"false_positive"`。
|
|
127
|
+
- `reason`:判定依据。误报必须给出"起到过滤作用的具体代码 + 推演链条";真漏洞必须给出"可达 payload + 拼接位置"。
|
|
128
|
+
|
|
129
|
+
文件示例(合法 JSON,可直接保存):
|
|
130
|
+
```json
|
|
131
|
+
[
|
|
132
|
+
{
|
|
133
|
+
"vul_hash": "7c3ecd91ff42281bd862b400f5b955b1",
|
|
134
|
+
"result": "false_positive",
|
|
135
|
+
"reason": "代码在第 45 行使用 int() 将 input 强制转换为整数,转换失败抛 ValueError;payload \"1' OR 1=1--\" 经 int() 抛异常,无法到达 cursor.execute。清洗函数:int"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"vul_hash": "a1b2c3d4e5f6...",
|
|
139
|
+
"result": "true_positive",
|
|
140
|
+
"reason": "order_by 参数经默认值兜底后通过 f-string 拼入 ORDER BY 子句(app/services/order_service.py:78),可注入 payload \"id; DROP TABLE orders--\",未发现任何白名单/类型转换/参数化保护。"
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
```
|