比较Semgrep和CodeQL
引言
近期,客户要求我们对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)需要事先了解代码的安全状态或手动审查代码。
这 then raises the question of how to quantify the negatives that a SAST tool can realistically discover. Broadly, a true positive is either a connection of a source and unprotected sink or possibly a stand-alone configuration (e.g., disabling a security feature). So how do we count true negatives specifically? Do we count the total number or sources that lead to a protected sink, the total of protected sinks, the total safe function calls (regardless if they are identified sinks), and all the safe configuration options? Of course, if the objective is solely to verify the relative quality of detection between competing software, simply comparing the results, provided all things were reasonably equal, can be sufficient.
我们的测试方法
我们利用OWASP基准项目分析预分类的Java应用代码,以提供更准确的Semgrep与CodeQL正面对比。虽然在运行工具时遇到了一些错误,但我们能够绕过它们并产生有效的测试和有意义的結果。
CodeQL和Semgrep都附带了用于演示工具功能的示例代码。我们使用每个工具的样本漏洞测试套件来测试另一个工具,交换测试文件"跨工具"。这样做是基于每个工具的测试套件应该为原始工具返回100%准确结果的假设,但不一定为另一个工具。然而,由于测试文件的组织和结构,一些修改和省略是必要的。
我们还以需要最少配置和/或工具知识的方式运行工具扫描客户代码。这是为了展示每个工具"开箱即用"的效果。我们迭代了工具及其规则的几种配置,直到得到有意义且可管理的结果集(由于时间限制)。
分配重要性
在比较SAST工具时,基于过去经验,我们认为这些标准是需要检查的重要方面。
- 语言和框架支持
- 词法分析器/解析器功能
- 扫描前/后选项(排除、禁用检查、过滤等)
- 规则
- 发现工作流(内部跟踪、工单系统集成等)
- 扫描时间
语言和框架支持
下图概述了每个工具支持的语言,请参考源链接获取有关支持框架的更多信息:
Semgrep:
CodeQL:
不出所料,我们看到两种工具都支持许多最流行的语言,但Semgrep支持的语言数量更多,包括正式发布和开发中的语言。一般来说,这给Semgrep带来了优势,但实际上,大多数组织只关心它是否支持他们需要扫描的语言。
词法分析器/解析器功能
词法分析器/解析器的性能会因语言和框架、它们的版本和代码复杂性而异。只有通过扫描大量存储库并监控错误或检查解析器和工具的源代码,才能对此有一个大致的了解。
在各种应用测试中,两种工具都遇到了错误,只允许部分解析许多文件。解析结果的彻底性因工具和被分析代码而异。测试客户的Golang项目时,我们偶尔也遇到两种工具的解析错误。
Semgrep: 在测试第三方代码时,我们遇到了一个问题:尽管是保留的,但声明并使用了一个自定义函数(exit()),导致解析器在到达该函数时因无效语法而失败。这里有两个值得注意的事情:代码理论上不应该正常工作,尽管如此,Semgrep仍然能够进行部分检查。Semgrep在处理不完整代码或有错误代码方面表现出色,因为它通常在单个文件范围内操作。
CodeQL: CodeQL的工作方式略有不同,它有效地从代码创建数据库,允许您然后对该数据库编写查询以定位漏洞。为了做到这一点,它需要一个完全可构建的应用。这 inherently means that it must be more strict with its ability to parse all the code.
在我们的测试中,CodeQL对其有发现的大多数文件生成了错误(最多部分解析),几乎没有一个文件分析时没有错误。大约85%的文件在数据库创建期间生成了一些错误。
根据CodeQL的说法,少量提取错误是正常的,但大量错误则不是。不清楚如何减少大量提取错误。根据CodeQL的文档,唯一的方法是等待CodeQL发布修复版本的提取器或使用日志调试。我们尝试用日志调试,但错误消息并不完全清楚,似乎两个最常见的错误与文件顶部声明的包名和变量重新声明有关。不清楚这些错误是由于提取器过于严格还是被测试代码不完整。
Semgrep在这里似乎有优势,但由于操作模式不同,这不是完全公平的比较。
扫描前/后选项
Semgrep: 启动Semgrep扫描时可以选择以下选项:
- 选择运行哪些规则或规则组,或允许自动选择
- Semgrep注册表规则(远程社区和r2c创建;目前973条规则)
- 本地自定义规则和/或作为命令行参数传递的"临时"规则
- 以上组合
- 选择检查哪些语言
- 强大的过滤选项以包含/排除文件、目录和特定内容
- 配置比较基线(查找提交X中尚未存在的内容)的能力
- 是否在遇到错误或漏洞时继续
- 是否通过替换文本自动执行修复,以及是否在执行实际更改前执行试运行检查
- 要扫描的文件的最大大小
- 输出格式
注意:
- 虽然工具提供了自动扫描选项,但我们发现某些情况下
--config auto
没有找到手动选择语言时找到的所有漏洞。 - 扫描结果的重复使用/跟踪需要使用Semgrep CI或Semgrep App。
CodeQL: CodeQL需要一个可构建的应用(即不处理有限文件集),具有完全不同的"扫描"概念,因此这个概念不能直接转换。实际上,您从代码创建数据库,随后查询以查找错误,因此许多"过滤"可以通过修改运行的查询来完成。
选项包括:
- 指定处理哪种语言
- CodeQL存储库包含每种语言的库和特定查询
- Security文件夹包含(21)个各种CWE的查询
- 运行哪些查询
- 文件位置
- 当查询在结果数据库上运行时,可以指定输出格式
- 创建数据库时可调整详细程度
因为CodeQL创建了一个可搜索的数据库,您可以无限期地对扫描的代码版本运行查询。
由于方法不同,很难说一个工具比另一个有优势。最显著的差异可能是Semgrep允许您自动修复漏洞。
规则
如前所述,这些工具采取完全不同的方法(即规则与查询)。有人更喜欢编写查询还是YAML是主观的,因此我们不会具体讨论格式本身。
Semgrep: 作为 primarily a string-matching static code analysis tool, Semgrep的准确性主要取决于使用的规则及其操作模式。Semgrep可能最好被认为是Linux命令行工具grep的改进。它增加了改进的易用性、多行支持、元变量和污点跟踪,以及grep直接不支持的其他功能。Beta功能还包括跨相关文件跟踪的能力。
Semgrep规则在相对简单的YAML文件中定义,只有少数元素用于创建它们。这允许某人在阅读文档和教程后几小时内变得相当熟练。有时,工具对代码的不完全理解可能导致规则编写比初看起来更困难。
在Semgrep中,有几种执行规则的方法,可以在本地或远程执行。此外,您可以将它们作为命令行参数传递,称为"临时"规则,完全消除YAML文件。
以下规则显示了一个相当简单的规则示例。它有效地查找HTTP请求中可能为秘密的内容的不安全比较。
|
|
规则中的逻辑很熟悉,感觉像是堆叠RegEx,但增加了围绕匹配内容创建边界的能力,并具有语言理解的好处。然而,重要的是要注意,Semgrep缺乏对代码流的充分理解,无法通过复杂代码跟踪源到汇的流。默认情况下,它在单个文件基础上工作,但Beta功能还包括跨相关文件跟踪的能力。Semgrep的当前能力介于基本grep和传统静态代码分析工具之间,具有抽象语法树和控制流图。
扫描开始前不需要对存储库进行特殊准备。该工具完全能够检测语言并在异构代码存储库中同时运行多种语言的扫描。此外,该工具能够运行在不可构建的代码上,但当它解析它认为无效的语法时会返回错误。
也就是说,规则往往比CodeQL中的查询更通用,可能导致更多的假阳性。对于某些情况,不可能制作一个完全准确的规则,而不自定义规则以匹配特定的代码库。
CodeQL: CodeQL的查询语言具有类似SQL的语法,具有以下特点:
- 逻辑:QL中的所有操作都是逻辑操作。
- 声明性:提供结果必须满足的属性,而不是计算结果的程序。
- 面向对象:它使用类、继承和其他面向对象功能来增加代码的模块化和可重用性。
- 只读:无副作用,无命令式功能(例如变量赋值)。
引擎为每种支持的语言提供提取器。它们用于将代码库中的信息提取到数据库中。多语言代码库一次分析一种语言。尝试指定目标语言列表(go、javascript和c)并没有开箱即用,因为CodeQL需要显式设置此语言组合的构建命令。
CodeQL也可以在VSCode中作为扩展、CLI工具或与GitHub工作流集成使用。VS扩展代码允许在IDE的自动完成支持下编写查询,并针对先前创建的一个或多个数据库进行测试。
以下查询显示了如何搜索与上述Semgrep规则相同的漏洞。
|
|
为了创建数据库,CodeQL需要一个可构建的代码库。这意味着分析包括多个步骤:代码库的标准构建、创建数据库和查询代码库。由于每个步骤的过程复杂性,我们的经验是,在某些情况下,完整分析可能需要不可忽略的时间。
为CodeQL编写查询也需要大量努力,尤其是在开始时。用户应该非常了解CodeQL语法,并注意条件的结构以避免影响性能。我们 experienced an infinite compilation time just adding an OR condition in the WHERE clause of a query. 从零经验开始使用该工具,使用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工具维护者可能知道这个项目,理论上可以确保他们的工具在这些测试中表现良好, potentially masking shortcomings when used in broader contexts.
在此测试中,根据OWASP基准项目建议,Semgrep配置为运行最新的"security-audit"注册表规则集。CodeQL使用"Security-and-quality queries"查询套件运行。CodeQL查询套件包括来自"security-extended"的查询,以及可维护性和可靠性查询。
从下图可以看出,Semgrep平均表现优于CodeQL。更仔细地检查规则,我们看到三个CWE(常见弱点枚举)领域,CodeQL似乎没有发现任何问题,显著影响了平均性能。还应注意,CodeQL在某些类别中表现更好,但确定每个类别的重要性留给工具用户。
![OWASP基准测试结果图表]
跨工具测试套件结果
本节讨论使用Semgrep工具针对CodeQL测试用例的结果,反之亦然。虽然最初似乎是一种比较工具的好方法,但不幸的是,测试用例文件给这种方法带来了几个挑战。虽然在文件名或注释中标记为"Good"和"Bad",但文件不一定都是"Good"代码或"Bad"代码,而是不一致地混合、不一致地标记,有时在同一文件中存在多个潜在漏洞。此外,我们偶尔发现一些文件中的漏洞不是应该在该文件中的CWE类(例如,在SQL注入测试用例中发现XSS)。
这些问题阻止了基于发现/未发现漏洞的文件的简单计数。我们呈现的统计数据在分配时间内尽可能修改以解释这些问题,并应用了数据分析技术来解释一些错误。
如下表所示,CodeQL在检测方面表现明显更好,但代价是更高的假阳性率。这强调了引言中提到的一些潜在权衡,需要由输出消费者考虑。
指标 | Semgrep | CodeQL |
---|---|---|
真阳性率 | 较低 | 较高 |
假阳性率 | 较低 | 较高 |
注意:
- Semgrep的配置仅限于运行分类为安全