
Laboratorio reproducible para CVE-2026-10053 (GitLab npm package-registry: recorrido de rutas -> escritura arbitraria de archivos como git). Versión vulnerable 19.2.1 frente a la parcheada 19.2.2, oráculo determinista.
Laboratorio autónomo que demuestra el path traversal del registro de paquetes npm de GitLab (CVE-2026-10053) y muestra que está corregido, usando un oráculo determinista en disco — no el estado HTTP.
Qué se demuestra y qué no. Este laboratorio demuestra la escritura arbitraria de archivos autenticada como el usuario del sistema
giten GitLab vulnerable, y que la versión parcheada la bloquea. No es un PoC independiente de ejecución remota de código — consulte Alcance. No lo cite como RCE.
| Vulnerabilidad | Path traversal CWE-22 en el registro de paquetes npm (TOCTOU: la comprobación se ejecutaba before :cache, no before :store) |
| CVSS | 8.5 Alta — AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H |
| Versiones afectadas | GitLab CE/EE 18.8 → <19.0.6, 19.1 → <19.1.4, 19.2 → <19.2.2 |
| Corregido en | 19.0.6 / 19.1.4 / 19.2.2 — commit 435cf863 "Revalidar el path traversal de la subida antes del store" |
python3, git, curl en el host../run.sh # up + provision + exploit + verify BOTH; exits 0 iff vuln writes AND patched blocks
Resultado esperado:
================ CVE-2026-10053 verification ================
INSTANCE EXPECT RESULT STATUS
gitlab-vuln (19.2.1) written written PASS
gitlab-patched (19.2.2) blocked blocked PASS
------------------------------------------------------------
PROVEN: authenticated arbitrary file write as git on 19.2.1.
NOT a standalone RCE — see README.md 'Scope' and ./run.sh rce-gate.
============================================================
Otros subcomandos:
./run.sh up # just start + wait until healthy
./run.sh rce-gate # honest exec-bit-gate demo (below)
./run.sh down # docker compose down -v
Tanto el servidor vulnerable como el parcheado responden a la publicación maliciosa con
HTTP 200 {"status":"processing"} — el archivo se escribe más tarde, en el worker de finalización. Por lo tanto, el estado
HTTP no es un oráculo. exploit/poc.py --verify-docker <container> en su lugar consulta dentro del
contenedor la presencia del archivo controlado en la ruta atravesada y compara su SHA-1:
/var/tmp/CVE_2026_10053_PROOF-1.0.0.tgz aparece (propietario git:git, nuestros bytes) → exit 0.Gitlab::PathTraversal::PathTraversalAttackError: Invalid path → exit 2.El exploit en sí no es más que la solicitud autenticada PUT …/packages/npm/:pkg; el traversal reside por completo en el cuerpo JSON name (file_name = "#{name}-#{version}.tgz", solo se comprueba que no esté vacío). Las llamadas a docker exec son verificación y configuración, nunca parte de la capacidad del atacante.
git. Los archivos quedan con permisos 0644.-<semver>.tgz (el kernel rechaza el NUL,
el salto de línea no trunca), por lo que no se puede sobrescribir ningún objetivo con nombre exacto (secrets.yml, authorized_keys,
un .rb, binarios de gitaly). El único ejecutor independiente del nombre de archivo,
custom_hooks/<hook>.d/, ejecuta cualquier nombre de archivo pero solo si es ejecutable — y esta escritura es 0644.
Sobrescribir un archivo 0755 preexistente no ayuda: el store reemplaza el inodo, restableciéndolo
a 0644. Por lo tanto, RCE necesita una primitiva separada de bit ejecutable / ejecución que este PoC no proporciona../run.sh rce-gate demuestra exactamente esto de forma honesta: planta un hook pre-receive.d mediante el
traversal, hace push, y muestra que el hook 0644 no se ejecuta. ./rce_gate_demo.sh --illustrate-gate
además establece +x mediante docker exec chmod (una acción docker-root fuera de banda, no una capacidad
del atacante) únicamente para mostrar que gitaly sí lo ejecutaría — lo que subraya que la palanca que falta es el bit de ejecución.
Consulte ANALYSIS.md para ver el análisis completo. En resumen: app/uploaders/gitlab_uploader.rb
validaba la ruta de almacenamiento solo con before :cache; en before :store, el file_name derivado del modelo
(controlado por el atacante, sin validar) se escribía tal cual. La corrección añade before :store, :protect_from_path_traversal!.
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