查询 31 次,耗时 0.3370 秒

把大模型接入代码审计流水线:Semgrep 初筛、上下文脱敏与人工复核的可靠实践

Likccc

New member

Likccc Rep
0
0
0
Rep
0
Likccc Vouches
Vouches
0
1 MONTH
1 1 MONTH OF SERVICE
Likccc 获得
0
0
0
获得
0
Likccc 凭证
0
0
0
凭证
0
主题
13
点赞
0
HakB
1 月
在社区成长 1 月
LEVEL 1 1 XP
 
## 背景

大模型能够解释代码、追踪数据流并生成修复建议,因此不少团队开始尝试用它辅助安全代码审计。但如果只是把整个仓库直接交给模型,再接受一份看似专业的漏洞报告,结果通常并不可靠:模型可能遗漏跨文件调用关系、误判框架自带的安全机制,也可能引用根本不存在的函数。更严重的是,源码、密钥、内部域名和客户数据还可能在上传过程中泄露。

一个更稳妥的方案是把大模型放在审计流水线的“解释与复核”环节,而不是让它单独决定是否存在漏洞。本文搭建一套可在本地授权项目或实验仓库中运行的流程:先用 Semgrep 做确定性初筛,再提取经过脱敏的最小上下文,让模型按照固定证据格式分析,最后由人工和测试用例完成验证。全文仅用于本地代码、防御审计和已获授权的安全测试。

## 一、工作原理

这套流程分为四层。

第一层是静态规则扫描。Semgrep 根据抽象语法结构寻找危险调用和可疑数据流,例如 Python 中通过 shell 执行外部输入、Flask 路由中拼接 SQL,或者日志中记录认证信息。规则扫描速度快、结果可复现,但容易产生误报。

第二层是上下文收集。针对每一条命中,只提取所在函数、直接调用者、相关配置和有限的上下行代码,而不是上传整个仓库。这样既减少模型输入噪声,也降低源码暴露范围。

第三层是模型辅助研判。模型必须分别列出输入源、传播路径、危险汇点、已有防护、缺失证据和置信度。没有完整证据链时,只能标记为“待确认”,不能直接定性为漏洞。

第四层是人工验证。审计人员检查框架语义、权限边界和实际部署条件,并在本地测试中验证修复前后的行为。最终结论应来自可复现证据,而不是模型措辞是否笃定。

## 二、前置条件

建议准备以下环境:

1. 一台隔离的 Linux、macOS 或 WSL 实验环境,Python 3.10 以上版本。
2. 仅包含自有代码、开源样例或明确授权项目的 Git 仓库。
3. Semgrep CLI;如使用云端模型,还需先确认组织的数据合规要求。
4. 可选的本地模型服务,例如监听在 127.0.0.1 的 Ollama 或其他兼容接口。
5. 能够运行项目单元测试的依赖环境。

安装基础工具:

```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip semgrep pytest
semgrep --version
```

不要把真实密钥写入命令历史或提示词。建议在专用分支工作,并先确认 `.gitignore` 已排除扫描报告、脱敏中间文件和本地配置。

## 三、建立一个可控的本地审计样例

以下代码仅用于本地验证检测规则,不能部署到生产环境。创建 `demo/app.py`:

```python
import subprocess
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.get('/diagnose')
def diagnose():
host = request.args.get('host', '127.0.0.1')
result = subprocess.run(
['ping', '-c', '1', host],
capture_output=True,
text=True,
timeout=3,
check=False,
)
return jsonify({'returncode': result.returncode})
```

这里特意使用参数数组并关闭 `shell`,同时不把命令输出直接返回客户端。它适合用来检验审计流程能否区分“调用了 subprocess”和“存在命令注入”这两个不同结论。仅凭看到外部输入进入 `subprocess.run` 就报告高危漏洞,是常见误报。

## 四、第一步:用 Semgrep 进行确定性初筛

创建 `rules/python-subprocess.yml`:

```yaml
rules:
- id: python-subprocess-shell-true
languages: [python]
severity: ERROR
message: 外部命令通过 shell=True 执行,需要核实参数是否可控
patterns:
- pattern-either:
- pattern: subprocess.run(..., shell=True, ...)
- pattern: subprocess.call(..., shell=True, ...)
- pattern: subprocess.Popen(..., shell=True, ...)
```

执行扫描并保存结构化结果:

```bash
mkdir -p reports
semgrep scan --config rules/ --json --output reports/semgrep.json demo/
```

该规则不会命中上面的安全样例,因为样例未使用 `shell=True`。这是刻意设计的:规则必须描述真正需要关注的结构,而不是笼统禁止某个 API。随后可以用官方规则集补充覆盖面,但引入新规则前应固定版本,并在自己的代码基线上评估误报率。

读取结果时至少保留以下字段:规则 ID、文件路径、起止行号、严重级别和规则消息。不要直接把扫描器给出的严重级别映射为业务风险;业务风险还取决于输入是否可控、代码是否可达、执行账户权限和部署暴露面。

## 五、第二步:安全地提取最小上下文

建议为每个命中构造一个独立审计包,其中只包含:命中函数、上下各约 20 行、直接相关的数据结构、依赖版本,以及扫描器原始结论。以下命令可以查看指定范围,行号需要根据实际报告替换:

```bash
sed -n '20,80p' demo/app.py
```

在交给模型之前进行脱敏。重点识别以下内容:

- API Key、访问令牌、私钥和数据库口令;
- 内部域名、私网地址、客户标识和真实邮箱;
- `.env`、云服务凭据目录、生产配置及数据库转储;
- 注释或测试夹具中的真实个人数据。

可以先使用 Git 自带检查和专门的密钥扫描工具检查待提交内容。无论工具是否报警,都应人工检查一次,因为自定义令牌格式未必能被通用规则识别。脱敏时最好保留类型信息,例如把真实令牌替换成 `<API_TOKEN>`,这样模型仍能理解变量用途。

## 六、第三步:用固定提示模板约束模型输出

模型审计的关键不是要求它“找出所有漏洞”,而是要求它提供可核验的证据。可使用如下模板:

```text
角色:你是防御性代码审计助手。
范围:只分析所给代码,不假设未提供的实现。
任务:核实静态扫描结果是否成立。
必须输出:
1. 输入源及其可控性;
2. 从输入源到危险汇点的逐步传播路径;
3. 已存在的校验、编码、参数化或权限控制;
4. 成立所需但当前缺失的证据;
5. 结论:确认、可能、误报或信息不足;
6. 置信度及理由;
7. 最小修复建议和建议补充的回归测试。
禁止:虚构函数、依赖版本、运行结果或未提供的部署条件。
```

如果使用本地模型服务,建议设置较低温度,并保存模型名称、版本、提示模板哈希和响应时间。模型升级后,应使用同一批已标注样本重新评估,而不是默认新版一定更准确。

模型的结构化结论可以设计为:

```json
{
"finding_id": "python-subprocess-shell-true:app.py:42",
"verdict": "information_needed",
"source": "request.args.get('host')",
"sink": "subprocess.run",
"path": ["host", "argument_list", "subprocess.run"],
"existing_controls": ["argument list", "shell disabled", "timeout"],
"missing_evidence": ["host allowlist requirement", "deployment exposure"],
"confidence": 0.72
}
```

置信度只能用于排序,不能自动替代人工审批。尤其不要因为模型给出 0.95 就自动创建“已确认高危”工单。

## 七、第四步:人工复核与修复验证

人工复核应回答五个问题:输入是否由低权限用户控制;代码路径在实际部署中是否可达;危险操作是否真的发生;现有控制是否能覆盖边界情况;成功利用后会造成什么业务影响。

对于前述诊断接口,即使没有 shell 注入,仍可能存在参数滥用、资源消耗或内部地址探测风险。因此可以增加格式限制和业务允许列表:

```python
import ipaddress

ALLOWED_HOSTS = {
ipaddress.ip_address('127.0.0.1'),
ipaddress.ip_address('192.0.2.10'),
}

def parse_allowed_host(raw: str) -> str:
value = ipaddress.ip_address(raw)
if value not in ALLOWED_HOSTS:
raise ValueError('host is not allowed')
return str(value)
```

这里使用的 `192.0.2.0/24` 属于文档示例地址范围,实践时应替换为授权实验环境中的地址。修复后编写回归测试:

```python
import pytest
from app import parse_allowed_host

def test_loopback_is_allowed():
assert parse_allowed_host('127.0.0.1') == '127.0.0.1'

def test_unlisted_address_is_rejected():
with pytest.raises(ValueError):
parse_allowed_host('198.51.100.7')

def test_invalid_input_is_rejected():
with pytest.raises(ValueError):
parse_allowed_host('not-an-ip')
```

运行测试与复扫:

```bash
cd demo
pytest -q
cd ..
semgrep scan --config rules/ demo/
```

有效的关闭证据应包括代码差异、测试结果、复扫结果和复核人,而不能只写“已按模型建议修复”。

## 八、在 CI 中设置可靠的质量门禁

