6.2 CodeQL污点追踪


6.2 CodeQL污点追踪

本节摘要:CodeQL 是 GitHub 的语义分析引擎,社区热度约 10.2k stars(2026-10 社区口径)。它的心智模型是一句话:把代码当数据库查询。本节先走通链路——database create 建库、.ql 查询、analyze 出报告;再讲 QL 语言的声明式思想:你描述「要什么」,引擎负责「怎么找」,递归与传递闭包是语言内建能力;然后拆解一条污点追踪查询的三要素 source、sink、path,看数据流查询如何跨函数跨文件落地,并做一次「换 sink」的改写练习;接着解释变体发现——一条查询为什么能横扫上千个仓库;最后划清授权边界:查询库 MIT 开源,开源与研究用途免费,商业 CI 需要授权。示例对象仍是自建 Flask 项目。

学习目标

  • 完成 CodeQL 建库到查询落地的完整链路,说清每步产出。
  • 理解 QL 的声明式思想,与过程式脚本的分析方式对比。
  • 拆解污点追踪查询的 source、sink、path 结构并能改写复用。
  • 说明变体发现的原理与典型用法。
  • 准确说明开源免费与商业授权的边界。

一、把代码当数据库

CodeQL 的第一课不是学语法,而是换心智模型:分析不是「跑脚本遍历代码」,而是「对代码数据库执行查询」。建库时,抽取器把源码解析成一张张关系表——函数表、调用表、表达式表、赋值表;查询时,QL 引擎在这些表上做逻辑推导。

# cql_flow.sh —— CodeQL 链路:建库、查询、出报告(CLI 自 GitHub 官方发布页获取) codeql database create flask-db --language=python --source-root=./myflask codeql database analyze flask-db CmdInjection.ql --format=sarif-latest --output=out.sarif ​

两条命令的关键差异要记住:Python、JavaScript 这类解释型语言建库不需要编译;C、C++、Java 需要 --command 捕获真实构建过程(比如 --command="make"),因为抽取器要看到完整的类型与宏展开信息。这也是它比 semgrep「重」的根源——语义信息的代价是构建环境。

# db_pipeline.txt —— 建库到报告的数据流示意 源码 ──database create──▶ 代码数据库(关系表集合) │ .ql 查询(逻辑推导) │ ▼ SARIF/表格形式的路径级发现 ​

建库完成后可以验证规模:codeql database info flask-db(子命令以官方文档为准)能列出抽取到的文件数、函数数等规模信息——建库是「一次投入、多次查询」的资产,日常迭代只改查询不改库。

二、QL 的声明式思想

QL 是声明式语言:查询描述「什么条件成立」,不描述「按什么顺序去找」。对比一段心算:

  • 过程式思路:从 main 开始遍历调用图,遇到 request 记一笔,遇到 system 记一笔,自己维护工作队列——你要管遍历、去重、终止。
  • 声明式思路:声明「存在一条从 request 到 system 的数据流」,引擎负责找——递归、传递闭包都是语言内建能力。

QL 的核心构件是谓词(predicate):一个返回真假的逻辑判断,可以组合、可以递归。查询主体是 from-where-select 三段:声明变量、施加约束、输出结果,形似 SQL 而推理能力强得多。

过程式脚本的负担 QL 里的对应物
手写遍历与工作队列 引擎自动搜索
手工去重与记忆化 逻辑变量的唯一性约束
自己实现传递闭包 谓词递归一行搞定
终止条件靠人保证 引擎保证半可判定安全

第一次写 QL 的常见不适应是「想写循环」——忍住,把循环改写成谓词的递归定义,通常三五行就够。第二个不适应是「调试」:QL 没有 print 断点,调试方法是把中间谓词写成小查询逐个 select,看它命中的是哪些元素——这反而逼着你把大问题拆成可命名的小断言。

三、污点追踪查询的结构:source、sink、path

污点追踪是 CodeQL 的招牌能力。任何一条污点查询都由三要素构成:污染源 source(外部输入从哪进来)、汇聚点 sink(危险操作在哪)、以及连接两者的数据流路径 path。以 Python 项目为例,查询骨架如下(写法示意,API 细节以官方查询库文档为准):

