
Rails CVE-2026-66066 向けシングルファイル PoC:任意ファイル読み取り、シークレットの復元、スレッド化スキャン、および署名付き画像バリエーションによる条件付き RCE。
Rails Active Storage/libvips の任意ファイル読み取り脆弱性に対する、依存関係ゼロ・単一ファイルの概念実証(PoC)と、ループバックのみの Docker ラボです。また、アドバイザリに記載された、回収した secret_key_base から偽造した画像バリエーションとコマンド実行への条件付き昇格も実演します。
主な参照先: Rails アドバイザリ、 Rails フォレンジック分析、 Ethiack 技術解説、 および 8.1.3.1 リリース。
https://github.com/user-attachments/assets/fdcb5651-c1d1-4bc4-b322-8f1205b09635
kr2s.py には完全なクライアントが 1 つのファイルに含まれています:
h5py や numpy を必要としない、埋め込み可能でパッチ可能な HDF5/MAT ペイロードこのツールはパッケージのインストール、コールバックリスナーの起動、ターゲット上の curl の要求、make への依存を行いません。RCE ステージは組み込みの Active Storage representation ルートを使用し、Marshal ガジェットを必要としません。
脆弱なラボを起動します:
cd lab
RAILS_VERSION=8.1.3 docker compose up --build
Compose のポートは 127.0.0.1 にバインドされ、コンテナはすべての Linux ケーパビリティを落とした非特権ユーザーとして実行されます。
別のターミナルで:
# Intrusive: uploads a blob, then confirms /etc/hostname is readable.
python3 kr2s.py check http://127.0.0.1:3000
# Recover a fixed-size prefix. Binary output goes to stdout unless -o is used.
python3 kr2s.py read http://127.0.0.1:3000 /etc/passwd --size 4096
python3 kr2s.py read http://127.0.0.1:3000 /etc/passwd -o passwd.bin
# Recover selected environment secrets. Values are redacted by default.
python3 kr2s.py secrets http://127.0.0.1:3000
# Display complete matched environment values for authorized analysis.
python3 kr2s.py secrets http://127.0.0.1:3000 --show-full
# Passive preflight of one target or a file containing one target per line.
python3 kr2s.py scan http://127.0.0.1:3000
python3 kr2s.py scan targets.txt --threads 10 --json scan.json
# Intrusively confirm file read on every target in the list.
python3 kr2s.py scan targets.txt --active
# Probe selected libvips loaders.
python3 kr2s.py probe http://127.0.0.1:3000
# Conditional escalation. This executes the supplied command.
python3 kr2s.py rce http://127.0.0.1:3000 "id; uname -a"
すべての上書きオプションについては python3 kr2s.py COMMAND --help を実行してください。実アプリケーションで一般的な制御オプションには、--harvest-url、--show-path、--proxy、--insecure、--skb があります。
secrets --show-full は、一致した環境変数の値を完全に表示します。これを指定しない場合、ツールは最初の 12 文字と省略記号のみを表示します。--also-read で要求されたファイル(デフォルトの Rails クレデンシャルパスを含む)は、読み取り可能な場合に最大 600 文字まで表示されます。
受動的な scan は、ページが収集可能な Active Storage 表現を公開しているかどうかのみを報告し、インストールされている Rails のパッチレベルを特定することはできません。scan --active は悪意のある blob をアップロードし、カナリア読み取りを試みます。能動スキャンの失敗結果は、WAF、認証、未対応のバリエーション、Rails の修正がアプリケーション外部からは同一に見える可能性があるため、SAFE ではなく BLOCKED_OR_INCONCLUSIVE とラベル付けされます。
ラボを停止するには:
cd lab
docker compose down
image/png として宣言された Active Storage ダイレクトアップロードを予約します。matload に渡します。ターゲットファイルのバイト列は画像ピクセルになり、生成された PNG として返されます。このハイブリッドファイルは 2 つのパーサーを同時に満たします。テキストは MATLAB 5.0 で始まり、オフセット 124 のバージョンワードが HDF5 ベースの MATLAB 7.3 パスを選択します。Rails の修正は、攻撃者が制御するファイルが matload に到達する前に、信頼できないとマークされた libvips 操作をブロックします。
rce は、--skb が指定されない限り、まず /proc/self/environ から SECRET_KEY_BASE を読み取ります。次に Active Storage の verifier キーを導出し、instance_eval 操作を含む JSON バリエーションに署名します。影響を受ける vips パスでは、この操作は Vips::Image.public_send に到達します。PoC はこれを組み込みの representation ルート経由で送信し、指定されたコマンドを /bin/sh で実行し、両方の出力ストリームをキャプチャします。
この第 2 ステージは、署名シークレットの回収と、CVE-2025-24293 に関する Ethiack の分析に記載された脆弱な vips 変換動作への到達が条件となります。CVE-2026-66066 のパッチはファイル読み取りのエントリポイントを閉じますが、以前に漏えいした署名シークレットに対する一般的な修正ではありません。Rails アドバイザリが推奨しているように、漏えいが疑われる場合はシークレットをローテーションしてください。
コマンド出力はランダムな /tmp/.kr2s_* ファイルに書き込まれ、同じファイル読み取りプリミティブを通じて回収された後、2 つ目の署名済みペイロードで削除されます。
読み取りは、要求された固定サイズの領域を返します。ソースがそれより短い場合は、そのプレフィックスの後にゼロバイトが続きます。後続の領域には --offset を使用します。
デフォルトのラボのバリエーションは PNG に変換するだけなので、復元はバイト単位で正確です。実際のアプリケーションではバリエーションをリサイズする場合があります。収集キーモードは水平方向のリサンプリングを避けるために 1 ピクセルの列を使用しますが、高さが小さいバリエーションではジオメトリが変わる可能性があります。その場合、クライアントは明示的に失敗します。--size を減らすか、リサイズしない表現を収集してください。
すべてのコマンドは侵入的です。永続的な Active Storage blob を作成し、処理済みバリエーションを作成する可能性があります。rce はさらに、指定されたコマンドを実行します。
マトリクススクリプトは各リリースをビルドし、脆弱なバージョンで実際の読み取りと RCE チェックを実行し、修正済みバージョンで読み取りがブロックされることを検証し、不一致があれば非ゼロを返します:
./lab/matrix.sh
./lab/matrix.sh 8.1.3
./lab/matrix.sh 8.1.3.1:PATCHED
2026-07-31 に Ruby 3.4.10、image_processing 1.14.0、ruby-vips 2.3.0、libvips 8.16.1 で検証済み:
修正済みの行では、事前に提供された署名シークレットを使った RCE ペイロードを意図的に実行しません。マトリクスは CVE のファイル読み取り境界をテストします。
research/craft.py は、ローダー研究用の候補ペイロードを再生成し、h5py と numpy を必要とします。これはリリース版 PoC からはインポートされません。lab/probe/probe.sh は、ラボイメージ内の ruby-vips を使ってこれらの候補を検査します:
uv run --script research/craft.py all -o /tmp/kr2s-payloads
docker run --rm \
-v "$PWD/lab/probe:/probe:ro" \
-v /tmp/kr2s-payloads:/payloads:ro \
--entrypoint /probe/probe.sh kr2s-rails:8.1.3
依存関係のないユニットテストを実行するには:
python3 -m unittest discover -s tests -v
MAT/HDF5 ペイロードと依存関係のないクライアントは、ローカルテストを通じて開発されました。公開 PoC が登場した後、このリリースには Zer0SumGam3 の PoC で文書化されたバリエーションキー収集と組み込みの Active Storage ルートが組み込まれました。ここでの Python 実装はオリジナルであり、標準ライブラリのみを使用しています。
初期のリビジョンでは、Puma::MiniSSL::Context と ActiveSupport プロキシを使用した署名付き Marshal ペイロードでも RCE が可能であることを実証していました。これは Behrad Taher の公開ガジェット研究 に基づいています。その方法はターゲット側のヘルパーバイナリを回避しましたが、Marshal 対応の署名付き ID シンクに加えて、読み込まれた Puma と ActiveSupport のガジェットクラスが必要でした。Ethiack リサーチチームが、よりシンプルな署名付き JSON バリエーション手法を文書化した KindaRails2Shell 技術解説 を公開した後、この方法は置き換えられました。この手法は標準のルートを使用し、それらの前提条件を排除します。
発見の功績は André Baptista (0xacb)、Bruno Mendes (s3np41k1r1t0)、Rafael Castilho と Ethiack リサーチチーム、RyotaK (GMO Flatt Security) に帰属します。Abdelmounaim Moulahcene (bl0rph) は、調整された開示の直前に基盤となるプリミティブを独自に公開しました。メンテナーの完全なタイムラインとクレジットについては、Rails フォレンジックリポジトリを参照してください。
所有しているシステム、または明示的にテストを許可されたシステムでのみ使用してください。作者は誤用に対する責任を負いません。この研究アーティファクトの問題を報告するには、SECURITY.md を参照してください。
MIT ライセンス の下で公開されています。
| Rails | ファイル読み取り | RCE チェック | 結果 |
|---|
| 7.2.3.1 | READ-OK | RCE-OK | 合格 |
| 7.2.3.2 | ブロック | 未実行 | 合格 |
| 8.0.5 | READ-OK | RCE-OK | 合格 |
| 8.0.5.1 | ブロック | 未実行 | 合格 |
| 8.1.3 | READ-OK | RCE-OK | 合格 |
| 8.1.3.1 | ブロック | 未実行 | 合格 |