3

如何检查 Docker 镜像是否存在漏洞

 1 year ago
source link: https://www.techug.com/post/how-to-check-for-vulnerabilities-in-docker-imagesf5e9b37c994754816345/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何检查 Docker 镜像是否存在漏洞



how-to-check-for-vulnerabilities-in-docker-imagesf5e9b37c994754816345.01.jpeg

定期检查管道中的漏洞是非常重要的。执行步骤之一是对你的 Docker 镜像进行漏洞扫描。在本文中,你将学习如何执行漏洞扫描,如何修复漏洞,以及如何将其添加到你的 Jenkins 管道中。

在几年前的一篇博文中,描述了如何扫描 Docker 镜像的漏洞。后续的博文展示了如何将扫描添加到 Jenkins 管道中。然而,之前博文中使用的 Anchore Engine 已经不被支持了,我认为另一个解决方案是使用由 Anchore 提供的 grype

如今,我们必须保持最新的安全修复措施。许多安全漏洞是公开的,可以很容易地被利用。因此,为尽量减少被攻击,尽快修复安全漏洞是必须的。但如何跟上这个步伐呢?你主要关注的是业务,不希望有一个全职工作来修复安全漏洞。这就是为什么自动扫描你的应用程序和你的 Docker 镜像很重要。Grype 可以帮助扫描 Docker 镜像、检查操作系统的漏洞,也会检查特定语言的包,如 Java JAR 文件的漏洞,并会报告它们。它还可以扫描文件和目录,因此可以用来扫描你的源代码。

在本文中,我创建了一个包含 Spring Boot 应用程序的有漏洞的 Docker 镜像,并将安装和使用 grype,以便扫描镜像并修复漏洞。本文中使用的资源可以在 GitHub 上找到。

了解本文,所需的前提条件是:

  • 基本的 Linux 知识

  • 基本的 Docker 知识

  • 基本的 Java 和 Spring Boot 知识

易受攻击的应用程序

打开 Spring Initializr,选择 Maven 构建、Java 17、Spring Boot 2.7.6,以及 Spring Web 依赖。这不会是一个非常脆弱的应用程序,因为 Spring 已经确保你使用最新的 Spring Boot 版本。因此,将 Spring Boot 的版本改为 2.7.0。可以用以下命令构建 Spring Boot 应用程序,它将为你创建 jar 文件:

$ mvn clean verify

你要扫描一个 Docker 镜像,因此需要创建一个 Dockerfile。你将使用一个非常基本的 Dockerfile,它只包含创建镜像所需的最低指令。如果你想创建生产就绪的 Docker 镜像,请阅读《Docker 最佳实践》(Docker Best Practices)和《Spring Boot Docker 最佳实践》(Spring Boot Docker Best Practices)。

