# !exploitable Episode Three - Devfile Adventures

GitLab Workspaces devfile parent option bypassed via YAML parser differential, chaining path traversal in devfile registry tar decompression to arbitrary file write and RCE.

- Payout: Research
- Severity: high
- Program: GitLab
- Credited: Doyensec
- Disclosed: 2025-03-17
- CVE: CVE-2024-0402
- Write-up quality: 8/10
- Original: https://blog.doyensec.com/2025/03/18/exploitable-gitlab.html
- Library page: https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2025-03-18-exploitable-gitlab-html
- Tags: domain:web3 impact:privesc impact:rce kind:research platform:research sev:high tech:go tech:kubernetes tech:ruby technique:chained technique:parser-differential vuln:auth-bypass vuln:path-traversal

**Target:** GitLab Workspaces devfile processing / devfile registry tar decompression
**Root cause:** Ruby YAML lib evaluates !binary tag while Go yaml.v3 drops it, bypassing validate_parent; devfile decompress uses path.Join with filepath.Clean on attacker-controlled tar header.Name, allowing ../ traversal.
**Steps:**
1. Deploy malicious devfile registry with stacks/nodejs/2.2.1/archive.tar containing traversal entries (evilarc.py) and index.json
2. Commit .devfile.yaml with `!binary parent:` pointing registryUrl to malicious registry
3. Start a Workspace in the repo to trigger devfile binary pull and decompress
4. Tar entries write to arbitrary paths as git user, e.g. /var/opt/gitlab/.ssh/authorized_keys
5. SSH as git with unrestricted key, run `gitlab-rails console` to reset root password
**How found:** Reviewed validate_parent blocking parent option, hypothesized YAML parser differential between Ruby and Go; tested !binary tag behavior in both parsers; then read devfile decompress code and traced PullStackByMediaTypesFromRegistry to locate archive.tar placement in registry stacks.
**Exploitation:** Developer commits crafted .devfile.yaml with !binary parent referencing attacker registry; GitLab server pulls archive.tar and decompress writes attacker files (authorized_keys) as git user; attacker SSHes in unrestricted and resets root password via rails console.
**Escalation:** Arbitrary file write as git user -> overwrite /var/opt/gitlab/.ssh/authorized_keys -> unrestricted SSH -> gitlab-rails console -> root account takeover.
**Insight:** When a validator blocks a feature in one language but the value is re-parsed by another runtime, test YAML tag differentials like !binary that one parser evaluates and the other drops; also trace where remote archives are decompressed and check filepath.Clean on relative tar entry names.
