Gitlab Vulnerability · Research

GitLab Unauthenticated Arbitrary File Read Exposes Critical Secrets: Full Blast Radius and Incident Recovery Runbook

Cybersecurity dossier diagram illustrating the GitLab unauthenticated arbitrary file read vulnerability, detailing path traversal exploitation, key exfiltration from gitlab-secrets.json, and the impact of forged administrative session tokens.
AK

Threat intelligence editor · Updated Sep 12, 2026, 11:15 AM EDT

Critical GitLab arbitrary file read flaw leaks server secrets. Learn how to hunt logs, stop admin session forgery, and execute an incident recovery runbook.

[[image:poster]]

A maximum-severity GitLab arbitrary file read vulnerability allows unauthenticated attackers to execute directory traversal and access sensitive operating system files across self-managed Community Edition (CE) and Enterprise Edition (EE) instances. Canonical disclosures such as CVE-2023-2825 established the critical baseline for unauthenticated path traversal scoring CVSS 10.0 (Critical), while subsequent high-severity flaws like CVE-2024-5655 (CVSS 9.6, improper pipeline access control) and threat intelligence trackers including CVE-2026-85706 highlight a recurring architectural threat model. When exploitation occurs, the primary target is instance configuration data—specifically /etc/gitlab/gitlab-secrets.json and application secrets files—leaving server secrets compromised and granting adversaries cryptographic primitives for session forgery, database decryption, and continuous integration pipeline hijacking.

Standard binary package upgrades eliminate the traversal vector but do not cycle compromised keys. An instance patched with the latest release remains fully vulnerable to offline session minting and database decryption if secrets were exfiltrated during the exposure window.

Diagram source
graph TD
 A[Unauthenticated Attacker] -->|Path Traversal Payload /../| B[GitLab Workhorse / Nginx]
 B -->|Bypasses Path Validation| C[Rails Attachment Parser]
 C -->|Arbitrary File Read| D[Exfiltrates Server Secrets]
 D -->|secret_key_base| E[Admin Session Cookie Forgery]
 D -->|db_key_base| F[PostgreSQL CI/CD Decryption]
 D -->|ci_jwt_signing_key| G[Cloud OIDC Impersonation]
 E --> H[Persistent System Takeover]
 F --> H
 G --> H

Threat Context & Flaw Mechanics

The traversal vulnerability stems from improper path validation within GitLab's upload routing, specifically across Gitlab::Uploads::FindUpload and CarrierWave attachment components. GitLab organizes attachments on issues, merge requests, and avatars within hash-partitioned filesystem directories (such as /var/opt/gitlab/gitlab-rails/uploads/@hashed/...). When file requests travel through GitLab Workhorse to the Puma application server, unsanitized route parameters allow URL-encoded directory traversal sequences (..%2F or %2e%2e%2f) to escape directory boundaries. In nested group hierarchies, dynamic parent directory recalculation allows attackers to break out of the upload sandbox root entirely.

The web application process executes under the unprivileged git system user (UID/GID 1000). While bare-metal Omnibus deployments restrict /etc/gitlab/gitlab-secrets.json to permissions 0600 owned by root:root, traversal attacks directly extract other sensitive application-level configuration files owned by the git user, including:

  • /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml and database.yml
  • Containerized deployments (Docker/Kubernetes) where /etc/gitlab/gitlab-secrets.json shares process ownership
  • Raw Git repositories under /var/opt/gitlab/git-data/repositories
  • Environment configurations and internal UNIX domain socket paths
Release StreamVulnerable BaselinePatched Remediated ReleaseUpstream Advisory Reference
GitLab 16.11< 16.11.1116.11.11Emergency Critical Backport
GitLab 17.0< 17.0.717.0.7Critical Security Patch
GitLab 17.1< 17.1.617.1.6Critical Security Patch
GitLab 17.2< 17.2.417.2.4Critical Security Patch
GitLab 17.3< 17.3.217.3.2Critical Security Release

