CodeQL与Semgrep规则编写深度对比:静态代码分析工具的技术差异

本文详细对比CodeQL和Semgrep两种静态代码分析工具的规则编写方式,涵盖语法结构、数据流分析、污点跟踪实现差异,以及开发环境和迭代体验的技术细节分析。

Rule Writing for CodeQL and Semgrep

语法和数据结构 🔗

CodeQL和Semgrep OSS是主要支持自定义规则的免费代码分析工具。但它们的规则语法差异巨大,这也是CodeQL主要学习曲线所在。

CodeQL使用QL语言,看起来像典型的SQL查询,包含select和where子句:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/* github/codeql/blob/main/javascript/ql/src/Electron/AllowRunningInsecureContent.ql */
/**
 * @name 启用Electron allowRunningInsecureContent
 * @description 启用allowRunningInsecureContent可能导致远程代码执行
 * @kind problem
 * @problem.severity error
 * @security-severity 8.8
 * @precision very-high
 * @tags security
 *       frameworks/electron
 *       external/cwe/cwe-494
 * @id js/enabling-electron-insecure-content
 */

import javascript

from DataFlow::PropWrite allowRunningInsecureContent, Electron::WebPreferences preferences
where
  allowRunningInsecureContent = preferences.getAPropertyWrite("allowRunningInsecureContent") and
  allowRunningInsecureContent.getRhs().mayHaveBooleanValue(true)
select allowRunningInsecureContent, "强烈不建议启用allowRunningInsecureContent"

但这实际上是个陷阱:将QL当作数据库查询语言只会导致失败。正如文档所述,QL基于Datalog,“一种声明式逻辑编程语言,常用作查询语言”。我认为QL是一种用于进行查询的面向对象编程语言。要编写好的QL,需要非常熟悉CodeQL标准库中的各种类及其谓词(即方法)。

相比之下,Semgrep规则语法是面向模式的。以下Semgrep规则检查与CodeQL示例相同的漏洞:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# ajinabraham/njsscan/blob/master/njsscan/rules/semantic_grep/electronjs/security_electron.yaml
rules:
  - id: electron_allow_http
    patterns:
      - pattern-either:
          - pattern: >
              new BrowserWindow({webPreferences: {allowRunningInsecureContent:
              true}})              
          - pattern: |
                            var $X = {webPreferences: {allowRunningInsecureContent: true}};
    message: >-
      应用程序可以加载HTTP内容,这使得应用容易受到中间人攻击
    languages:
      - javascript
    severity: ERROR
    metadata:
      owasp-web: a6
      cwe: cwe-319

识别真实漏洞的高质量规则的试验场是污点跟踪。虽然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管道中运行。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计