話題の記事

Log4j2 脆弱性問題における SpringBoot アプリケーションの検証

2021.12.11

先日騒ぎになっていた CVE-2021-44228 についてのアプリケーション側の対応について記載いたします。

緩和策としてすでに AWS WAF での Rule の Update 等が行われているため、AWS サービスの詳細については別途記事を御覧ください。

Log4jの脆弱性対策としてAWS WAFのマネージドルールに「Log4JRCE」が追加されました

ここでは SpringBoot をベースとしたアプリケーションへの影響と対応可否の判断についてどのような調査を行ったかを記載します。

ひとまず結論

Spring 側から見解がすでに出ています。

Log4J2 Vulnerability and Spring Boot

以下抜粋します。

Spring Boot users are only affected by this vulnerability if they have switched the default logging system to Log4J2. The log4j-to-slf4j and log4j-api jars that we include in spring-boot-starter-logging cannot be exploited on their own. Only applications using log4j-core and including user input in log messages are vulnerable.

「SpringBoot ではデフォルトのログ実装から Log4j2 へ明示的に変更しない限り、この脆弱性による影響を受けない。 log4j-core を利用、かつユーザー入力をログメッセージに含む場合のみ影響を受ける」

我々の調査でも同じ結論に達していたので、公式の裏付けができたことになります。

ではどのように調査を進めたか。そして顧客へ説明するための材料をどのように集めたかについてをいかにつらつらと書いていきます。

第一報

2021/12/10 午後あたりにあるお客様から緊急の問い合わせを受けました。

「稼働中のサービスに log4j2 が含まれる可能性があるので緊急で調べてほしい」とのこと

私自身も log4j2 のこの脆弱性の情報は眺めておりましたが、我々の開発しているアプリケーションでは、Slf4j + Logback の構成によるログ出力構成を行っていたため領域外の話であると認識していました。

log4j2 関連依存の存在

アプリケーションを構成している build.gradle には当然 log4j の依存は明示的に指定されておりません。さらにパッケージ内でも log4j に関わるパッケージを参照しているような箇所はないことを確認しました。

次に SpringBoot そのものが依存しているパッケージの存在が可能性としてあります。gradle の dependencies で依存関係の一覧を取得します。 *1

|    +--- org.springframework.boot:spring-boot-starter-logging:2.X.XX
|    |    +--- ch.qos.logback:logback-classic:1.2.3
|    |    |    +--- ch.qos.logback:logback-core:1.2.3
|    |    |    \--- org.slf4j:slf4j-api:1.7.25 -> 1.7.30
|    |    +--- org.apache.logging.log4j:log4j-to-slf4j:2.XX.X
|    |    |    +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.30
|    |    |    \--- org.apache.logging.log4j:log4j-api:2.XX.X

spring-boot-sterter-logging の中に log4j に関わるものが 2 つあることがわかりました。これが今回の脆弱性の原因となるかどうかを検証し、この依存があっても問題ないのか、それともこの依存は脆弱性をついた攻撃の対象となりうるのかを、明確に判断し答える必要があります。

そこで我々は以下の方法で検証を行いました。

  • まず PoC とされるコードで攻撃が実行できるかを検証
  • ログ実装を Lo4j2 の脆弱性のあるバージョンを含む構成に変更し、上記攻撃が成功するかを検証
  • ログ実装を Lo4j2 の脆弱性のあるバージョンを含む構成に対し、以下の2つの点で攻撃が防げることを検証
    • 我々の 現在の SpringBoot アプリケーションに対して上記攻撃を行った際に問題がないかどうかを検証
      • log4j の依存を含んでいても、Logback によるログ実装を選択している場合は問題がないこと
    • JVM実行引数、もしくは properties の設定による攻撃の抑制が可能か
      • PoC にあった対応が正しいかどうか

実際には「我々の 現在の SpringBoot アプリケーションに対して上記攻撃を行った際に問題がないかどうかを検証」この項目については、初手で検証を実施しています。

しかし、「正しく動作しており、攻撃は実行できない」だけでは証拠としては不十分です。原因を特定した上で、この依存と設定が存在しないから「攻撃が成功しない」というところまで説明できるようにしておく必要があります。そのため、上記のように検証のバリエーションを増やしています。

検証について

検証は 2 つの作業を並行して実施しました。

  1. 対象のアプリケーションに対して、攻撃を再現できるようなテストケース及び構成を作成、検証するチーム
  2. 対象のアプリケーションに対し、脆弱性を含んだ構成へ変更させるチーム

わたしは主に 2 の方を担当し、私達のアプリケーションのログ実装構成をすべて変更して、攻撃が成功すると思われる状況にするというタスクを行いました。

Logback から Log4j2 への変更作業

build.gradle には明示的に Logback の依存が宣言されており、まずはこちらをすべて削除します。

//  compile "ch.qos.logback.contrib:logback-json-classic:$logbackContribVersion"
//  compile "ch.qos.logback.contrib:logback-jackson:$logbackContribVersion"
//  compile 'org.codehaus.janino:janino:3.1.6' // for log evaluator
//  compile 'org.codehaus.janino:commons-compiler:3.1.6'

さらに logback.xml といった Logback 関連の設定ファイルを削除します(念の為)

続いて Log4j2 の依存を追加します。

セットアップ方法に関しては、SpringBoot の Official Document を参考にします。

https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.logging.log4j

implementation "org.springframework.boot:spring-boot-starter-log4j2"
    modules {
        module("org.springframework.boot:spring-boot-starter-logging") {
            replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
        }
    }

