// 01
Resumen
Unholy Union es un reto de categoría Web de dificultad Very Easy en HackTheBox, diseñado específicamente para enseñar y practicar SQL Injection basada en UNION.
La aplicación, un falso "Haunted Inventory Manager", expone un campo de búsqueda que
concatena directamente la entrada del usuario dentro de una consulta
SELECT * FROM inventory sin ningún tipo de
sanitización ni uso de consultas parametrizadas.
Tras determinar el número exacto de columnas de la consulta original con
UNION SELECT, se utiliza ese mismo canal para
consultar information_schema y mapear la base de datos completa:
motor (MariaDB 10.11.8), nombre de la base de datos
(halloween_invetory), tablas disponibles
(inventory, flag)
y sus columnas. Finalmente se inyecta una consulta directa sobre la tabla
flag para obtener la flag del reto.
| Fase | Técnica / Payload | Resultado |
|---|---|---|
| Inspección | Búsqueda normal en la barra de texto | Endpoint devuelve un array JSON de items |
| Conteo de columnas | ' UNION SELECT 1,2,3,4,5-- - | 5 columnas confirmadas en la consulta original |
| Fingerprint | UNION SELECT 1,2,3,4,@@version-- - | 10.11.8-MariaDB |
| Fingerprint | UNION SELECT 1,2,3,4,database()-- - | halloween_invetory |
| Fingerprint | UNION SELECT 1,2,3,4,user()-- - | user@localhost |
| Enum. BDs | information_schema.schemata | information_schema, test, halloween_invetory |
| Enum. tablas | information_schema.tables WHERE table_schema=database() | flag, inventory |
| Enum. columnas | information_schema.columns WHERE table_name='flag' | columna única: flag |
| Extracción | UNION SELECT 1,2,3,4,flag FROM flag-- - | HTB{un10n_1Nj3ct1on_15_345y_t0_l34rn_r1gh17?} |
// 02
Inspección de la app
La aplicación web es un panel de temática de Halloween llamado "Haunted Inventory Manager", con un único campo de búsqueda que permite filtrar "objetos embrujados" del inventario. El panel de la derecha muestra el resultado en bruto en formato JSON, lo que confirma que el backend devuelve directamente filas de una tabla de base de datos.
// OBSERVACIÓN CLAVE
El panel "SQL Input" exhibe literalmente la consulta ejecutada en el backend: SELECT * FROM inventory. Esto sugiere fuertemente que el término de búsqueda se concatena directamente en una cláusula WHERE sin sanitizar, abriendo la puerta a una inyección SQL clásica.
// 03
Encontrando el UNION
Antes de poder inyectar datos propios con UNION SELECT,
es necesario que el número de columnas coincida exactamente con la consulta original. Se
prueba con 5 valores enteros, cerrando la comilla de entrada y comentando el resto de la
consulta con -- -:
| ID | NAME | DESCRIPTION | ORIGIN | CREATED_AT |
|---|---|---|---|---|
| 1 | Plumbus | A highly useful multi-purpose tool. | Planet Schlooch | 2026-06-21 05.25.42 |
| 1 | 2 | 3 | 4 | 5 |
La consulta no devuelve error y la fila inyectada (1, 2, 3, 4, 5) aparece al final de la tabla de resultados — confirmando que la consulta original tiene exactamente 5 columnas. La quinta columna es la que se renderiza como texto libre, así que será el punto de inyección para extraer datos en los siguientes pasos.
La variable global @@version reemplaza al literal en la quinta columna, revelando la versión exacta del motor:
// 04
Enumeración de la Base de Datos
Se identifican el contexto de ejecución actual con las funciones nativas database() y user():
Se consulta information_schema.schemata, la tabla de metadatos que almacena el nombre de cada base de datos del servidor:
Con el nombre de la base de datos confirmado, se listan sus tablas filtrando por table_schema=database() (evita tener que escribir el nombre literal):
// HALLAZGO
Además de la tabla esperada inventory, existe una tabla llamada flag — el objetivo final del reto.
Se enumeran las columnas de cada tabla consultando information_schema.columns, filtrando por table_schema y table_name:
La tabla flag tiene una única columna, también llamada flag — todo listo para la extracción final.
// 05
Extracción de la Flag
Con la tabla y columna conocidas, basta con seleccionarla directamente en la quinta posición del UNION:
A modo de verificación, se reconstruye la consulta original alineando cada nombre de columna
real de inventory en lugar de los enteros de prueba,
confirmando que el orden y los tipos de datos son compatibles:
// LECCIÓN DEL RETO
Este challenge ilustra el flujo canónico de una UNION-Based SQL Injection: (1) cerrar el contexto de la consulta original, (2) calibrar el número de columnas con enteros de prueba, (3) usar esas posiciones para filtrar contra information_schema y mapear la base de datos completa, y (4) pivotar hacia la tabla objetivo. Ninguna herramienta automatizada (sqlmap) fue necesaria — todo se resolvió manualmente desde la barra de búsqueda.
// 06
Referencia de Comandos
- -- - comenta el resto de la consulta original (sintaxis MySQL/MariaDB)
- ' cierra la comilla simple abierta por la consulta vulnerable
- database() devuelve el nombre del esquema activo
- user() devuelve usuario y host de la conexión actual
- table_schema filtra por nombre de base de datos
- table_schema + table_name acotan la búsqueda a una tabla concreta
// 07
Glosario
UNION de SQL para combinar los resultados de una consulta maliciosa con los de la consulta original vulnerable, mostrando datos arbitrarios directamente en la respuesta de la aplicación — a diferencia de la inyección ciega, no requiere inferencia carácter por carácter.-- (doble guión seguido de un espacio) inicia un comentario de línea que descarta el resto de la consulta. Se añade un guión adicional (-- -) por convención para asegurar que exista un carácter después del espacio cuando algunos frameworks de URL recortan espacios finales.ORDER BY n incremental (hasta que falle) o probando UNION SELECT 1,2,3... con un número creciente de literales hasta que la consulta no produzca error y los valores de prueba aparezcan en la respuesta.information_schema de MySQL.