
Log4Shell脆弱性(CVE-2021-44228)の実用的なデモンストレーション
このリポジトリは、セキュリティ関連のセミナー論文の一環として、教育およびデモンストレーション目的のみに使用されます。本コードを本番環境や明示的な許可なしにシステムに対して使用しないでください。この構成は、ロギング、名前解決、クラスの動的ローディングといった一見無害な機能が組み合わさると、どのように複雑な脆弱性が生じるかを示し、セキュリティ意識を高めることを目的としています。
本セミナー論文の目的は、2021年12月に公表され、近年最も深刻な脆弱性の一つと評価されたLog4Shellセキュリティホール(CVE-2021-44228)に関する深い理解を提供することです。本論文では、理論的な基礎を説明するだけでなく、脆弱性の実践的なデモンストレーションも行います。
Log4Shellセキュリティホールを実践的に示すために、このリポジトリでは完全な攻撃の流れを再現可能な、隔離されたコンテナ環境を構築しました。デモンストレーションは3つの中心的なコンポーネントに基づいています。
User-Agentヘッダーをログに記録し、攻撃者がこれを操作して脆弱性を悪用できます。Exploit.class)を配信する単純なHTTPサーバーです。LDAPサーバーと同様に、このサーバーも攻撃者の制御下にあります。注意: セットアップとデモンストレーションの実行に関する詳細は、4. プロジェクト構成とセットアップと5. プロジェクトのデモのセクションを参照してください。
Log4Shellは、JavaライブラリLog4jにおけるCVE-2021-44228という識別番号を持つ深刻なセキュリティ脆弱性の名前です。これにより、攻撃者は最小限の労力でリモートサーバー上で任意のコードを実行(リモートコード実行、RCE)することが可能になります。
この脆弱性はLog4jの2.0から2.14.1までのバージョンに影響し、多くのセキュリティ当局(ドイツ連邦情報セキュリティ庁(BSI)など)によって最高リスクレベルに分類されるほど深刻です。
Log4Shellが特に危険な理由は以下の通りです。
実際の原因は、Lookupと呼ばれる機能を通じて、ログメッセージに動的コンテンツをロードできるLog4jの機能にあります。JNDI(Java Naming and Directory Interface) およびLDAP(Lightweight Directory Access Protocol) プロトコルと組み合わせることで、リモートの悪意のあるJavaクラスをロードして実行することが可能になります。
この脆弱性の発見と公開により、世界中でセキュリティ対応の波が引き起こされました。多くのシステムが直ちにパッチ適用またはシャットダウンを余儀なくされました。その後、さらに関連する脆弱性(例:CVE-2021-45046)が明らかになり、この問題がいかに深刻かつ危険であったかを示しています。
以下では、脆弱性をより深く理解するために、関連する技術とその相互作用について詳しく説明します。
Log4jは、Apacheによって作成されたJavaアプリケーションにおけるイベントを記録するためのライブラリです。ロギングはソフトウェア開発において、システムの監視やエラー分析を行うための中心的なツールです。Log4jはJavaエコシステムで最も有名で広く使われているロギングフレームワークの一つであり、小規模なアプリケーションから大規模なエンタープライズシステムまで使用されています。
プログラムの実行中には、例えば以下のようなイベントが発生します。
これらのイベントはログとして文書化され、通常はコンソールへのテキスト出力、ファイル、またはネットワークプロトコルを介した中央ログサーバーへの出力として記録されます。適切なロギングにより、アプリケーションがいつ何を行ったかを追跡できます。
Log4jは、ログメッセージの生成と処理のための柔軟で高度に設定可能なインフラストラクチャを提供します。中心的な機能には以下が含まれます。
DEBUG、INFO、WARN、ERROR)があり、ログの詳細度を制御できます。セミナー論文に関連するその他の機能、特にプレースホルダー機能とLookup機能については、後のセクションで扱います。
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;
public class Example { private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {
logger.info("Starte Anwendung...");
}
}
この簡単な例では、Loggerインスタンスが作成されるか、既に存在する場合は取得されます。その後、`INFO`レベルのログメッセージが出力されます。Log4jは設定に基づいてメッセージのフォーマットと出力を処理します。設定の例は次のようになります:```xml
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
この設定は、Datum Uhrzeit Log-Level Loggername - Nachricht の形式でログメッセージをコンソールに出力するアペンダーを定義します。このアペンダーはルートロガーに割り当てられ、INFO レベル以上のすべてのログメッセージを処理します。
出力は次のようになります:``` 2023-10-01 12:00:00 INFO Example - Starte Anwendung...
それでは、Log4Shell脆弱性に最も関連するLog4jの具体的な機能について説明します。
#### ログメッセージ内のプレースホルダ
Log4jの特に便利な機能の1つは、ログメッセージ内の**プレースホルダ**のサポートです。これにより、動的なコンテンツを実行時にログ出力に挿入できます:```java
String username = "Alice";
logger.info("Benutzer angemeldet: {}", username);
実行時に、{} は変数 username の実際の値に置き換えられます。
これにより次の出力が得られます:```text
"Benutzer angemeldet: Alice"
#### 動的式(ルックアップ)
単純なプレースホルダーの他に、Log4jではログメッセージ内でより複雑な式を直接解決する機能も提供しています。この機能は**ルックアップ**と呼ばれ、実行時に値を動的に挿入できます(例:環境変数、システム情報、設定値など)。
そのような動的式の例:
- `${env:HOME}` - 環境変数 `HOME` の値を返します。Linux / macOS では、例えば `/home/username` となります。
- `${docker:...}` - アプリケーションが実行されている Docker コンテナに関する情報を提供する可能性があります。
- `${jndi:...}` - 内部または外部リソースをロードするために JNDI ルックアップを実行します。
次のセクションでは、JNDI 機能が Log4Shell 脆弱性において中心的な役割を果たすため、より詳しく見ていきます。
### 3.2 JNDI - ルックアップメカニズム
**JNDI** は _Java Naming and Directory Interface_ の略で、**名前およびディレクトリサービス**にアクセスするための標準化された Java API です。JNDI を使用すると、Java アプリケーションはリソースを技術的なパスではなく、シンボリック名で参照できます。
JNDI の古典的な使用例はデータベース接続の検索であり、ここで見られるように:```java
public class JndiExample {
public static void main(String[] args) throws Exception {
InitialContext ctx = new InitialContext();
Datasource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/myDB");
// Datenbankverbindung verwenden
}
}
最初に、JNDIを使用した名前解決のエントリポイントとなるInitialContextが作成されます。次に、lookupメソッドを使用してリソースを検索します。この場合は、シンボリック名java:/comp/env/jdbc/myDBを持つデータソース(DataSource)です。

JavaアプリケーションはJNDIのプロトコルに依存しないインターフェースを使用します。これにはlookupメソッドを持つInitialContextなどのクラスが含まれます。APIはLDAP、DNSなどを使用する場合でも常に同じです。Naming Managerは仲介役として機能し、実際の通信を担当する適切な_Service Provider_を選択します。JNDI SPI(Service Provider Interface)は、さまざまなプロトコルに対してJNDI機能を実装するクラスのコレクションです。この場合、関連するService ProviderはLDAPです。
次のセクションでは、Service Provider LDAPを詳しく見ていきます。
LDAPは_Lightweight Directory Access Protocol_の略で、いわゆるディレクトリサービスへのアクセスを可能にする標準化されたネットワークプロトコルです。元々はX.500の軽量な代替として開発され、今日では多くの企業ネットワーク、特に中央集権的なユーザーおよび権限管理において標準となっています。
ディレクトリサービスは、情報を階層形式で保存する構造化データベースです。リレーショナルデータベースとは異なり、ディレクトリは次の特性を持ちます。

画像でわかるように、LDAPディレクトリはツリー状の構造で編成されています。ルートレベルには**Domain Components (dc)があります。その下には、Usersなどのさらに細かい区分を表すOrganizational Units (ou)が存在します。個々のユーザーやオブジェクトには、特定のエントリを識別し、さまざまな属性を含むことができるCommon Names (cn)**があります。
意味:
dn: Distinguished Namedc: Domain Componentou: Organizational Unitcn: Common Nameここで、LDAPがどのように呼び出されるか、そしてLog4Shell脆弱性においてどのような役割を果たすかを見てみましょう。
LDAPでは、外部クラスへの参照を保存することもでき、必要に応じて後からロードすることができます。これはjavaClassNameやjavaCodeBaseなどの特別な属性を使用して行われます。これらの属性は、JavaクラスをロードするURLを参照できます。
例えば、次のURLを使用して、Javaクラスを参照するオブジェクトをクエリできます:``` ldap://ldap-server:1389/Exploit
