如何从我们的代码库和基础镜像中快速检测并删除 log4j 类? "mvn dependency:tree" 不检查基本图像

How to quickly detect and remove log4j classes from our code base and the base image? "mvn dependency:tree" does not check base image

我们正在构建一个基于 Red Hat JBoss AMQ 6 的应用程序。我们将一些 Java 代码封装在基本映像周围以提供 AMQ 6 中缺少的额外功能。

现在,当Log4j的CVE中风时,我们发现这个组件是易受攻击的,因为它使用了log4j 1.x。现在我不只是说我们的 Java 代码使用它,而且 Red Hat AMQ 6 的基础映像也使用它。由于AMQ 6现已停产,Red Hat不再提供支持,因此不会有正式版本修复。

那么,如何从以下位置删除易受攻击的 log4j 1.x 类:

?

我正在使用 jib maven 插件构建。

似乎 mvn dependency:tree 只提供了我们的 Java 包装器代码的信息,而不是基本图像的信息。而且我不明白“+”和“-”是什么意思,而它给出这样的输出:

[INFO] +- org.jboss.resteasy:resteasy-jaxrs:jar:3.7.0.Final:compile
[INFO] |  +- org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:jar:1.0.2.Final:compile
[INFO] |  +- org.jboss.spec.javax.xml.bind:jboss-jaxb-api_2.3_spec:jar:1.0.1.Final:compile
[INFO] |  +- org.reactivestreams:reactive-streams:jar:1.0.2:compile
[INFO] |  +- javax.validation:validation-api:jar:2.0.1.Final:compile
[INFO] |  +- org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec:jar:1.0.1.Final:compile
[INFO] |  +- javax.activation:activation:jar:1.1.1:compile
[INFO] |  +- org.apache.httpcomponents:httpclient:jar:4.5.4:compile
[INFO] |  |  +- org.apache.httpcomponents:httpcore:jar:4.4.7:compile
[INFO] |  |  +- commons-logging:commons-logging:jar:1.2:compile
[INFO] |  |  \- commons-codec:commons-codec:jar:1.10:compile
[INFO] |  +- commons-io:commons-io:jar:2.5:compile
[INFO] |  +- net.jcip:jcip-annotations:jar:1.0:compile
[INFO] |  \- org.jboss.logging:jboss-logging:jar:3.3.2.Final:compile

+是否表示可以进一步展开但这里没有显示?

这里有一些背景:https://nvd.nist.gov/vuln/detail/CVE-2021-44228

第 1 步:处理我们的包装 Java 代码库

对于我们的代码,我们这样做:

mvn dependency:tree | grep log4j

我们发现其他团队的一些开发人员引入了可传递的 log4j 1.17。通知了那个团队,他们在最近的版本中修复了这个问题,我们只是在我们的 pom 中改变了它的版本,我们的 pom 就被修复了。

