sql注入规则常漏报format和f-string,因semgrep默认仅匹配execute()等显式调用,而拼接发生在字符串构造阶段;需锚点前移至插值语法、结合污点追踪与上下文排除orm等安全模式。

SQL注入规则为什么总漏报 format 和 f-string?
因为 Semgrep 默认规则大多只匹配 execute()、execute_many() 这类显式调用,而 Python 中大量 SQL 拼接发生在字符串构造阶段——f"SELECT * FROM users WHERE id = {user_id}" 或 "SELECT ...".format(id=user_id) 根本没进数据库函数体,静态分析器一眼扫过去就跳过了。
真正要捕获这类“黑洞”,得把规则锚点前移到字符串拼接行为本身,并结合上下文判断是否最终流入执行函数。实操建议:
- 用
pattern-either同时覆盖f"、.format(、%三类插值语法,别只盯f-string - 必须加
metavariable-regex限制变量名含sql、query、stmt等关键词,否则会误报日志拼接 - 用
taint-mode: taint+sources(如request.args.get)和sinks(如cursor.execute)做污点追踪,比纯模式匹配准得多
如何让规则不把 ORM 查询当漏洞报?
像 User.query.filter(User.id == user_id).all() 或 session.execute(text("...")) 这类用法本身安全,但 Semgrep 模式若写成 text("...") 就会无差别命中——它不理解 text() 是 SQLAlchemy 的安全封装,更不知道 .filter() 自动参数化。
绕过误报的关键是「白名单式排除」:
- 在
patterns下用pattern-not显式排除text(...)、.filter(、.where(等 ORM 特征调用 - 对
cursor.execute类规则,加pattern-not-inside排除包裹在try/except且含sqlalchemyimport 的文件(说明项目用了 ORM) - 慎用
pattern-not直接排除整个函数名,比如排除execute会导致漏掉原生 DB-API 调用——应限定在 ORM 上下文中排除
semgrep --config 本地规则跑不通?检查这三处
写完 YAML 规则丢进 .semgrep/rules/ 目录,运行却提示 No matches found 或直接报错,大概率卡在这几个地方:
- YAML 缩进错位:Semgrep 对空格极其敏感,
patterns必须顶格,其子项(如pattern、focus-metavariable)统一缩进 2 空格,多一个少一个都解析失败 - 语言声明写错:
language: python不能写成lang: py或漏掉——Semgrep 不猜语言,没声明就跳过该规则 - 路径未包含:用
semgrep --config ./rules/sql-inject.yaml时,确保当前目录能访问到该文件;若用--config auto,规则必须放在.semgrep/rules/且文件名以.yml结尾(不是.yaml)
为什么生产环境要禁用 pattern 单独匹配?
单独靠 pattern: 'f"SELECT * FROM $TABLE WHERE id = {$ID}"' 这种写法,在真实代码库中基本等于无效——变量名千奇百怪(user_input、req_id、pk),表名动态拼接(f"logs_{year}"),甚至 SQL 被拆成多行字符串加 += 拼接。这种规则只能当玩具玩。
实际可用的规则必须组合使用:
- 用
metavariable-pattern抓取所有疑似用户输入的变量(如request.*.get、args[、json.get() - 用
taint-mode: taint追踪该变量是否经由字符串插值进入execute/executemany参数 - 最后用
pattern-not-inside排除已知安全上下文(如被escape_string()包裹、或出现在logging.info()中)
真正难的不是写一条规则,而是让多条规则协同判断「这段 SQL 构造链里有没有未经消毒的外部输入」——漏掉任意一环,要么误报炸屏,要么漏报留洞。