src/main のリソース配下に以下の xml を配置します。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout
                    pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level}[%style{%t}{bright,blue}] %style{%C}{bright,yellow}: %m%n"/>
        </Console>
        <RollingFile name="RollingFile"
                     fileName="./logs/spring-boot-logger-log4j2.log"
                     filePattern="./logs/$${date:yyyy-MM}/spring-boot-log4j2-%d{-dd-MMMM-yyyy}-%i.log.gz">
            <PatternLayout>
                <pattern>%d %p %C [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</configuration>

これだけで問題ないはず。

この状態で依存関係を確認します。

+--- org.springframework.boot:spring-boot-starter-log4j2 -> 2.X.XX
|    +--- org.apache.logging.log4j:log4j-slf4j-impl:2.12.XX
|    |    +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.30
|    |    \--- org.apache.logging.log4j:log4j-api:2.12.XX
|    +--- org.apache.logging.log4j:log4j-core:2.12.XX
|    |    \--- org.apache.logging.log4j:log4j-api:2.12.XX
|    +--- org.apache.logging.log4j:log4j-jul:2.12.XX
|    |    \--- org.apache.logging.log4j:log4j-api:2.12.XX

log4j-core を含めた複数の依存が追加されたことが確認できます。Version は 2.12.XX なのでばっちり脆弱性を含んだ対象ですね。良さそう(良いのか?)

Logback の依存が外れない

ところが実行しても Log4j2 へ切り替わりません。なぜか。

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Hoge.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.3/7c4f3c474fb2c041d8028740440937705ebb473a/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Hoge/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-slf4j-impl/2.12.XX/14973e22497adaf0196d481fb99c5dc2a0b58d41/log4j-slf4j-impl-2.12.XX.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

ログの通り、クラスパス上に複数の Binding が見えています。Slf4j は複数の Binding での動作は許しておらずどちらかしか適用できません。

http://www.slf4j.org/codes.html#multiple_bindings

そしてこの場合、Logback が優先的に選択されています。 Logback は先程の準備ですべて削除したはずなのですが、クラスパスに残っているようです。再度依存関係を確認します。

+--- jp.classmethod.sample:child-module:1.0.10
|    +--- org.slf4j:slf4j-api -> 1.7.30
|    +--- javax.xml.bind:jaxb-api:2.3.1 (*)
|    +--- com.sun.xml.bind:jaxb-impl:2.3.1
|    +--- com.sun.xml.bind:jaxb-core:2.3.0.1
|    +--- jp.classmethod.sample:child-module-core:1.0.10 (*)
|    +--- ch.qos.logback:logback-classic -> 1.2.3
|    |    +--- ch.qos.logback:logback-core:1.2.3
|    |    \--- org.slf4j:slf4j-api:1.7.25 -> 1.7.30
|    +--- ch.qos.logback.contrib:logback-json-core:0.1.5
|    |    \--- ch.qos.logback:logback-core:1.1.3 -> 1.2.3

我々がチーム内で開発しているライブラリを取り込んでいるのですが、その子モジュールが Logback への依存を持っているようです。これを Runtime の依存に入れてほしくない。

古くから開発されており、Gradle のメンテナンスがされていないため記述が古いのですが以下のように修正しました。

// compile "jp.classmethod.sample:child-module:$version"
compileOnly "jp.classmethod.sample:child-module:$version"

これで Logback から Log4j2 へ変更することができました。

SpringBootアプリケーションへの攻撃方法について

別チームが並行で作業していたため、まだまとまっていません。後日まとめます。

具体的な実行可能な検証コードについては、2021/12/15 時点でアウトプットするのがグレーゾーンとのことなので、掲載を見送ります。

我々のアプリケーションにわざと脆弱性を作り込んだ上でリモートコードを実行することを確認。実行シーケンス等の資料も作成しましたので、もし問題ないと判断された場合には公開するかもしれません。

攻撃について

上記脆弱性のある Log4j2 で構成したアプリケーションで、当該の攻撃を実行したところ実際に攻撃が成功することを確認しました。これにより

  • Logback で構成したアプリケーションに含まれる log4j2 関連の依存だけでは攻撃は成功しない
  • Log4j2 へログ機構をスイッチした場合に含まれる依存によって脆弱性が生まれ攻撃が成功する(この場合, log4j-core

ことまで確認できました。

JNDILookup

https://blog.cloudflare.com/inside-the-log4j2-vulnerability-cve-2021-44228/

こちらに解説があるように、今回の脆弱性は JNDILookup というクラスが問題とのこと。このクラスは log4j-core に存在するため、この依存が問題になるようです。

https://github.com/apache/logging-log4j2/blob/master/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JndiLookup.java

我々のアプリケーションの依存を確認し、Interface に関わる依存はいくつかあるものの、脆弱性の大本である core の依存が入っていないから問題ないと言えるのではないでしょうか。

まとめ

現在もいくつか検証を残していますが、攻撃が成功しないことのみを確認するだけでなく、攻撃の原因となる脆弱性のポイントの確認、なぜ攻撃が成功しないかをアプリケーションを提供するお客様へマネージャー陣が提示できる材料を揃えるため、数名で検証しました。

結果として、大きな影響はなさそうであるものの、昨今のアプリケーションはたくさんの依存を持っており、「問題がないことを証明する」ことが非常に難しくなってきていると感じました。

また、規模が大きくなりアップデートが滞ると、脆弱性以外にも積算する影響が大きくなるため、どうしてもインパクトが大きくなりがちです。アプリケーション基盤の進化も非常に早いので、そういった流れに置いていかれないように、こまめに基盤のアップデートも追っていきたいところです。

おまけ

この調査は 2021/12/10 終業のタイミングで、アプリケーション側としては一旦の見解は出ており、調査によって休日出勤を強いられてこの記事を書いているわけではありません。

参照

脚注

  1. SpringBootのバージョンがやけに古いことについてはスルーでお願いします。