Sploitus

Exploit for CVE-2026-10053

githubexploit Β· 2026-08-23

Exploit Code

README57 lines
## https://sploitus.com/exploit?id=0EA9307C-5E8F-5065-ACB7-BC40C37AFBD0
# CVE-2026-10053 β€” reproducible verification lab

Self-contained lab that **proves** the GitLab npm package-registry path traversal
(CVE-2026-10053) and shows it fixed, using a deterministic on-disk oracle β€” not HTTP status.

> **What is and isn't proven.** This lab proves **authenticated arbitrary file write as the
> `git` OS user** on vulnerable GitLab, and that the patched release blocks it. It is **not** a
> standalone remote-code-execution PoC β€” see [Scope](#scope). Don't cite it as RCE.

| | |
|---|---|
| Vulnerability | CWE-22 path traversal in the npm package registry (TOCTOU: check ran `before :cache`, not `before :store`) |
| CVSS | 8.5 High β€” `AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H` |
| Affected | GitLab CE/EE 18.8 β†’ ` instead polls **inside the
container** for the controlled file at the traversed path and compares its SHA-1:

- **vulnerable** β†’ `/var/tmp/CVE_2026_10053_PROOF-1.0.0.tgz` appears (owner `git:git`, our bytes) β†’ exit 0.
- **patched** β†’ file never appears; the worker logs
  `Gitlab::PathTraversal::PathTraversalAttackError: Invalid path` β†’ exit 2.

The exploit itself is nothing but the authenticated `PUT …/packages/npm/:pkg` request; the traversal
lives entirely in the JSON body `name` (`file_name = "#{name}-#{version}.tgz"`, only blank-checked).
The `docker exec` calls are **verification and setup**, never part of the attacker's capability.

## Scope

- **Proven:** an authenticated user (Developer+ can publish packages; this lab uses a root PAT for
  setup convenience) writes attacker-controlled bytes to any `git`-writable path. Files land **0644**.
- **Not proven here:** RCE. The stored basename is forced to end `-.tgz` (NUL is rejected by
  the kernel, newline doesn't truncate), so no exact-name target (`secrets.yml`, `authorized_keys`,
  a `.rb`, gitaly binaries) can be overwritten. The only filename-agnostic executor,
  `custom_hooks/.d/`, runs any filename **but only if executable** β€” and this write is 0644.
  Overwriting a pre-existing 0755 file does **not** help: the store replaces the inode, resetting it
  to 0644. So RCE needs a **separate executable-bit / execution primitive** this PoC does not supply.

`./run.sh rce-gate` demonstrates exactly this honestly: it plants a `pre-receive.d` hook via the
traversal, pushes, and shows the 0644 hook **does not execute**. `./rce_gate_demo.sh --illustrate-gate`
additionally sets `+x` via `docker exec chmod` (an out-of-band **docker-root** action, *not* attacker
capability) purely to show gitaly *would* run it β€” underscoring that the missing lever is the exec bit.

## Root cause & fix

See [`ANALYSIS.md`](ANALYSIS.md) for the full analysis. In short: `app/uploaders/gitlab_uploader.rb`
validated the storage path only `before :cache`; at `before :store` the model-derived `file_name`
(attacker-controlled, unvalidated) was written verbatim. The fix adds `before :store,
:protect_from_path_traversal!`.

## Files

```
docker-compose.yml   vuln (19.2.1) + patched (19.2.2) GitLab CE
run.sh               one-command harness with deterministic file oracle + PASS/FAIL
provision.rb         lab setup: mint root PAT + create project (NOT part of the exploit)
exploit/poc.py       the PoC sender + --verify-docker oracle
rce_gate_demo.sh     honest exec-bit-gate demonstration
```