# CmdInjection.ql —— CodeQL 污点查询示意:请求参数流向命令执行 import python module CmdInjectionConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { exists(Call call | call.getFunc().(Attribute).getName() = ["get", "post"] and call.getFunc().(Attribute).getAttr().getName() = "args" and call = source.asCall() ) } predicate isSink(DataFlow::Node sink) { exists(Call call | call.getFunc().(Name).getId() = "system" and sink.asExpr() = call.getArg(0) ) } } module Flow = DataFlow::Global<CmdInjectionConfig>; from Flow::PathNode source, Flow::PathNode sink where Flow::flowPath(source, sink) select sink.getNode(), source, sink, "用户输入流向命令执行" ​

结构读法:isSource 定义「什么算外部输入」,isSink 定义「什么算危险出口」,flowPath 负责中间的一切——跨函数、跨文件、经过赋值与容器操作。select 输出的不只是两端,还有完整路径节点序列,这就是 path 要素:证据自带的调用链。

对照表帮记忆三要素与 6.1 节的映射:

要素 CodeQL 里的形态 semgrep taint 对应物 提问
source isSource 谓词 pattern-sources 输入从哪进来
sink isSink 谓词 pattern-sinks 危险在哪发生
path flowPath 推导 引擎内建 中间经过了什么
屏障 障碍谓词(可选) pattern-sanitizers 什么算净化

改写练习:把 isSink 换成 open 调用即得路径穿越查询;再加一个屏障谓词,把已知安全的封装函数排除——这与 6.1 节 sanitizer 的思路一一对应,只是粒度从「一行净化调用」升级到「任意自定义屏障逻辑」。改写后先造一个必命中的测试函数放进项目(阳性对照),查询抓到它再扫全库——这是第 10.2 节会反复出现的评测基本功。

四、变体发现:一条查询横扫千仓

变体发现(variant analysis)是 CodeQL 区别于单仓库扫描工具的战略能力,思路分三步:

  • 一次:某个新漏洞修了,把它抽象成一条污点查询。
  • 一次跑多个仓库:用同一查询扫组织内所有相关仓库,或经 GitHub 的变体分析能力扫海量公开仓库。
  • 一次收一片:命中者大概率是同源缺陷的变体,按路径证据逐个确认。
# variant_flow.txt —— 变体发现的复用路径 新漏洞修复补丁 ──抽象──▶ 污点查询(缺陷模式) │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ 仓库 A 仓库 B 仓库 C ... │ │ │ └───────┬───────┴───────┬───────┘ ▼ ▼ 命中:同源变体 未命中:排除 ​

对个人研究者的日常用法更朴素:把 6.2 节练手的查询留存成库,每接手一个新项目先跑一遍——查询就是可复用的「缺陷模式记忆」。第 10 章会看到,这种「缺陷模式沉淀」与 AI 挖洞方法论是互补关系:模型负责提出新的模式假设,CodeQL 负责在全仓库范围内验证。

五、授权边界与练习资源

引用与使用 CodeQL 前先把授权说清楚(以 GitHub 官方条款为准):

事项 口径
查询库与标准库 github/codeql 仓库中开源,MIT 许可证
开源项目与研究用途 免费使用
企业内部代码的商业 CI 需要 GitHub 相应授权(Advanced Security 体系)
学习资源 官方文档提供教学查询与 CTF 练习资源,codeql 仓库内含大量示例

对本书读者最实际的建议:研究、学习、开源贡献三类场景都在免费范围内;把能力建设花在查询编写上,而不是引擎本身。社区也流传大量第三方查询集,采用前先审一遍内容——查询本身也是代码,也该被 review;这一点在把查询接入第 10 章的 AI 流程时尤其重要,因为一条写错的查询会把幻觉合法化成「机器结论」。

本节要点回顾

  • 链路三步:database create 建库、.ql 查询、analyze 出 SARIF;编译型语言建库要真实构建。
  • QL 是声明式语言:谓词组合加递归,循环思维要翻译成逻辑定义。
  • 污点查询三要素 source、sink、path,输出自带完整数据流路径作为证据。
  • 变体发现让一条查询成为可复用的缺陷模式记忆。
  • 查询库 MIT 开源;研究免费;商业 CI 授权——边界以官方条款为准。

一轻一重两台引擎都转起来了:semgrep 秒级快筛、CodeQL 深度污点。但它们各是各的模型——一个活在规则文件里,一个活在数据库里。下一节先把两者放进同一张对比表,再回答一个容易混淆的问题:这和《语义代码检索》讲的「检索式理解」到底是不是一回事。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U