如果你的依赖不再维护,你可以进入你的组织的工件,并在你需要的所有jar中手动查找下面的classes(列表很长,因为除了CVE- 2021-4104 提到了 JMSAppender,我发现 log4j 1.x 有很多其他漏洞,应该删除更多 classes)

  • org/apache/log4j/net/SocketServer.class
  • org/apache/log4j/net/SimpleSocketServer.class(以防万一)
  • org/apache/log4j/net/SocketAppender.class
  • org/apache/log4j/net/SMTPAppender.class
  • org/apache/log4j/net/SMTPAppender.class
  • org/apache/log4j/net/JMSAppender.class
  • org/apache/log4j/net/JMSSink.class
  • org/apache/log4j/net/JDBCAppender.class
  • org/apache/log4j/chainsaw/*.class

如果您无法修复您的内部 Nexus repo/artifactory,您可以在本地 Maven 注册表(在 ~/.m2 下)找到 log4j 的 jar 并删除 class;然后你再次构建你的应用程序;但请记住不要使用 -U 从远程注册表重新下载 jar。

第 2 步:处理基本图像 jar

在包含 log4j 的基础镜像中查找其他库比较复杂。

  • 通过删除 classes 文件来篡改图层不会被 Docker 守护程序检测到。 sha256 值发生变化,您必须将主目录中 json 文件中的 sha256 值替换为新的 sha256sum layer.tar;但即便如此,Docker 守护程序在加载 tar 时也会出错:Cannot open /var/lib/docker/tmp-xxxx/...: file not found 左右。

  • 然后我尝试创建一个脚本以在 运行 时间删除 classes,就在 运行 应用程序之前,并定义一个新的入口点在运行安装应用程序之前运行它。

#!/bin/sh
/opt/amq/bin/fix_log4j_1.x_cves.sh
/opt/amq/bin/launch.sh # the original, inherited entrypoint in jib

但后来我发现它会减慢 pod startup;无响应 pods 可能会被 Openshift 恢复tar,从而导致不必要的延迟和错误。 但是这个脚本的输出让我知道哪些 jar 包含要删除的 classes,这是我下一个解决方案的坚实基础。

最后,我想出了一个完美的解决方案

  1. 实施之前的解决方案,docker run 图像,并记下脚本输出中的 jars 名称。
Starting to fix all CVEs regarding Log4j 1.x...
>>>>> Removing class file from '/opt/amq/lib/optional/log4j-1.2.17.redhat-1.jar':
removed 'org/apache/log4j/chainsaw/ControlPanel.class'
removed 'org/apache/log4j/chainsaw/ControlPanel.class'
...
>>>>> Removing class file from '/opt/amq/activemq-all-5.11.0.redhat-630495.jar':
...
  1. 在pom.xml
  2. 中将这两个定义为provided依赖
  3. 使用 maven 复制依赖插件将它们复制到构建文件夹中(默认 target/dependency
  4. 使用 maven exec 插件 运行 针对 target/dependency 目录删除易受攻击的 classes 的相同脚本,同时使用 jib
  5. 构建图像
  6. 使用 maven jib 插件将固定的 jar 复制到容器中,这样它们将在所有先前层之上的新层上,到 shadow/whiteout 未固定的 jar(参见 )

通过这样做,我们在构建映像时消除了易受攻击的 classes,pod startup 速度没有受到影响,传输到生产映像注册表的二进制文件已经安全。

这种方法的一个优点是我们不受容器提供的可用工具的限制,因为脚本 运行 现在在我们的本地环境中。我们可以安装我们需要的任何工具并在脚本中使用它们。例如,在原始脚本中我定义了函数 extract_remove_repackage 来完成一个简单的提取+删除 classes+重新打包的任务,只是因为 zip 没有安装在基础镜像中。但是在我的本地机器上,这可以通过 zip 一行完成,

你必须确保将 3、4 和 5 绑定到不同的 Maven 构建阶段,以便它们按这样的顺序发生。 我将 3) 绑定到 compile, 4) 到 process-classes 和 5) 到 package.


实施细节如下:

  • 脚本(在我以前的解决方案中,放在 src/main/jib/opt/amq/bin 下,因此可以将其复制到容器中。您还需要在同一文件夹中使用新的入口点脚本。现在在这个解决方案中,移动到 src/main/scripts)

fix_log4j_1.x_cves.sh:

#!/bin/bash
# Script to fix log4j 1.x CVEs. Initially it is only for CVE-2021-4104, but
# since there are multiple CVEs regarding log4j 1.x, they are all fixed here:

# Class File                                        CVE
# org/apache/log4j/net/SocketAppender.class         CVE-2019-17571
# org/apache/log4j/net/SocketServer.class           CVE-2019-17571
# org/apache/log4j/net/SMTPAppender.class         CVE-2020-9488
# org/apache/log4j/net/SMTPAppender.class           CVE-2020-9488
# org/apache/log4j/net/JMSAppender.class            CVE-2021-4104
# org/apache/log4j/net/JMSSink.class                CVE-2022-23302
# org/apache/log4j/net/JDBCAppender.class           CVE-2022-23305
# org/apache/log4j/chainsaw/*.class                 CVE-2022-23307

cves=(
'CVE-2019-17571'
'CVE-2019-17571'
'CVE-2020-9488'
'CVE-2020-9488'
'CVE-2021-4104'
'CVE-2022-23302'
'CVE-2022-23305'
'CVE-2022-23307'
)

size() {
    stat -c %s ""
}

extract_remove_repackage() {
    before=
    # jar xf -C some_dir only extract to current dir, we have to cd first
    jar_dir=$(dirname "")
    jar_file=$(basename "")
    temp_dir=$jar_dir/temp
    mkdir "$temp_dir"
    cp list.txt "$temp_dir"/ && cp "" "$temp_dir"/
    cd "$temp_dir"
    jar xf "$jar_file"
    # provide file and dir names to rm with list.txt
    xargs rm -rvf < list.txt && rm list.txt "$jar_file"
    jar cf "$jar_file" .
    mv "$jar_file" ../
    # go back and clean up
    cd "$before" && rm -rf "$temp_dir"
}

find_vulnerable_jars() {
    cd "$root_dir"
    jar -tvf "" | grep -E "$pattern" | awk '{ print  }' > list.txt
    if [ "$(size list.txt)" -gt 0 ]; then
        echo ">>>>> Removing class file from '$(realpath "")'":
        extract_remove_repackage "$(pwd)" ""
    else
        return 0
    fi
}
remove_classes_from_jars() {
    echo Starting to fix all CVEs regarding Log4j 1.x...
    # exclude jolokia.jar(link)
    # xargs can return error level to "if", when any of execution fails, while "find -exec" cannot
    # because we use custom function, xargs needs "bash -c"; thus we have to use "_" to pass each arg
    if find "$root_dir" -name "*.jar" -not -type l -print0 | xargs -0 -n1 bash -c 'find_vulnerable_jars "$@"' _; then
        echo All vunerable classes removed. CVE addressed:
        printf '%s\n' "${cves[@]}"
    else
        echo "Error while removing classes; exiting..."
        return 1
    fi
}

# to be able to use in find -exec child shell, we need to export all vars and functions
# : where to search jars, should match copy-dependency output dir.
export root_dir=
export pattern=".*(JMS|JDBC|SMTP|Socket)Appender.*.class|.*SocketServer.class|.*JMSSink.class|org/apache/log4j/chainsaw/.*"
export -f size
export -f extract_remove_repackage
export -f find_vulnerable_jars
remove_classes_from_jars
  • 定义提供的依赖项:
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-all</artifactId>
    <version>${version.activemq-all}</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>${version.log4j}</version>
    <scope>provided</scope>
</dependency>
  • copy-dependency插件
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-cve-jars</id>
            <phase>compile</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <includeArtifactIds>activemq-all,log4j</includeArtifactIds>
                <includeScope>provided</includeScope>
                <includeTypes>jar</includeTypes>
                <outputDirectory>${project.build.directory}/dependency</outputDirectory> <!-- default value -->
                <excludeTransitive>true</excludeTransitive>
            </configuration>
        </execution>
    </executions>
</plugin>
  • exec-plugin:
<plugin>
    <artifactId>exec-maven-plugin</artifactId>
    <groupId>org.codehaus.mojo</groupId>
    <executions>
        <execution>
            <id>remove-cve-classes</id>
            <phase>process-classes</phase>
            <goals>
                <goal>exec</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <executable>${project.build.scriptSourceDirectory}/log4j_cve_fix.sh</executable>
        <arguments>
            <!-- should match copy-dependency output dir -->
            <argument>${project.build.directory}/dependency</argument>
        </arguments>
    </configuration>
</plugin>

jib plugin:(需要 > 3.0.0 才能使用 <path><inclueds>

<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
    <configuration>
        <from>
            <image>${docker.base.image}</image>
        </from>
        <to>
            <image>${docker.image}</image>
            <tags>
                <tag>${project.version}</tag>
            </tags>
        </to>
        <container>
            <appRoot>/dev/null</appRoot>
            <entrypoint>INHERIT</entrypoint> <!-- customized entrypoint not needed anymore, just revert to the way it was -->
        </container>
        <containerizingMode>packaged</containerizingMode>
        <extraDirectories>
            <paths>
                <path>${project.basedir}/src/main/jib</path>
                <path>${project.build.directory}/jib</path>
                <path>
                    <from>target/dependency</from>
                    <into>/opt/amq/lib/optional</into>
                    <includes>log4j-${version.log4j}.jar</includes>
                </path>
                <path>
                    <from>target/dependency</from>
                    <into>/opt/amq</into>
                    <includes>activemq-all-${version.activemq-all}.jar</includes>
                </path>
            </paths>
            <permissions>
                <permission>
                    <!-- don't forget to restrict writing to prevent tampering -->
                    <file>/opt/amq/conf/log4j.properties</file>
                    <mode>444</mode>
                </permission>
                <!-- the copied jars need to be executable -->
                <permission>
                    <file>/opt/amq/lib/${application.executable}</file>
                    <mode>755</mode>
                </permission>
                <permission>
                    <file>/opt/amq/activemq-all-${version.activemq-all}.jar</file>
                    <mode>755</mode>
                </permission>
                <permission>
                    <file>/opt/amq/lib/optional/log4j-${version.log4j}.jar</file>
                    <mode>755</mode>
                </permission>
            </permissions>
        </extraDirectories>
    </configuration>
    <executions>
        <execution>
            <id>jib-build</id>
            <phase>package</phase>
            <goals>
                <goal>${jib.goal}</goal>
            </goals>
        </execution>
    </executions>
</plugin>

切记始终从下面列出的资源中获取最新信息


直接回答问题:
检查代码中的 Log4J 依赖项:

  • 我认为 WesternGun 的回答很好...但我个人认为最简单的事情可能就是构建您的应用程序(如果您还没有),然后递归搜索构建应用程序的目录结构以查找 JAR 文件匹配正则表达式 log4j-core-2.([0-9]+\.){1,2}jar(将检测易受 CVE-2021-45046 ... CVE-2021-44228 ... CVE-2021-45105 影响的版本)。如果您还想检测旧版本(它们有自己的严重严重性 CVE 并且还需要升级),那么 REGEX 将只是 log4j 并且您必须手动找出哪些特定的 jar 是易受攻击的那些。
    • 它实际上可以进一步完善...但我不确定 特定的 jar 文件是否是这些其他 CVE 的问题:CVE-2019-17571 and CVE-2021-4104
  • Reddit thread: log4j_0day_being_exploited cntl+f for .class and .jar recursive hunter 将为您提供一些工具来帮助您进行递归搜索

检测 Log4J 在 运行 应用程序上的使用(是否在容器中并不重要):

  • 转到 Reddit thread: log4j_0day_being_exploitedcntl+f 以获得 Vendor Advisories。在列表中搜索任何 software/plugins 你是 运行。如果您是 运行 列表中的某个人并且有可用的更新,请更新。
  • 然后转到相同的网站和 cntl+f for Vulnerability Detection。使用那里的工具。如果您检测到该漏洞,请修复。
  • 然后转到相同的网站和 cntl+f for Exploitation Detection。使用那里的工具。这些将检测您是否 已经 受到攻击。如果您检测到您有,则根据需要补救和响应该攻击。

更多资源


修复:
CVE-2021-45046 ... CVE-2021-44228 ... CVE-2021-45105
虽然大多数需要知道的人可能已经知道足够多的知识来做他们需要做的事情,但我想我还是会把它放在以防万一...

  • 遵循这些资源中的指导...它可能会改变,但是

截至 2021-12-18

基本上是

  • 如果可能,删除 log4j-core JAR 文件
    • 来自两台 运行 机器以立即修复 AND
    • 在您的源代码/源代码管理文件中,以防止未来的构建/发布/部署覆盖更改
  • 如果那不可能(由于依赖),升级它们
    • 如果你是运行Java8,那么你可以升级到log4j 2.17.0+
    • 如果您运行是Java的早期版本,那么您可以升级到log4j 2.12.3
    • 如果你是运行旧版本的Java,那么你需要升级到最新版本的Java,然后使用最新版本的Log4J
    • 同样,这些更改必须同时发生在 运行 机器和代码中
  • 如果由于某种原因这些都不可能...那么存在从 log4j-core JAR 中删除 JndiLookup.class 文件的非补救措施。
    • 在 Linux 上使用 zip 命令有一个止损选项,默认情况下大多数 Linux 发行版都附带了该命令。
      • zip -q -d "$LOG4J_JAR_PATH" org/apache/logging/log4j/core/lookup/JndiLookup.class
    • 在撰写本文时,Windows 上关于权宜之计选项的大多数在线指南都说要执行以下操作(再次...假设您不能执行删除 JAR 或升级选项之一多于):
      • 安装类似 7-zip 的东西
      • 找到您所有的 log4j-core JAR 文件,并对每个文件执行以下操作...
      • 重命名 JAR 以将扩展名更改为 .zip
      • 使用 7-zip 解压缩 JAR(现在具有 .zip 扩展名)
      • 从解压缩的文件夹中找到并删除 JndiLookup.class 文件
        • 路径是\path\to\unzippedFolder\org\apache\logging\log4j\core\lookup\JndiLookup.class
      • 删除旧的 JAR 文件(现在扩展名为 .zip)
      • 使用 7-zip 重新压缩文件夹
      • 重命名新的 .zip 文件夹以将扩展名更改为 .jar
    • 还有一些使用Power的选项Shell

如果您只有 1 或 2 个 JAR 文件要处理并且您不介意安装 7-zip 或您有可用的 PowerShell 就可以了。但是,如果您有很多 JAR 文件,或者如果您不想安装 7-zip 并且无法访问 Power Shell,我创建了一个开源 VBS 脚本来为您完成无需安装任何额外的软件。 https://github.com/CrazyKidJack/Windowslog4jClassRemover

阅读自述文件和发行说明https://github.com/CrazyKidJack/Windowslog4jClassRemover/releases/latest

您可以使用:

mvn dependency:tree -Dincludes=*log4j*

它将找到任何在其 groupId 中的任何位置具有“log4j”的依赖项和传递依赖项。

输出示例:

\- org.springframework.boot:spring-boot-starter-web:jar:2.6.0:compile
[INFO]    \- org.springframework.boot:spring-boot-starter:jar:2.6.0:compile
[INFO]       \- org.springframework.boot:spring-boot-starter-logging:jar:2.6.0:compile
[INFO]          \- org.apache.logging.log4j:log4j-to-slf4j:jar:2.14.1:compile
[INFO]             \- org.apache.logging.log4j:log4j-api:jar:2.14.1:compile

每个模式段都是可选的,并且支持完整和部分 * 通配符。空模式段被视为隐式通配符。

例如,org.apache.* 将匹配组 ID 以 org.apache 开头的所有工件,而 ::::*-SNAPSHOT 将匹配所有快照工件。

另见 maven doc

编辑

然后您很可能希望通过以下方式排除这些依赖项:

<dependency>
    <groupId>your dep groupId</groupId>
    <artifactId>your dep artifactId</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
            </exclusion>
    </exclusions>
</dependencies>

备注

在撰写本文时,它是 < 2.17.1 的任何版本

存在漏洞的 Log4j 版本在 Maven Repository

上可用