比较Semgrep和CodeQL
2022年10月6日 - 作者:Anthony Trummer
引言
最近,我们的一位客户要求我们对R2c的Semgrep和GitHub的CodeQL进行正面测试。Semgrep是开源且免费的(提供高级选项)。CodeQL"对研究和开源项目免费",并接受对其库和查询的开源贡献,但对大多数公司并非免费。我们的许多工程师已经频繁使用Semgrep,因此我们对它相当熟悉。另一方面,由于咨询方面的严格许可,CodeQL在我们内部用途中并未获得太多关注。也就是说,我们客户的用例与我们的不同,因此对我们有效的方法可能对他们并不适用。我们决定在此分享我们的结果。
SAST背景
SAST工具通常由几个组件组成:1)用于理解语言的词法分析器/解析器,2)处理词法分析器/解析器输出以查找漏洞的规则,以及3)用于管理规则输出的工具(跟踪/工单、漏洞分类、解释文本、优先级排序、调度、第三方集成等)。
评估SAST工具的困难
规则通常是大多数SAST投诉的源头,因为最终我们都希望工具能产生完美结果,但这并不现实。一方面,你可能得到一个找不到你知道存在于代码中的漏洞的工具(假阴性 - FN),另一方面,它可能返回一堆无用的所谓发现,这些发现要么缺乏任何实际影响,要么可能完全是错误的(假阳性 - FP)。这导致了我们在尝试定量衡量SAST工具好坏时的第一个问题——什么定义了真/假或阳性/阴性?
一些工程师可能会说,真阳性是可证明可利用的条件,而其他人则会说匹配漏洞模式就足够了,无论更广泛的上下文如何。对于设计上包含漏洞代码模式的应用来说,事情更加复杂。例如,通过Web请求中传递的参数执行shell命令的系统管理应用。在大多数环境中,这是最糟糕的情况。然而,对于这些类型的应用,这是它们的主要目的。在这些情况下,工程师们面临主观问题,即是否将发现分类为真阳性或假阳性,其中应用的用户可以在应用中执行任意代码,而他们需要完全和适当的身份验证和授权才能这样做。
这些类型的问题来自于对SAST应用要求过高,我们应该专注于定位漏洞代码模式——留给人来审查和分类发现。这是第三组组件发挥作用的地方,并且可以成为SAST应用之间的真正区别。用户如何忽略相同的发现类、相同代码上的发现、某些路径上的发现,或有条件地忽略事物,对于从噪声中过滤信号变得非常重要。通常,这些工具对愿意花时间正确配置扫描并优化结果的组织更有用,而不是花时间处理一堆他们最初不想看到的问题并变得沮丧。
此外,工具之间的定量比较可能因多种原因而有问题。例如,如果工具A发现许多低严重性错误,但漏掉一个高严重性错误,而工具B只发现一个高严重性错误,但漏掉所有低严重性错误,哪个工具更好?从数字上看,工具A得分更高,但大多数组织宁愿找到更高严重性的漏洞。如果工具A发现一个高严重性漏洞,B发现另一个,但不是A发现的那个,这意味着什么?其中一些问题可以用统计方法处理,但大多数人通常不采用这种方法。此外,当你在多语言环境中,一个工具在一种语言上表现很好,而在其他语言上表现不佳时,问题就会出现。另一个转折可能是,如果一个工具由于解析错误而漏掉一个漏洞,这肯定会在以后的版本中修复,而不是特定的规则匹配问题。
这些类型的担忧不一定有简单的答案,重要的是要记住,任何对SAST工具的评估都会根据正在检查的语言、配置运行的规则、代码库的结构和内容以及对规则或工具配置的任何自定义而有所不同。
正确评估SAST工具的另一个障碍是找到一个代码体来测试它。如果目标是简单地扫描代码并验证发现是真阳性(TP)还是假阳性(FP),几乎任何支持的代码都可以工作,但找到真阴性(TN)和假阴性(FN)需要事先了解代码的安全状态或手动审查代码。
这就提出了如何量化SAST工具可以实际发现的阴性的问题。 broadly,真阳性是源和未受保护的汇的连接,或者可能是一个独立的配置(例如,禁用安全功能)。那么,我们如何具体计算真阴性?我们计算导致受保护汇的源总数、受保护汇的总数、安全函数调用的总数(无论它们是否被识别为汇),以及所有安全配置选项?当然,如果目标仅仅是验证竞争软件之间的检测相对质量,只要所有条件合理相等,简单地比较结果就足够了。
我们的测试方法
我们利用OWASP基准项目分析预先分类的Java应用代码,以提供更准确的Semgrep与CodeQL的正面比较。虽然我们在运行工具时遇到了一些错误,但我们能够绕过它们并产生有效的测试和有意义的结果。
CodeQL和Semgrep都附带了用于演示工具功能的示例代码。我们使用每个工具的测试套件示例漏洞来测试另一个工具,交换测试文件"跨工具"。这样做是基于假设每个工具的测试套件应该为原始工具返回100%准确的结果,但不一定为另一个工具返回。然而,由于测试文件的组织和结构,一些修改和省略是必要的。
我们还以需要最少配置和/或工具知识的方式对客户代码版本运行了工具。这旨在显示每个工具"开箱即用"的效果。我们迭代了工具及其规则的几种配置,直到我们得到了一个有意义的、可管理的结果集(由于时间限制)。
分配重要性
在比较SAST工具时,基于过去的经验,我们认为这些标准是需要检查的重要方面。
- 语言和框架支持
- 词法分析器/解析器功能
- 扫描前/后选项(排除、禁用检查、过滤等)
- 规则
- 发现工作流(内部跟踪、工单系统集成等)
- 扫描时间
语言和框架支持
下面的图像概述了每个工具支持的语言,请参考源链接获取有关支持框架的附加信息:
Semgrep:
源: https://semgrep.dev/docs/supported-languages/
CodeQL:
源: https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/
不出所料,我们看到两种工具都支持许多最流行的语言,但Semgrep支持的语言数量更多,包括GA和开发中的语言。一般来说,这给Semgrep带来了优势,但实际上,大多数组织只关心它是否支持他们需要扫描的语言。
词法分析器/解析器功能
词法分析器/解析器的性能将根据语言和框架、它们的版本和代码复杂性而变化。只有通过扫描大量存储库并监控错误或检查解析器和工具的源代码,才能对此有一个大致的了解。
在各种应用测试期间,两种工具都遇到了错误,允许仅部分解析许多文件。解析结果的彻底性因工具和正在分析的代码而异。测试我们客户的Golang项目时,我们偶尔也会遇到解析错误。
Semgrep: 在测试第三方代码时,我们遇到了一个问题,其中自定义函数(exit())被声明和使用,尽管是保留的,导致解析器在到达函数时因无效语法而失败。这里有两个值得注意的事情:代码理论上不应该正常工作,尽管如此,Semgrep仍然能够进行部分检查。Semgrep在处理不完整代码或有错误代码方面表现出色,因为它通常工作在单个文件范围内。
CodeQL: CodeQL的工作方式略有不同,它有效地从代码创建数据库,允许你然后对该数据库编写查询以定位漏洞。为了做到这一点,它需要一个完全可构建的应用。这本质上意味着它必须更严格地解析所有代码。
在我们的测试中,CodeQL在大多数有发现的文件上生成了错误(最多是部分解析),几乎没有一个文件在没有错误的情况下被分析。大约85%的文件在数据库创建期间生成了一些错误。
根据CodeQL,少量提取错误是正常的,但大量错误不是。不清楚如何减少大量提取错误。根据CodeQL的文档,唯一的方法是等待CodeQL发布修复版本的提取器或使用日志调试。我们尝试用日志调试,但错误消息不完全清楚,似乎两个最常见的错误与文件顶部声明的包名和变量重新声明有关。不完全清楚这些错误是由于提取器过于严格还是被测试的代码不完整。
Semgrep在这里似乎有优势,但由于操作模式不同,这不是一个完全公平的比较。
扫描前/后选项
Semgrep: 在启动Semgrep扫描时,你可以选择的选项包括:
- 选择运行哪些规则或规则组,或允许自动选择
- Semgrep注册表规则(远程社区和r2c创建;目前973条规则)
- 本地自定义规则和/或作为命令行参数传递的"临时"规则
- 上述组合
- 选择检查哪些语言
- 一套强大的过滤选项,以包含/排除文件、目录和特定内容
- 配置比较基线(查找提交X中尚未存在的内容)的能力
- 是否在遇到错误或漏洞时继续
- 是否通过替换文本自动执行修复,以及是否在执行实际更改之前执行试运行
- 要扫描的文件的最大大小
- 输出格式
注意:
- 虽然该工具确实提供了自动扫描选项,但我们发现某些情况下
--config auto
没有手动选择语言找到的所有漏洞。 - 扫描结果的重复使用/跟踪需要使用Semgrep CI或Semgrep App。
CodeQL: CodeQL需要一个可构建的应用(即,不处理有限的文件集),具有完全不同的"扫描"概念,因此这个 notion 不能直接转换。实际上,你从代码创建数据库,随后查询以查找错误,因此许多"过滤"可以通过修改运行的查询来完成。
选项包括:
- 指定处理哪种语言
- CodeQL存储库包含每种语言的库和特定查询
- 安全文件夹包含(21)个各种CWE的查询
- 运行哪些查询
- 文件的位置
- 当查询在结果数据库上运行时,你可以指定输出格式
- 创建数据库时可调整的详细程度
因为CodeQL创建了一个可搜索的数据库,你可以无限期地对扫描的代码版本运行查询。
由于方法不同,很难说一个工具比另一个有优势。最显著的差异可能是Semgrep允许你自动修复漏洞。
规则
如前所述,这些工具采取完全不同的方法(即规则与查询)。是否有人更喜欢编写查询而不是YAML是主观的,因此我们不会具体讨论格式本身。
Semgrep: 作为 primarily 一个字符串匹配静态代码分析工具,Semgrep的准确性主要由使用的规则及其操作模式驱动。Semgrep可能最好被认为是Linux命令行工具grep的改进。它增加了改进的易用性、多行支持、元变量和污点跟踪,以及grep直接不支持的其他功能。Beta功能还包括跨相关文件跟踪的能力。
Semgrep规则在相对简单的YAML文件中定义,只有少数元素用于创建它们。这允许有人在阅读文档和教程后几小时内变得相当熟练。有时,该工具对代码的不完全理解可能导致规则编写比初看起来更困难。
在Semgrep中,有几种执行规则的方法, either locally or remotely。此外,你可以将它们作为命令行参数传递,称为"临时"规则,完全消除YAML文件。
下面的规则显示了一个相当简单的规则示例。它有效地查找HTTP请求中可能为秘密的内容的不安全比较。
|
|
规则中的逻辑很熟悉,感觉像是堆叠RegEx,但增加了围绕匹配内容的边界创建能力,并具有语言理解的好处。然而,重要的是要注意,Semgrep缺乏足够的代码流理解来通过复杂代码跟踪源到汇流。默认情况下,它工作在单个文件基础上,但Beta功能还包括跨相关文件跟踪的能力。Semgrep的当前能力介于基本grep和传统静态代码分析工具之间,具有抽象语法树和控制流图。
扫描开始前不需要特殊准备存储库。该工具完全能够检测语言并在异构代码存储库中同时运行多种语言的扫描。此外,该工具能够运行在不可构建的代码上,但工具在解析它认为无效的语法时会返回错误。
也就是说,规则往往比CodeQL中的查询更通用,并可能导致更多的假阳性。对于某些情况,不可能制作一个完全准确的规则,而不自定义规则以匹配特定的代码库。
CodeQL: CodeQL的查询语言具有类似SQL的语法,具有以下特性:
- 逻辑:QL中的所有操作都是逻辑操作。
- 声明性:提供结果必须满足的属性,而不是计算结果的 procedure。
- 面向对象:它使用类、继承和其他面向对象特性来增加代码的模块化和可重用性。
- 只读:无副作用,无命令式特性(例如变量赋值)。
引擎有每种支持语言的提取器。它们用于从代码库提取信息到数据库中。多语言代码库一次分析一种语言。尝试指定目标语言列表(go、javascript和c)并没有开箱即用,因为CodeQL需要显式设置此语言组合的构建命令。
CodeQL也可以在VSCode中作为扩展、CLI工具或与Github工作流集成使用。VS扩展代码允许编写查询,并得到IDE的自动完成支持,并针对先前创建的一个或多个数据库测试它们。
下面的查询显示了你如何搜索与上述Semgrep规则相同的漏洞。
|
|
为了创建数据库,CodeQL需要一个可构建的代码库。这意味着分析包括多个步骤:代码库的标准构建、创建数据库和查询代码库。由于每个步骤的过程复杂性,我们的经验是,在某些情况下,完整分析可能需要不可忽略的时间。
为CodeQL编写查询也需要大量努力,尤其是在开始时。用户应该非常了解CodeQL语法,并注意条件的结构以避免杀死性能。我们经历了无限编译时间,只是在查询的WHERE子句中添加了一个OR条件。从零经验开始使用该工具,使用CodeQL的好处只有在长期运行中才能感知。
发现工作流
Semgrep: 由于Semgrep允许你输出到多种格式,以及CLI输出,有多种方法可以管理发现。他们还在其管理发现页面上列出了一些这些信息。
CodeQL: 因为CodeQL CLI工具以CSV或SARIF文件格式报告发现,处理它报告的发现可能非常繁琐。在测试期间,我们感觉审查CodeQL CLI工具发现的最简单方法是从Visual Studio Code启动查询,并从那里手动审查结果(由于IDE的导航功能)。最终,在真实世界使用中,结果可能最好通过与GitHub的集成来消费。
扫描时间
由于它们的方法不同,很难公平地量化两种工具之间的速度差异。Semgrep在设置、运行扫描和获取结果所需的时间方面是明显的赢家。它不像CodeQL那样深入解释代码,也不必创建持久可搜索的数据库,然后对其运行查询。然而,一旦数据库创建,你可以认为在CodeQL中查询特定错误与在Semgrep中再次扫描项目大致相似,取决于与工具不直接相关的多个因素(例如,硬件、语言、代码复杂性)。
这突出了工具选择标准应纳入用例的事实。
- 在CI/CD管道中扫描 - 速度更重要
- 持续定期扫描 - 速度较不重要
- 基于时间的咨询工作 - 速度非常重要
OWASP基准项目结果
本节显示了使用这两种SAST工具测试相同Java代码存储库(唯一语言选项)的结果。该项目的示例代码已经过预先审查和分类, specifically to allow for benchmarking of SAST tools。使用这种方法,我们可以相对容易地进行正面比较,并允许OWASP基准项目评分和绘制每个工具的性能图。
这种方法的缺点包括它只是一种语言,Java,并且这不是我们客户选择的语言。此外,SAST工具维护者, who might be aware of this project,理论上可以确保他们的工具在这些测试中表现良好, potentially masking shortcomings when used in broader contexts。
在此测试中,Semgrep配置为运行最新的"security-audit"注册表规则集, per the OWASP Benchmark Project recommendations。CodeQL使用"Security-and-quality queries"查询套件运行。CodeQL查询套件包括来自"security-extended"的查询, plus maintainability and reliability queries。
从下面的图表中可以看出,Semgrep平均表现优于CodeQL。更仔细地检查规则,我们看到三个CWE(常见弱点枚举)领域,CodeQL似乎没有发现任何问题,显著影响平均性能。还应注意,CodeQL在某些类别中确实表现更好,但确定每类别重要性留给工具用户。
跨工具测试套件结果
本节讨论使用Semgrep工具对抗CodeQL测试用例的结果,反之亦然。虽然最初似乎是一种比较工具的好方法,但不幸的是,测试用例文件对这种方法提出了几个挑战。虽然被标记为"Good"和"Bad", either in file names or comments,文件不一定都是"Good"代码或"Bad"代码, but inconsistently mixed, inconsistently labeled and sometimes with multiple potential vulnerabilities in the same files。此外,我们偶尔发现一些文件中的漏洞不是应该在该文件中的CWE类(例如,在SQL注入测试用例中发现XSS)。
这些问题阻止了基于发现/未发现漏洞的文件的简单计数。我们