Telemetry indicates automated scanning engines target internet-facing instances within 4 to 12 hours of vulnerability disclosures. Threat actors spray endpoints with relative paths, probing /etc/passwd to confirm traversal before exfiltrating secrets files for offline exploitation.


The True Blast Radius: Anatomy of Compromised Server Secrets

When an attacker reads instance secrets, they obtain cryptographic material governing identity authentication, session encryption, and data-at-rest protection.

Key / AttributeCryptographic PurposeExploitation MechanismBlast Radius Impact
secret_key_baseEncrypts and signs Rails session cookies (_gitlab_session).Mints encrypted cookies with administrative user IDs via ActiveSupport::MessageEncryptor.Full administrative web access bypassing passwords, MFA, and SAML assertions.
db_key_baseAES-256 symmetric key encrypting sensitive PostgreSQL columns via attr_encrypted.Decrypts database dumps or SQL query results offline.Direct exposure of plaintext CI/CD variables, private SSH deploy keys, and cloud credentials.
otp_key_baseEncrypts TOTP two-factor authentication seeds.Decrypts stored TOTP seeds to generate valid rolling six-digit MFA codes.Complete neutralization of account-level multi-factor protection.
ci_jwt_signing_keyRSA private key signing CI_JOB_JWT tokens for pipelines.Forges valid OIDC identity tokens without triggering a pipeline.Unauthorized role assumption in AWS IAM, GCP Workload Identity, or HashiCorp Vault.
openid_connect_signing_keySigns OpenID Connect assertions for enterprise SSO.Forges JWT claims for downstream applications federated to GitLab.Lateral movement into internal wikis, dashboards, and enterprise tooling.
gitlab_shell.secret_tokenAuthorizes gitlab-shell hook execution against the internal API.Invokes internal APIs directly, bypassing branch commit rules.Silent code tampering without standard audit trail generation.

Possession of secret_key_base allows administrative session forgery:

# Construct forged administrative session payload
key = ActiveSupport::KeyGenerator.new(secret_key_base).generate_key("encrypted cookie")
encryptor = ActiveSupport::MessageEncryptor.new(key, cipher: 'aes-256-gcm')
forged_cookie = encryptor.encrypt_and_sign({ "warden.user.user.key" => [[1], "$2a$10$forged..."], "session_id" => "recon_active" })

Forensic Log Hunting Guide

To determine whether an instance was probed or exfiltrated, security teams must inspect Nginx access logs (/var/log/gitlab/nginx/gitlab_access.log) and Workhorse logs (/var/log/gitlab/gitlab-workhorse/current).

Identifying Traversal Payloads and Successful Exfiltrations

Inspect access and application logs for traversal signatures and successful HTTP 200 responses returning data:

# Hunt Nginx logs for directory traversal signatures
grep -E -i '(\.\.%2f|%2e%2e%2f|%2e%2e/|\.\.%5c|%252e%252e|gitlab-secrets|secrets\.yml|passwd)' \
 /var/log/gitlab/nginx/gitlab_access.log*

# Isolate successful data exfiltrations (HTTP 200 with response body > 500 bytes)
awk '($9 == 200 && $10 > 500 && ($7 ~ /\.\./ || $7 ~ /%2e%2e/i || $7 ~ /secrets/)) {print $1, $4, $7, $9, $10}' \
 /var/log/gitlab/nginx/gitlab_access.log

Auditing Session Forgery in Rails JSON Logs

Adversaries leveraging forged cookies execute administrative actions without preceding sign-in entries:

# Detect administrative controller access in Rails logs
grep '"controller":"Admin::' /var/log/gitlab/gitlab-rails/production_json.log | \
 jq '{time: .time, remote_ip: .remote_ip, user_id: .user_id, path: .path, status: .status}'

GitLab Secrets Rotation Runbook and Incident Recovery

Critical Warning: Modifying db_key_base in /etc/gitlab/gitlab-secrets.json breaks existing database ciphertext, causing OpenSSL::Cipher::CipherError: bad decrypt exceptions on all encrypted records. Teams must follow a staged invalidation and token reset process.