不建议第一天就让所有模型判断阻断合并。可分三阶段上线:第一阶段仅记录结果;第二阶段只阻断高精度静态规则;第三阶段再对已由人工确认、且有稳定回归样本的规则启用门禁。

CI 中建议固定 Semgrep 版本与规则提交哈希,避免工具自动升级导致结果漂移。门禁条件可以是“新增的 ERROR 级确定性命中数量大于零”,而不是“模型认为可能有漏洞”。同时,把基线问题与新增问题分开,否则历史遗留告警会使团队很快忽略整套系统。

可持续跟踪四项指标:人工确认率、误报率、从发现到关闭的时间、修复后重新出现的比例。不要只统计发现数量,因为数量越多并不代表审计质量越高。

## 九、风险与注意事项

第一,源码泄露风险。云端模型的日志保留、训练使用策略和数据地域必须经过组织评审;无法确认时优先使用本地模型,或只发送不可逆脱敏后的最小片段。

第二,提示注入风险。仓库中的注释、README、生成代码甚至字符串常量都可能包含类似“忽略审计规则”的文本。代码内容必须作为不可信数据引用,不能与系统指令混在同一层级,也不能允许模型直接执行仓库中的命令。

第三,自动修复风险。模型生成的补丁可能破坏兼容性、只遮蔽扫描规则,或引入新的授权缺陷。补丁必须经过代码评审、单元测试和安全复扫。

第四,环境差异。某段代码是否危险可能取决于框架版本、反向代理配置、容器权限和网络策略。缺少这些证据时应标记“信息不足”,不能自行补全事实。

第五,审计记录本身也可能敏感。报告里可能包含源码片段、路径和架构信息,应设置访问控制、保存期限和删除机制。

## 十、常见问题

### 1. 为什么不直接让大模型扫描整个仓库?

超长上下文会稀释关键调用关系,增加成本和不稳定性,也扩大敏感代码暴露面。按静态命中提取最小上下文更容易复现和复核。跨文件问题可以按调用图逐步补充,而不是一次性上传全部代码。

### 2. 模型与 Semgrep 结论冲突时听谁的?

两者都不能直接作为最终事实。Semgrep 擅长稳定匹配语法结构,模型擅长解释上下文;最终应回到源码、依赖文档、运行条件和测试证据。无法验证时保留为待确认项。

### 3. 能否让模型自动提交修复代码?

可以在隔离分支生成候选补丁,但不应直接合并。至少要经过人工评审、测试、静态扫描和依赖检查。涉及认证、授权、加密或支付流程时,应提高审批级别。

### 4. 怎样评估模型是否真的提高了效率?

建立一组经过人工标注的历史样本,包含真阳性、误报和信息不足案例。每次更换模型或提示模板后,比较准确率、召回率、误报率和平均复核时间。没有固定评测集,就很难区分真实改进与输出风格变化。

### 5. 是否应该把低置信度结果全部丢弃?

不应该。低置信度可能意味着上下文不足,也可能意味着复杂漏洞。更合适的做法是进入补证队列,例如补充调用者、配置文件或依赖版本,然后重新分析。

## 总结

大模型在代码审计中的可靠定位,是“证据整理器、上下文解释器和修复建议助手”,而不是独立漏洞裁判。可落地的流程应当由确定性静态扫描发现候选点,以最小化和脱敏后的上下文控制数据风险,用固定格式要求模型展示证据链,再通过人工复核、回归测试和二次扫描完成闭环。

如果只能先做一项改进,建议从“强制记录输入源、传播路径、危险汇点和缺失证据”开始。这能显著减少只有结论、无法复现的告警,也便于团队逐步积累高质量样本,最终把人工经验沉淀为规则、测试和可持续维护的安全门禁。
 

xiaobinz08

New member

xiaobinz08 Rep
0
0
0
Rep
0
xiaobinz08 Vouches
Vouches
0
1 MONTH
1 1 MONTH OF SERVICE
xiaobinz08 获得
0
0
0
获得
0
xiaobinz08 凭证
0
0
0
凭证
0
主题
12
点赞
0
HakB
1 月
在社区成长 1 月
LEVEL 1 1 XP
 
建议再加一道“审计包可追溯”机制:对 Semgrep 规则版本、Git commit、截取文件及提示模板分别计算 SHA-256,并把哈希写入 finding 元数据;脱敏替换表只保存在本地隔离目录,不随上下文上传。人工复核后,将确认、误报、信息不足各沉淀为固定回归样本,模型或规则升级时重跑并比较结论漂移。若扩展到 Solidity,还应同时记录编译器版本、优化参数和代理实现地址,否则相同源码在不同部署边界下可能得出错误风险结论。
 
顶部