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 --> HThreat 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.ymlanddatabase.yml- Containerized deployments (Docker/Kubernetes) where
/etc/gitlab/gitlab-secrets.jsonshares process ownership - Raw Git repositories under
/var/opt/gitlab/git-data/repositories - Environment configurations and internal UNIX domain socket paths
| Release Stream | Vulnerable Baseline | Patched Remediated Release | Upstream Advisory Reference |
|---|---|---|---|
| GitLab 16.11 | < 16.11.11 | 16.11.11 | Emergency Critical Backport |
| GitLab 17.0 | < 17.0.7 | 17.0.7 | Critical Security Patch |
| GitLab 17.1 | < 17.1.6 | 17.1.6 | Critical Security Patch |
| GitLab 17.2 | < 17.2.4 | 17.2.4 | Critical Security Patch |
| GitLab 17.3 | < 17.3.2 | 17.3.2 | Critical 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 / Attribute | Cryptographic Purpose | Exploitation Mechanism | Blast Radius Impact |
|---|---|---|---|
secret_key_base | Encrypts 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_base | AES-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_base | Encrypts 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_key | RSA 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_key | Signs 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_token | Authorizes 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_basein/etc/gitlab/gitlab-secrets.jsonbreaks existing database ciphertext, causingOpenSSL::Cipher::CipherError: bad decryptexceptions 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 downPhase 1: Quarantine and Invalidate Forged Sessions
- 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
- Generate a 128-character hex string and replace
secret_key_basein/etc/gitlab/gitlab-secrets.json:
NEW_KEY=$(openssl rand -hex 64)
# Replace "secret_key_base" in gitlab-secrets.json with $NEW_KEY
- 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
- Reset encrypted internal tokens across system models:
sudo gitlab-rake gitlab:doctor:reset_encrypted_tokens MODEL_NAMES="Project,Group,User,ApplicationSetting"
- 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.
- Cycle runner authentication tokens via the API:
curl --request POST --header "PRIVATE-TOKEN: <ADMIN_PAT>" \
"https://gitlab.example.com/api/v4/runners/<RUNNER_ID>/reset_authentication_token"
- 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.