FROM eclipse-temurin:17.0.1_12-jre-alpine
WORKDIR /opt/app
ARG JAR_FILE
COPY target/${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

在撰写本文时,Java 17 的最新 eclipse-temurin 基础镜像是 17.0.5_8 版本。同样,使用较旧的才能使其易受攻击。

为了构建 Docker 镜像,将使用 Spotify 的 dockerfile-maven-plugin 的分支。因此,将下面的代码片段添加到 pom 文件中。

<plugin>
  <groupId>com.xenoamess.docker</groupId>
  <artifactId>dockerfile-maven-plugin</artifactId>
  <version>1.4.25</version>
  <configuration>
    <repository>mydeveloperplanet/mygrypeplanet</repository>
    <tag>${project.version}</tag>
    <buildArgs>
      <JAR_FILE>${project.build.finalName}.jar</JAR_FILE>
    </buildArgs>
  </configuration>
</plugin>

使用该插件的好处是,你可以轻松地重复使用配置。创建 Docker 镜像只需一条 Maven 命令即可完成。

可以通过调用以下命令来构建 Docker 镜像:

mvn dockerfile:build

现在,你已经准备好开始使用 grype 了。

可以通过执行以下脚本来安装 grype:

$ curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

通过执行以下命令验证安装:

$ grype version
Application:          grype
Version:              0.54.0
Syft Version:         v0.63.0
BuildDate:            2022-12-13T15:02:51Z
GitCommit:            93499eec7e3ce2704755e9f51457181b06b519c5
GitDescription:       v0.54.0
Platform:             linux/amd64
GoVersion:            go1.18.8
Compiler:             gc
Supported DB Schema:  5

扫描 Docker 镜像的方法是调用 grype,然后是 docker:,表示你想从 Docker 守护程序、镜像和标签中扫描一个镜像。

$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT
Application:          grype
Version:              0.54.0
Syft Version:         v0.63.0
 Vulnerability DB        [updated]
 Loaded image            
 Parsed image            
 Cataloged packages      [50 packages]
 Scanned image           [42 vulnerabilities]
NAME              INSTALLED  FIXED-IN   TYPE          VULNERABILITY        SEVERITY 
busybox           1.34.1-r3  1.34.1-r5  apk           CVE-2022-28391       High      
jackson-databind  2.13.3                java-archive  CVE-2022-42003       High      
jackson-databind  2.13.3                java-archive  CVE-2022-42004       High      
jackson-databind  2.13.3     2.13.4     java-archive  GHSA-rgv9-q543-rqg4  High      
jackson-databind  2.13.3     2.13.4.1   java-archive  GHSA-jjjh-jjxp-wpff  High      
java              17.0.1+12             binary        CVE-2022-21248       Low       
java              17.0.1+12             binary        CVE-2022-21277       Medium    
java              17.0.1+12             binary        CVE-2022-21282       Medium    
java              17.0.1+12             binary        CVE-2022-21283       Medium    
java              17.0.1+12             binary        CVE-2022-21291       Medium    
java              17.0.1+12             binary        CVE-2022-21293       Medium    
java              17.0.1+12             binary        CVE-2022-21294       Medium    
java              17.0.1+12             binary        CVE-2022-21296       Medium    
java              17.0.1+12             binary        CVE-2022-21299       Medium    
java              17.0.1+12             binary        CVE-2022-21305       Medium    
java              17.0.1+12             binary        CVE-2022-21340       Medium    
java              17.0.1+12             binary        CVE-2022-21341       Medium    
java              17.0.1+12             binary        CVE-2022-21360       Medium    
java              17.0.1+12             binary        CVE-2022-21365       Medium    
java              17.0.1+12             binary        CVE-2022-21366       Medium    
libcrypto1.1      1.1.1l-r7             apk           CVE-2021-4160        Medium    
libcrypto1.1      1.1.1l-r7  1.1.1n-r0  apk           CVE-2022-0778        High      
libcrypto1.1      1.1.1l-r7  1.1.1q-r0  apk           CVE-2022-2097        Medium    
libretls          3.3.4-r2   3.3.4-r3   apk           CVE-2022-0778        High      
libssl1.1         1.1.1l-r7             apk           CVE-2021-4160        Medium    
libssl1.1         1.1.1l-r7  1.1.1n-r0  apk           CVE-2022-0778        High      
libssl1.1         1.1.1l-r7  1.1.1q-r0  apk           CVE-2022-2097        Medium    
snakeyaml         1.30                  java-archive  GHSA-mjmj-j48q-9wg2  High      
snakeyaml         1.30       1.31       java-archive  GHSA-3mc7-4q67-w48m  High      
snakeyaml         1.30       1.31       java-archive  GHSA-98wm-3w3q-mw94  Medium    
snakeyaml         1.30       1.31       java-archive  GHSA-c4r9-r8fh-9vj2  Medium    
snakeyaml         1.30       1.31       java-archive  GHSA-hhhw-99gj-p3c3  Medium    
snakeyaml         1.30       1.32       java-archive  GHSA-9w3m-gqgf-c4p9  Medium    
snakeyaml         1.30       1.32       java-archive  GHSA-w37g-rhq8-7m4j  Medium    
spring-core       5.3.20                java-archive  CVE-2016-1000027     Critical  
ssl_client        1.34.1-r3  1.34.1-r5  apk           CVE-2022-28391       High      
zlib              1.2.11-r3  1.2.12-r0  apk           CVE-2018-25032       High      
zlib              1.2.11-r3  1.2.12-r2  apk           CVE-2022-37434       Critical 

这个输出告诉你什么?

  • NAME:易受攻击的包的名称

  • INSTALLED:安装的是哪个版本

  • FIXED-IN:在哪个版本中修复了漏洞

  • TYPE:依赖项的类型,例如 JDK 的二进制等

  • VULNERABILITY:漏洞的标识符;通过此标识符,你可以获得有关 CVE 数据库中漏洞的更多信息

  • SEVERITY:不言自明,可以是可忽略、低、中、高或严重

当你仔细观察输出结果时,你会发现并非每个漏洞都有确认的修复方法。那么,在这种情况下,你该怎么办呢?Grype 提供了一个选项,以便只显示已经确认修复的漏洞。添加--only-fixed 标志就可以了。

$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [50 packages]
 Scanned image           [42 vulnerabilities]
 
NAME              INSTALLED  FIXED-IN   TYPE          VULNERABILITY        SEVERITY 
busybox           1.34.1-r3  1.34.1-r5  apk           CVE-2022-28391       High      
jackson-databind  2.13.3     2.13.4     java-archive  GHSA-rgv9-q543-rqg4  High      
jackson-databind  2.13.3     2.13.4.1   java-archive  GHSA-jjjh-jjxp-wpff  High      
libcrypto1.1      1.1.1l-r7  1.1.1n-r0  apk           CVE-2022-0778        High      
libcrypto1.1      1.1.1l-r7  1.1.1q-r0  apk           CVE-2022-2097        Medium    
libretls          3.3.4-r2   3.3.4-r3   apk           CVE-2022-0778        High      
libssl1.1         1.1.1l-r7  1.1.1n-r0  apk           CVE-2022-0778        High      
libssl1.1         1.1.1l-r7  1.1.1q-r0  apk           CVE-2022-2097        Medium    
snakeyaml         1.30       1.31       java-archive  GHSA-3mc7-4q67-w48m  High      
snakeyaml         1.30       1.31       java-archive  GHSA-98wm-3w3q-mw94  Medium    
snakeyaml         1.30       1.31       java-archive  GHSA-c4r9-r8fh-9vj2  Medium    
snakeyaml         1.30       1.31       java-archive  GHSA-hhhw-99gj-p3c3  Medium    
snakeyaml         1.30       1.32       java-archive  GHSA-9w3m-gqgf-c4p9  Medium    
snakeyaml         1.30       1.32       java-archive  GHSA-w37g-rhq8-7m4j  Medium    
ssl_client        1.34.1-r3  1.34.1-r5  apk           CVE-2022-28391       High      
zlib              1.2.11-r3  1.2.12-r0  apk           CVE-2018-25032       High      
zlib              1.2.11-r3  1.2.12-r2  apk           CVE-2022-37434       Critical 

请注意,Java JDK 的漏洞已经消失,尽管 Java 17 JDK 存在一个较新的更新。然而,这可能不是一个大问题,因为其他(非 java-archive)的漏洞显示基础镜像已经过时了。

在这种情况下,修复漏洞是很容易的。首先,你需要更新 Docker 基础镜像。改变 Docker 镜像中的第一行:

FROM eclipse-temurin:17.0.1_12-jre-alpine
FROM eclipse-temurin:17.0.5_8-jre-alpine

构建镜像并再次运行扫描:

$ mvn dockerfile:build
...
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [62 packages]
 Scanned image           [14 vulnerabilities]
NAME              INSTALLED  FIXED-IN  TYPE          VULNERABILITY        SEVERITY 
jackson-databind  2.13.3     2.13.4    java-archive  GHSA-rgv9-q543-rqg4  High      
jackson-databind  2.13.3     2.13.4.1  java-archive  GHSA-jjjh-jjxp-wpff  High      
snakeyaml         1.30       1.31      java-archive  GHSA-3mc7-4q67-w48m  High      
snakeyaml         1.30       1.31      java-archive  GHSA-98wm-3w3q-mw94  Medium    
snakeyaml         1.30       1.31      java-archive  GHSA-c4r9-r8fh-9vj2  Medium    
snakeyaml         1.30       1.31      java-archive  GHSA-hhhw-99gj-p3c3  Medium    
snakeyaml         1.30       1.32      java-archive  GHSA-9w3m-gqgf-c4p9  Medium    
snakeyaml         1.30       1.32      java-archive  GHSA-w37g-rhq8-7m4j  Medium 

正如你在输出中所看到的,只有 java-archive 的漏洞仍然存在。其他漏洞已经被解决了。

接下来,修复 Spring Boot 依赖性漏洞,在 POM 中将 Spring Boot 的版本从 2.7.0 更改为 2.7.6。

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.7.6</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

构建 JAR 文件,构建 Docker 镜像,然后再次运行扫描:

$ mvn clean verify
...
$ mvn dockerfile:build
...
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [62 packages]
 Scanned image           [10 vulnerabilities]
NAME       INSTALLED  FIXED-IN  TYPE          VULNERABILITY        SEVERITY 
snakeyaml  1.30       1.31      java-archive  GHSA-3mc7-4q67-w48m  High      
snakeyaml  1.30       1.31      java-archive  GHSA-98wm-3w3q-mw94  Medium    
snakeyaml  1.30       1.31      java-archive  GHSA-c4r9-r8fh-9vj2  Medium    
snakeyaml  1.30       1.31      java-archive  GHSA-hhhw-99gj-p3c3  Medium    
snakeyaml  1.30       1.32      java-archive  GHSA-9w3m-gqgf-c4p9  Medium    
snakeyaml  1.30       1.32      java-archive  GHSA-w37g-rhq8-7m4j  Medium 

所以,你修复了 jackson-databind 的漏洞,但没有修复 snakeyaml 的漏洞。那么,snakeyaml 1.30 是在哪个依赖中被使用的?你可以通过 Maven 的 dependency:tree 命令来了解。为简洁起见,这里只显示部分输出:

$ mvnd dependency:tree
...
 com.mydeveloperplanet:mygrypeplanet:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.6:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.7.6:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:2.7.6:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.6:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.6:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.11:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.11:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.17.2:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.36:compile
[INFO] |  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.30:compile
...

输出显示,这个依赖是 spring-boot-starter-web 依赖的一部分。那么,你如何解决这个问题呢?严格来说,Spring 必须要解决这个问题。但如果你不想等待解决方案,你可以自己解决。

解决方案 1: 不需要依赖项。这是最简单的解决方案,风险也低。只需在 pom 中从 spring-boot-starter-web 依赖项中排除该依赖项即可。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
    </exclusion>
  </exclusions>
</dependency>

构建 JAR 文件,构建 Docker 镜像,然后再次运行扫描:

$ mvn clean verify
...
$ mvn dockerfile:build
...
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [61 packages]
 Scanned image           [3 vulnerabilities]
No vulnerabilities found

再也没发现任何漏洞了。

解决方案 2:你确实需要这个依赖关系。你可以通过 pom 中的 dependencyManagement 来替换这个过渡性依赖。这就有点麻烦了,因为更新后的横向依赖没有经过 spring-boot-starter-web 依赖的测试。你要不要这样做,需要权衡一下。在 pom 中添加以下部分。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
      <version>1.32</version>
    </dependency>
  </dependencies>
</dependencyManagement>

构建 JAR 文件,构建 Docker 镜像,然后再次运行扫描:

$ mvn clean verify
...
$ mvn dockerfile:build
...
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [62 packages]
 Scanned image           [3 vulnerabilities]
No vulnerabilities found

同样,再也没发现漏洞了。

解决方案 3:这是在你不想做任何事情或是否有误报通知时的解决方案。创建一个 .grype.yaml 文件,在其中排除高严重性的漏洞,然后用 --config 标志执行扫描,后面是包含排除项的 .grype.yaml 文件。

.grype.yaml 文件如下所示:

ignore:
  - vulnerability: GHSA-3mc7-4q67-w48m

再次运行扫描:

$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [62 packages]
 Scanned image           [10 vulnerabilities]
NAME       INSTALLED  FIXED-IN  TYPE          VULNERABILITY        SEVERITY 
snakeyaml  1.30       1.31      java-archive  GHSA-98wm-3w3q-mw94  Medium    
snakeyaml  1.30       1.31      java-archive  GHSA-c4r9-r8fh-9vj2  Medium    
snakeyaml  1.30       1.31      java-archive  GHSA-hhhw-99gj-p3c3  Medium    
snakeyaml  1.30       1.32      java-archive  GHSA-9w3m-gqgf-c4p9  Medium    
snakeyaml  1.30       1.32      java-archive  GHSA-w37g-rhq8-7m4j  Medium 

High 漏洞就不再显示了。

现在你知道如何手动扫描你的 Docker 镜像了。然而,你可能想把扫描镜像作为持续集成管道的一部分。在本节中,将提供一个使用 Jenkins 作为 CI 平台时的解决方案。

要回答的第一个问题是,当发现漏洞时,你将如何得到通知。到现在为止,你只能通过查看标准输出来注意到这些漏洞。这不是一个 CI 管道的解决方案。你想得到通知,这可以通过失败的构建来实现。Grype 有 --fail-on <severity>标志来达到这个目的。你可能不想在发现严重程度为 negligible 的漏洞时让管道失败。

让我们看看当你手动执行这个时,会发生什么。首先,在 Spring Boot 应用程序和 Docker 镜像中再次引入漏洞。

构建 JAR 文件,构建 Docker 镜像,用标志 --fail-on 运行扫描。

$ mvn clean verify
...
$ mvn dockerfile:build
...
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed --fail-on high
...
1 error occurred:
        * discovered vulnerabilities at or above the severity threshold

这里没有显示所有的输出,只显示了重要的部分。而且,正如你所看到的,在输出的最后,显示了一条信息,表明扫描产生了一个错误。这将导致你的 Jenkins 管道失败,因此,开发人员会被通知出了问题。

为了将其添加到您的 Jenkins 管道中,有几个选项。这里选择创建 Docker 镜像,并从 Maven 中执行 grype Docker 扫描。grype 没有单独的 Maven 插件,但你可以使用 exec-maven-plugin 来实现这一目的。在 POM 的 build-plugins 部分添加以下内容。

<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>exec-maven-plugin</artifactId>
      <version>3.1.0</version>
      <configuration>
        <executable>grype</executable>
          <arguments>
            <argument>docker:mydeveloperplanet/mygrypeplanet:${project.version}</argument>
            <argument>--scope</argument>
            <argument>all-layers</argument>
            <argument>--fail-on</argument>
            <argument>high</argument>
            <argument>--only-fixed</argument>
            <argument>-q</argument>
          </arguments>
      </configuration>
    </plugin>
  </plugins>
</build>

这里添加了两个额外的标志:

  • --scope all-layers。这将扫描 Docker 镜像中涉及的所有层。

  • --q:这将使用安静的日志,只显示漏洞和可能出现的故障。

你可以用下面的命令来调用它:

$ mvnd exec:exec

你可以把它添加到你的 Jenkinsfile 中的 withMaven 包装器中:

withMaven() {
  sh 'mvn dockerfile:build dockerfile:push exec:exec'
}

在本文中,你了解了如何通过 grype 来扫描你的 Docker 镜像。Grype 有一些有趣的、用户友好的特性,允许你有效地将它们添加到你的 Jenkins 管道中,另外安装 grype 也很容易。与 Anchor Engine 相比,Grype 绝对是一个很大的改进。

作者简介:

Gunter Rotsaert,系统工程师,居住在荷兰的比利时人。

原文链接:

https://mydeveloperplanet.com/2023/01/18/how-to-check-docker-images-for-vulnerabilities/

本文文字及图片出自 InfoQ


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK