自己完結型ラボ。GitLab npm パッケージレジストリのパストラバーサル (CVE-2026-10053) を証明し、修正されたことを示します。HTTP ステータスではなく、決定的なオンディスクオラクルを使用します。
証明されること・されないこと。 このラボが証明するのは、脆弱な GitLab 上での
gitOS ユーザーとしての認証済み任意ファイル書き込みと、パッチ適用済みリリースがそれをブロックすることです。これはスタンドアロンのリモートコード実行 PoC ではありません — スコープ を参照してください。RCE として引用しないでください。
| 脆弱性 | npm パッケージレジストリの CWE-22 パストラバーサル (TOCTOU: チェックは before :cache で実行され、before :store では実行されなかった) |
| CVSS | 8.5 High — AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H |
| 影響を受けるバージョン | GitLab CE/EE 18.8 → <19.0.6、19.1 → <19.1.4、19.2 → <19.2.2 |
| 修正バージョン | 19.0.6 / 19.1.4 / 19.2.2 — コミット 435cf863 "保存前にアップロードのパストラバーサルを再検証" |
python3、git、curl が必要です。./run.sh # up + provision + exploit + verify BOTH; exits 0 iff vuln writes AND patched blocks
期待される結果:
================ CVE-2026-10053 verification ================
INSTANCE EXPECT RESULT STATUS
gitlab-vuln (19.2.1) written written PASS
gitlab-patched (19.2.2) blocked blocked PASS
------------------------------------------------------------
PROVEN: authenticated arbitrary file write as git on 19.2.1.
NOT a standalone RCE — see README.md 'Scope' and ./run.sh rce-gate.
============================================================
その他のサブコマンド:
./run.sh up # just start + wait until healthy
./run.sh rce-gate # honest exec-bit-gate demo (below)
./run.sh down # docker compose down -v
脆弱なサーバーもパッチ適用済みのサーバーも、悪意のあるパブリッシュに対して HTTP 200 {"status":"processing"} で応答します。ファイルが書き込まれるのは後で、ファイナライズワーカー内です。したがって HTTP ステータスはオラクルではありません。代わりに exploit/poc.py --verify-docker <container> はコンテナ内部をポーリングして、トラバーサルしたパスに制御下のファイルが存在するか確認し、その SHA-1 を比較します。
/var/tmp/CVE_2026_10053_PROOF-1.0.0.tgz が出現する (所有者 git:git、こちらが制御するバイト列) → exit 0。Gitlab::PathTraversal::PathTraversalAttackError: Invalid path をログに記録する → exit 2。エクスプロイト自体は、認証済みの PUT …/packages/npm/:pkg リクエストにすぎません。トラバーサルは完全に JSON ボディの name に存在します (file_name = "#{name}-#{version}.tgz"、ブランクチェックのみ)。docker exec 呼び出しは検証とセットアップのためのものであり、攻撃者の能力の一部ではありません。
git が書き込み可能な任意のパスに書き込めます。ファイルは 0644 で配置されます。-<semver>.tgz であることを強制されます (NUL はカーネルが拒否し、改行では切り詰められません)。したがって、正確な名前のターゲット (secrets.yml、authorized_keys、.rb ファイル、gitaly バイナリ) を上書きすることはできません。ファイル名に依存しない唯一の実行機構である custom_hooks/<hook>.d/ は、任意のファイル名を実行できますが、実行可能ビットが立っている場合のみです — そして今回の書き込みは 0644 です。既存の 0755 ファイルを上書きしても役に立ちません。ストアが inode を置き換えて 0644 にリセットするためです。したがって RCE には、この PoC が提供しない別の実行ビット / 実行プリミティブが必要です。./run.sh rce-gate はまさにこれを正直にデモします。トラバーサルを介して pre-receive.d フックを植え付け、プッシュし、0644 のフックが実行されないことを示します。./rce_gate_demo.sh --illustrate-gate はさらに docker exec chmod で +x を設定します (帯域外の docker-root アクションであり、攻撃者の能力ではありません)。これは gitaly が実行するであろうことを示すためだけのものであり、欠けているレバーが実行ビットであることを強調しています。
完全な分析については ANALYSIS.md を参照してください。要約すると、app/uploaders/gitlab_uploader.rb はストレージパスを before :cache でのみ検証していました。before :store では、モデル由来の file_name (攻撃者が制御し、未検証) がそのまま書き込まれていました。修正では before :store, :protect_from_path_traversal! が追加されています。
docker-compose.yml vuln (19.2.1) + patched (19.2.2) GitLab CE
run.sh one-command harness with deterministic file oracle + PASS/FAIL
provision.rb lab setup: mint root PAT + create project (NOT part of the exploit)
exploit/poc.py the PoC sender + --verify-docker oracle
rce_gate_demo.sh honest exec-bit-gate demonstration