Diagram source
sequenceDiagram
 autonumber
 participant Sec as Platform Team
 participant App as GitLab Server
 participant Redis as Redis Cache
 participant DB as PostgreSQL
 Sec->>App: gitlab-ctl deploy-page up (Maintenance)
 Sec->>App: Rotate secret_key_base
 Sec->>Redis: Flush session cache
 Sec->>App: gitlab-ctl reconfigure && gitlab-ctl restart
 Sec->>DB: gitlab-rake gitlab:doctor:secrets
 Sec->>DB: Reset and re-encrypt system tokens
 Sec->>App: gitlab-ctl deploy-page down

Phase 1: Quarantine and Invalidate Forged Sessions

  1. Enable the maintenance page and back up configurations:
sudo gitlab-ctl deploy-page up
sudo cp /etc/gitlab/gitlab-secrets.json /etc/gitlab/gitlab-secrets.json.bak
sudo gitlab-rake gitlab:doctor:secrets VERBOSE=1
  1. Generate a 128-character hex string and replace secret_key_base in /etc/gitlab/gitlab-secrets.json:
NEW_KEY=$(openssl rand -hex 64)
# Replace "secret_key_base" in gitlab-secrets.json with $NEW_KEY
  1. Purge session keys in Redis to invalidate active adversary sessions:
sudo gitlab-rake cache:clear
sudo gitlab-redis-cli -s /var/opt/gitlab/redis/redis.socket \
EVAL "for i, name in ipairs(redis.call('KEYS', 'session:gitlab:*')) do redis.call('DEL', name) end" 0
sudo gitlab-ctl reconfigure && sudo gitlab-ctl restart

Phase 2: Mitigate Database Encryption and CI/CD Compromise

  1. Reset encrypted internal tokens across system models:
sudo gitlab-rake gitlab:doctor:reset_encrypted_tokens MODEL_NAMES="Project,Group,User,ApplicationSetting"
  1. Rotate external cloud credentials:
  • Revoke AWS IAM keys, GCP service account tokens, and database passwords stored in CI/CD variables.
  • Re-enter updated values via the GitLab web interface or REST API to write newly encrypted records to PostgreSQL.
  1. Cycle runner authentication tokens via the API:
curl --request POST --header "PRIVATE-TOKEN: &lt;ADMIN_PAT>" \
"https://gitlab.example.com/api/v4/runners/&lt;RUNNER_ID>/reset_authentication_token"
  1. Restore production traffic:
sudo gitlab-rake gitlab:check SANITIZE=true
sudo gitlab-ctl deploy-page down

Perimeter Defenses and Remediation Workflow

When immediate maintenance downtime is not feasible, apply perimeter filter rules at the Web Application Firewall (WAF) or reverse proxy layer.

ModSecurity / OWASP Core Rule Set

SecRule REQUEST_URI_RAW "@rx (?i)(?:\.\.(?:\/|%2f|\\|%5c)|%2e%2e(?:\/|%2f|\\|%5c)|%252e%252e)" \
 "id:1000001,phase:1,deny,status:403,log,msg:'GitLab Traversal Blocked'"

SecRule REQUEST_URI "@rx (?i)(?:gitlab-secrets\.json|secrets\.yml|database\.yml|\/etc\/passwd)" \
 "id:1000002,phase:1,deny,status:403,log,msg:'GitLab Configuration File Read Blocked'"

Fronting Nginx Proxy Filtering

if ($request_uri ~* "(\.\.%2f|%2e%2e%2f|%2e%2e/|\.\.%5c|gitlab-secrets|secrets\.yml)") {
 return 403 "Forbidden: Exploit Signature Detected";
}

Official Binary Package Upgrade

Install official distribution point releases to permanently remediate the underlying file handling vulnerability:

# Debian / Ubuntu
sudo apt-get update && sudo apt-get install gitlab-ee=17.3.2-ee.0

# RHEL / AlmaLinux
sudo dnf check-update && sudo dnf install gitlab-ee-17.3.2-ee.0.el9

Upgrading binaries patches the path traversal flaw, but true incident closure requires completing secrets rotation and verifying application logs for indicators of pre-patch data compromise.