RCE via git hooks
Un git hook es un script que git ejecuta automáticamente alrededor de ciertas acciones (commit, push, merge, checkout...). Viven en .git/hooks/ dentro de cada repositorio y son locales a ese repo: no se versionan ni viajan con un clone, por eso se pasan por alto. Si el script tiene permiso de ejecución, git lo lanza; si no, lo ignora.
Los más útiles para escalar son los que corren antes de completar la acción:
pre-commit: justo antes de registrar un commit.prepare-commit-msg/post-commit: alrededor del mensaje y tras el commit.pre-push,post-checkout,post-merge: en sus respectivas operaciones.
El patrón de escalada. El hook se ejecuta con los privilegios del usuario que lanza la operación git, no del que escribió el hook. La combinación peligrosa aparece cuando:
- Un proceso privilegiado (un
cronderoot, un servicio) ejecuta periódicamente ungit commit,git pullo similar sobre un repositorio. - Nosotros podemos escribir en el directorio
.git/hooks/de ese repositorio (porque somos dueños del working tree, p. ej. un repo en nuestro$HOME).
Entonces basta con plantar el hook adecuado para ejecutar código como ese proceso privilegiado:
echo '#!/bin/bash' > /ruta/al/repo/.git/hooks/pre-commit
echo 'chmod u+s /bin/bash' >> /ruta/al/repo/.git/hooks/pre-commit
chmod +x /ruta/al/repo/.git/hooks/pre-commitEn el siguiente ciclo del proceso, el git commit de root dispara el pre-commit y /bin/bash queda con el bit SUID. Recuperar root es inmediato conservando el EUID:
bash -p # -p no descarta el EUID 0 del binario SUID
id # euid=0(root)En lugar del SUID se puede usar cualquier payload: una reverse shell, copiar una clave en /root/.ssh/authorized_keys, añadir un usuario, etc. El SUID sobre bash es el más limpio y fiable.
Cómo detectarlo. La pista suele ser un proceso periódico. A falta de pspy sirve un bucle que difumine la salida de ps:
old=$(ps -eo command); while true; do new=$(ps -eo command); diff <(echo "$old") <(echo "$new"); old=$new; doneVer un git commit/git-sync recurrente lanzado por root sobre un repo escribible es la señal. Encaja con el patrón general de cron sobre recurso escribible: lo abusable no es el cron en sí, sino que toca un recurso bajo nuestro control.
Defensa. No ejecutar operaciones git privilegiadas sobre repositorios cuyo working tree pertenezca a un usuario sin privilegios; usar core.hooksPath apuntando a un directorio controlado y de solo lectura; o git -c core.hooksPath=/dev/null para desactivar hooks en automatizaciones.
Máquinas donde se aplica
- CompiledHackTheBox
- OpenSourceHackTheBox