现代 Java 应用程序是建立在无数开源库之上的。这些库封装了常见的重复代码,使应用程序员能够专注于提供客户价值。但使用这些库也有代价——安全漏洞。流行库中的安全问题可以让恶意者以低廉的代价攻击广泛的目标。
因此,在持续集成流水线中进行依赖漏洞检查(即软件组合分析或SCA)至关重要。不幸的是,安全世界并不是非黑即白;一个漏洞在某个应用程序中可能是无害的,在另一个应用程序中却是一个关键问题,因此扫描始终需要人工监督,以确定报告是否误报。
本文将探讨在过去几年中标准 Spring Boot 项目中常见的漏洞示例。本文是从软件工程师的角度编写的。重点将转向在使用广泛可用的工具,如 OWASP 依赖项检查 时工程师所面临的挑战。
由于软件工程师致力于提供产品价值,他们会将安全视为他们的诸多责任之一。尽管安全很重要,但由于其他任务的复杂性,安全反而有时会妨碍工作导致被忽视。
解决漏洞的生命周期
典型的漏洞生命周期如下。
发现
安全研究员通常会发现漏洞。然后漏洞会被报告给受影响的开源软件项目,并通过一系列非营利组织最终进入 NIST 国家漏洞数据库(NVD)。例如,Spring4Shell 漏洞就是通过这种方式记录的。
检测
当报告一个漏洞时,需要检测应用程序是否包含有漏洞的依赖项。幸运的是,我们有大量的工具可用于协助检测。
其中一个流行的解决方案是使用 OWASP 依赖项检查 - 它可以作为 Gradle 或 Maven 插件使用。当执行时,它会将你应用程序的所有依赖项与 NIST NVD 数据库和 Sonatype OSS index 进行比较。它允许你阻止警告并生成报告,并且很容易集成到持续集成流水线中。主要缺点是它有时会误报,因为 NIST NVD 数据库没有用理想的格式提供数据。此外,第一次运行需要很长时间,因为它会下载整个漏洞数据库。
我们还有各种免费和商业的工具可以使用,例如 GitHub Dependabot、Checkmarx 和 Snyk。一般来说,这些工具的功能类似,扫描所有依赖项并将其与已知漏洞的数据库进行比较。商业提供商通常投资维护更准确的数据库。因此,商业工具可能会提供更少的误报甚至负面的结果。
分析
在检测到漏洞后,开发者必须进行分析以确定其影响。正如下面的示例所示,这通常是最具挑战性的部分。进行分析的人必须理解漏洞报告、应用程序代码和部署环境,以确定漏洞是否可以被充分利用。通常,这项任务由应用程序员来完成,因为他们是唯一具备所有必要上下文的人。
解决方案
必须解决这个漏洞。
理想情况下,可以通过升级有漏洞的依赖项到修复版本来解决问题。
如果尚未发布修复程序,应用开发程序员可以采用某种临时办法,例如更改配置、过滤输入等。
很多情况下,漏洞报告都是误报。通常情况下,在给定的环境中无法利用该漏洞。在这种情况下,必须阻止这样的报告,以防止我们习惯性地产生失败的漏洞报告。
一旦分析完成,解决方案通常是直截了当的,但如果有数十个服务需要修补,就会耗费相当的时间。因此,尽可能简化解决方案的过程就非常重要。由于这通常是繁琐的手动工作,建议尽可能自动化。像 Dependabot 或 Renovate 这样的工具在这方面可以一定程度上提供帮助。
漏洞示例
让我们来看一些漏洞示例以及在解决它们时可能遇到的问题。
Spring4Shell(CVE-2022-22965,评分 9.8)
让我们从一个严重的漏洞开始 - Spring Framework RCE 通过 JDK 9+ 上的数据绑定,也称为 Spring4Shell,它允许攻击者通过调用 HTTP 端点远程执行代码。
检测
这个漏洞很容易被检测到。Spring 是一个相当知名的框架;这个漏洞存在于大多数版本中,并且在互联网上讨论得很多。自然而然,所有的检测工具都能检测到它。
分析
在漏洞的 早期公告 中,它指出只有使用 Spring WebMvc/Webflux 部署为 Servlet 容器中的 WAR 的应用程序才会受到影响。理论上,使用嵌入式 Servlet 容器进行部署应该是安全的。不幸的是,公告缺乏漏洞的详细信息,这导致很难确认事实是否的确如此。然而,这个漏洞非常严重,应该尽快采取措施予以缓解。
解决方案
修复方法是在几个小时内发布的,所以最好的方法是等待修复和升级。像 Dependabot 或 Renovate 这样的工具可以帮助你在所有服务中完成此操作。
如果有意愿更早解决漏洞,可以使用这个临时方法。但这意味着应用一个不清楚其作用的晦涩的配置。手动在所有服务上应用它,或者只是等待修复,这样的决定意味着很有挑战性。
HttpInvoker RCE(CVE-2016-1000027,评分 9.8)
让我们继续关注一下 Spring。这个 漏洞 与 Spring4Shell 9.8 具有相同的严重性。但是人们可能会注意到这是 2016 年的事情,并想知道为什么它还没有被修复,或者为什么它没有一个花哨的名字。原因在于,它位于 HttpInvoker 组件中,该组件用于 RPC 通信样式。它在 2000 年左右很受欢迎,但现在已经很少使用了。更令人困惑的是,这个漏洞在 2020 年出于一些管理上的原因才被公布,这已经是在最初报告的四年后的事情了。
检测
这个问题是由 OWASP 依赖检查和其他工具报告的。由于它并没有影响很多人,所以没有成为头条新闻。
分析
NIST CVE 详细信息并未透露太多信息:
如果用于 Java 反序列化未经信任的数据,Pivotal Spring Framework 5.3.16 存在潜在的远程代码执行(RCE)问题。根据库在产品中的实现方式,此问题可能会也可能不会发生,并且可能需要进行身份验证。
这听起来非常严重,需要立即关注并通过链接搜索更多详细信息。然而,这个问题只在使用HttpInvokerServiceExporter
时才会出现,所以实际上只是一个误报。
解决方案
由于 Pivotal 认为这并不是一个错误,因此没有发布修复版本的库。它是过时代码的一个特性,本应仅用于内部通信。几年后,整个功能在 Spring 6 中被完全删除。
唯一需要采取的行动是阻止该警告。可以使用免费的 OWASP 依赖检查工具,但如果需要为每个服务手动执行此操作,这个过程可能非常耗时。
有几种简化流程的方法。其中一种方法是通过指定 URL,在所有项目中公开使用一个共享的抑制文件。最后,你可以使用像 Dependency Shield 这样的简单服务来简化整个抑制流程。重要的是,需要一个流程来简化抑制操作,因为大多数报告很可能是误报。
SnakeYAML RCE(CVE-2022-1471,评分 9.8)
又出现了一个重大漏洞,这次是在 SnakeYAML 解析库中。再次涉及远程代码执行,评分为 9.8。但是,它只适用于使用 SnakeYAML 的Constructor
类来解析攻击者提供的 YAML 的情况。
检测
这是通过漏洞扫描工具检测出来的。SnakeYAML 被 Spring 用于解析 YAML 配置文件,所以它非常常见。
分析
应用程序是否正在解析可能由攻击者提供的 YAML,例如在 REST API 上?是否使用了不安全的Constructor
类?如果是这样,你的系统恐怕就存在漏洞。如果仅用于解析 Spring 配置文件,系统就是安全的。必须由了解代码及其用法的人来做出决策。情况可能很关键,需要立即关注和纠正,也可能是安全的,因此可以忽略它。
解决方案
问题很快得到解决。让问题变得棘手的是 SnakeYAML 不是直接的依赖项;它是通过 Spring 间接引入的,这使得升级变得异常困难。如果你想升级 SnakeYAML,可以采用以下几种方法。
如果使用了 Spring Boot 的依赖管理插件和 Spring Boot BOM,a. 可以覆盖
snakeyaml.version
变量。b. 可以覆盖依赖管理声明。如果没有使用依赖管理,必须添加 SnakeYAML 为项目的直接依赖项,并覆盖版本。
当与复杂的多项目构建结合使用时,工具几乎无法自动升级版本。Dependabot 和 Renovate 都无法做到这一点。即使是像 Snyk 这样的商业工具也无法成功升级,因为"无法升级,依赖项是在外部进行管理的"。
当然,一旦版本被覆盖,重要的是记得在 Spring 中更新版本后,删除覆盖。在我们的情况中,最好在新版本使用在 Spring 之前,暂时抑制警告。
误判的 Avro 漏洞
漏洞 CVE-2021-43045 是 Avro 库的.NET 版本中的一个错误,因此不太可能影响 Java 项目。那么,为什么会被报告呢?不幸的是,NIST报告包含cpe:2.3:a:apache:avro:*:*:*:*:*:*:*:*
标识符。难怪工具错误地将org.apache.avro/avro@1.9.0
识别为漏洞,尽管它来自完全不同的生态系统。
解决方案: 抑制
总结
让我们回顾一下解决漏洞的不同阶段,以及如何尽可能简化它,这样报告才不会阻碍工程师过长时间。
检测
检测的最重要部分是避免习惯失败的依赖检查。理想情况下,如果检测到存在漏洞依赖项,构建应该失败。为了实现这一点,解决方案需要尽可能简单快速。没有人希望由于误报而遇到破坏的流水线。
由于 OWASP 依赖性检查主要使用 NIST NVD 数据库,它有时会出现误报的情况。然而,正如我们所观察到的,误报是不可避免的,因为分析并不总是直接明了的。
分析
这是一个困难的部分,实际上,在这个部分,工具无法提供太多帮助。以 SnakeYAML 远程代码执行漏洞为例。要使其有效,必须非安全地使用库,例如解析来自攻击者的数据。遗憾的是,没有工具能够可靠地检测出一个应用程序及其所有库中是否包含有漏洞的代码。因此,这部分总是需要一些人工的干预。
解决方案
对于直接依赖项,将库升级到修复版本相对要简单。像 Dependabot 和 Renovate 这样的工具可以帮助完成这个过程。然而,如果有漏洞的依赖项是通过传递依赖或依赖管理引入的,这些工具就会失败。手动覆盖依赖项可能是单个项目可接受的解决方案。对于维护多个服务的情况,我们应该引入集中管理的依赖管理,来简化这个过程。
大多数报告都是误报,因此有一种简单的方法来抑制警告非常重要。在使用 OWASP 依赖检查时,可以尝试使用共享的抑制文件,或者像 Dependency Shield 这样的工具来帮助完成这个任务。
通常情况下,暂时抑制报告是有意义的。可以暂时解除流水线阻塞,直到有人有时间仔细分析报告,或者直到引入该依赖项的项目更新了传递依赖项。
原文链接:
https://www.infoq.com/articles/dealing-with-java-cves/
相关阅读:
Java 近期新闻:单查询加载、GraalVM、GlassFish、JReleaser、Quarkus、Micronaut
评论