Rule Writing for CodeQL and Semgrep
语法和数据结构 🔗
CodeQL和Semgrep OSS是主要支持自定义规则的免费代码分析工具。但它们的规则语法差异巨大,这也是CodeQL主要学习曲线所在。
CodeQL使用QL语言,看起来像典型的SQL查询,包含select和where子句:
|
|
但这实际上是个陷阱:将QL当作数据库查询语言只会导致失败。正如文档所述,QL基于Datalog,“一种声明式逻辑编程语言,常用作查询语言”。我认为QL是一种用于进行查询的面向对象编程语言。要编写好的QL,需要非常熟悉CodeQL标准库中的各种类及其谓词(即方法)。
相比之下,Semgrep规则语法是面向模式的。以下Semgrep规则检查与CodeQL示例相同的漏洞:
|
|
识别真实漏洞的高质量规则的试验场是污点跟踪。虽然Semgrep仅在付费的Semgrep Pro产品中提供全局污点跟踪,但CodeQL开箱即用。
这是因为两种工具对源代码的建模方式不同。Semgrep将代码解析并表示为一个通用抽象语法树(AST),然后将其转换为可以匹配Semgrep规则语法模式的中间语言(IL)。
这种解析到通用AST再转换到IL的方法使得添加语言支持更加容易,但AST对于污点跟踪不是最优的。树结构不是有向图,因此不能直接提供数据或控制流信息;你需要数据流图(DFG)和控制流图(CFG)。解析和转换步骤还会丢失关于语言特定行为的细节,如类继承和全局变量传播。这些都反映在Semgrep污点跟踪的设计权衡中:
- 无路径敏感性:考虑所有可能的执行路径,尽管有些可能不可行
- 无指针或形状分析:可能无法检测到以非平凡方式发生的别名
- 无健全性保证:Semgrep忽略类似eval函数对程序状态的影响
相比之下,CodeQL尝试在每个语言基础上提取尽可能多的关系数据,包括数据和控制流。它不是将每种语言解析为通用AST,而是为每种语言运行自定义提取器。
上下文切换 🔗
就规则编写而言,CodeQL的QL是一种面向对象的编程语言。你会花大量时间阅读CodeQL库文档,这些文档通常缺乏使用示例,并且充满了子类型、(直接和间接)超类型、分支类型、联合类型等。
相比之下,Semgrep的模式语法看起来非常像你正在扫描的实际代码。你可以从精确匹配开始,然后逐渐抽象出通用项(如变量名和序列)来推广你的规则。
迭代 🔗
CodeQL的杀手级功能是VS Code扩展。它很好地集成到类似IDE的环境中,允许你从用户界面重建数据库和可视化规则发现,而不是通过CLI。
Semgrep的迭代杀手级功能是Semgrep Playground,这是一个Web应用程序,允许你在同一窗口中编写测试规则。它会高亮显示规则匹配的代码行,以便快速纠正错误。
开发环境 🔗
虽然Semgrep可以作为pip包轻松安装,但它不能在Windows上运行。当然,你可以在Windows子系统 for Linux(WSL2)上运行,但在从Linux VM访问挂载的Windows文件系统时,WSL2在文件I/O方面会受到巨大的性能影响。
虽然CodeQL的二进制文件可以在Windows上运行,但设置稍微复杂一些,因为你需要正确配置工作空间才能编写自定义查询。
最终想法 🔗
总的来说,我发现Semgrep规则编写更容易上手,这主要归功于其查询代码库的方法。我可以快速编写简单规则、迭代并查看结果,而CodeQL可能会导致文档和调试的无底洞。
很大程度上还取决于你的代码扫描需求。如果你为组织的DevSecOps管道构建规则集,你希望扫描速度快,并且与大量不同的代码库兼容,而不必担心某些东西会崩溃(如构建步骤)。你希望最小化误报,因为没有人有时间处理警报。
另一方面,渗透测试人员或漏洞研究人员可以处理误报;你更希望减少漏报。速度不是问题,因为你在本地运行扫描而不是在CI管道中运行。