El testing de seguridad es un aspecto crítico del aseguramiento de calidad que garantiza que las aplicaciones estén protegidas contra ataques maliciosos y accesos no autorizados. El OWASP (como se discute en Penetration Testing Basics for QA Testers) (Open Web Application Security Project) Top 10 representa los riesgos de seguridad más críticos para aplicaciones web, proporcionando una base para que los profesionales de QA construyan estrategias completas de testing de seguridad.
Esta guía explora cada vulnerabilidad del OWASP (como se discute en Security Testing for QA: A Practical Guide) Top 10, enfoques prácticos de testing, herramientas para detección y mejores prácticas para identificar y mitigar riesgos de seguridad en aplicaciones modernas.
Entendiendo OWASP Top 10
El OWASP Top 10 es un reporte actualizado regularmente que describe los diez riesgos de seguridad más críticos que enfrentan las aplicaciones web. Para profesionales de QA, entender estas vulnerabilidades es esencial para:
- Evaluación de Riesgos: Identificar debilidades de seguridad potenciales temprano
- Planificación de Pruebas: Construir casos de prueba de seguridad en procesos de QA
- Colaboración: Comunicar preocupaciones de seguridad con desarrolladores
- Cumplimiento: Cumplir con estándares y regulaciones de seguridad
- Protección de Usuario: Salvaguardar datos sensibles de usuario y privacidad
OWASP (como se discute en OWASP ZAP Automation: Security Scanning in CI/CD) Top 10 (Edición 2021)
Rank | Vulnerabilidad | Impacto | Prevalencia |
---|---|---|---|
A01 | Control de Acceso Roto | Alto | Muy Común |
A02 | Fallos Criptográficos | Alto | Común |
A03 | Inyección | Crítico | Común |
A04 | Diseño Inseguro | Alto | Común |
A05 | Configuración de Seguridad Incorrecta | Medio | Muy Común |
A06 | Componentes Vulnerables y Desactualizados | Alto | Común |
A07 | Fallos de Identificación y Autenticación | Alto | Común |
A08 | Fallos de Integridad de Software y Datos | Alto | Menos Común |
A09 | Fallos de Registro y Monitoreo de Seguridad | Medio | Común |
A10 | Falsificación de Solicitudes del Lado del Servidor | Medio | Menos Común |
A01: Control de Acceso Roto
El control de acceso aplica políticas que evitan que los usuarios actúen fuera de sus permisos previstos. El control de acceso roto permite a los atacantes acceder a funcionalidad o datos no autorizados.
Vulnerabilidades Comunes de Control de Acceso
Escalada de Privilegios Vertical:
# Usuario regular accediendo funcionalidad de admin
GET /admin/users HTTP/1.1
Authorization: Bearer regular_user_token
Escalada de Privilegios Horizontal:
# Usuario accediendo datos de otro usuario
GET /api/users/123/profile HTTP/1.1
# Solo debería acceder a su propio perfil (user ID 456)
Referencias Directas a Objetos Inseguras (IDOR):
// IDs de recursos predecibles
/documents/1001
/documents/1002 // ¿Puede el usuario acceder a esto?
/documents/1003
Enfoques de Testing
Testing Manual:
- Intentar acceder a URLs restringidas directamente
- Modificar parámetros de URL (IDs, nombres de usuario)
- Probar diferentes roles y permisos de usuario
- Verificar controles de acceso a nivel de función faltantes
- Verificar restricciones POST/PUT/DELETE
Testing Automatizado:
# Ejemplo: Testing de vulnerabilidad IDOR
def test_idor_vulnerability():
# Login como usuario A
user_a_token = login("userA", "password")
user_a_id = get_user_id(user_a_token)
# Login como usuario B
user_b_token = login("userB", "password")
user_b_id = get_user_id(user_b_token)
# Intentar acceder recursos de usuario B con token de usuario A
response = requests.get(
f"/api/users/{user_b_id}/profile",
headers={"Authorization": f"Bearer {user_a_token}"}
)
# Debería retornar 403 Forbidden
assert response.status_code == 403, "¡Vulnerabilidad IDOR detectada!"
Mejores Prácticas de Prevención
- Implementar control de acceso basado en roles (RBAC)
- Denegar acceso por defecto (enfoque de lista blanca)
- Usar referencias indirectas a objetos (UUIDs en lugar de IDs secuenciales)
- Registrar fallos de control de acceso
- Limitar velocidad de solicitudes API
A02: Fallos Criptográficos
Los fallos criptográficos ocurren cuando datos sensibles no están protegidos adecuadamente mediante cifrado, llevando a exposición de contraseñas, números de tarjeta de crédito, registros de salud y otra información confidencial.
Problemas Criptográficos Comunes
Algoritmos de Cifrado Débiles:
# Inseguro - MD5 está criptográficamente roto
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()
# Seguro - Usar bcrypt o Argon2
import bcrypt
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
Transmisión de Datos Sin Cifrar:
# Inseguro - HTTP transmite datos en texto plano
http://example.com/login
# Seguro - HTTPS cifra datos en tránsito
https://example.com/login
Secretos Hardcodeados:
// ¡NUNCA hacer esto!
const API_KEY = "sk_live_abc123xyz789";
const DB_PASSWORD = "admin123";
// Usar variables de entorno
const API_KEY = process.env.API_KEY;
const DB_PASSWORD = process.env.DB_PASSWORD;
Enfoques de Testing
Testing de Configuración SSL/TLS:
# Verificar validez del certificado SSL
openssl s_client -connect example.com:443
# Probar cifrados débiles
nmap --script ssl-enum-ciphers -p 443 example.com
# Verificar soporte de versión TLS
testssl.sh https://example.com
Testing de Exposición de Datos Sensibles:
- Inspeccionar tráfico de red para datos sensibles sin cifrar
- Verificar almacenamiento del navegador (localStorage, sessionStorage, cookies)
- Revisar mensajes de error para divulgación de información
- Probar mecanismos de almacenamiento de contraseñas
- Verificar flag secure en cookies
Checklist de Testing:
testing_criptografico:
datos_en_reposo:
- Cifrado de base de datos habilitado
- Almacenamiento de archivos cifrado
- Gestión segura de claves
datos_en_transito:
- HTTPS forzado (HSTS habilitado)
- Configuración TLS fuerte (TLS 1.2+)
- Certificados SSL válidos
datos_sensibles:
- Contraseñas hasheadas con algoritmo fuerte
- Tarjetas de crédito tokenizadas
- PII cifrado
- Sin datos sensibles en URLs o logs
A03: Inyección
Los fallos de inyección ocurren cuando datos no confiables se envían a un intérprete como parte de un comando o consulta. SQL, NoSQL, comandos OS y inyección LDAP son variantes comunes.
Inyección SQL
Código Vulnerable:
# Peligroso - vulnerable a inyección SQL
username = request.POST['username']
password = request.POST['password']
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(query)
Ejemplo de Ataque:
-- Input del atacante para username: admin' OR '1'='1
-- Consulta resultante evita autenticación:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'anything'
Código Seguro:
# Seguro - usando consultas parametrizadas
query = "SELECT * FROM users WHERE username = ? AND password = ?"
cursor.execute(query, (username, password))
Testing de Inyección SQL
Payloads de Testing Manual:
-- Pruebas básicas
' OR '1'='1
'; DROP TABLE users; --
' UNION SELECT NULL, NULL, NULL--
-- Inyección ciega basada en tiempo
'; WAITFOR DELAY '00:00:05'--
-- Inyección ciega basada en booleano
' AND 1=1--
' AND 1=2--
Testing Automatizado:
# Ejemplo SQLMap
sqlmap -u "http://example.com/product?id=1" --batch --dbs
# Testing con OWASP ZAP
from zapv2 import ZAPv2
zap = ZAPv2(proxies={'http': 'http://127.0.0.1:8080'})
zap.ascan.scan(target_url)
Inyección de Comandos
Código Vulnerable:
# Peligroso - vulnerable a inyección de comandos
filename = request.GET['file']
os.system(f'cat {filename}')
Ejemplo de Ataque:
# Input del atacante: report.txt; rm -rf /
# Comando ejecutado: cat report.txt; rm -rf /
Código Seguro:
# Seguro - validación de entrada y escape
import subprocess
import shlex
filename = request.GET['file']
# Validar nombre de archivo
if not re.match(r'^[a-zA-Z0-9_\-\.]+$', filename):
raise ValueError("Nombre de archivo inválido")
# Usar subprocess con argumentos de lista
subprocess.run(['cat', filename], check=True)
Inyección NoSQL
Código Vulnerable:
// Consulta MongoDB peligrosa
const username = req.body.username;
const password = req.body.password;
db.users.find({
username: username,
password: password
});
Ejemplo de Ataque:
{
"username": {"$ne": null},
"password": {"$ne": null}
}
Código Seguro:
// Seguro - validar y sanitizar input
const username = String(req.body.username);
const password = String(req.body.password);
db.users.findOne({
username: username,
password: hashPassword(password)
});
A04: Diseño Inseguro
El diseño inseguro representa controles de seguridad faltantes o inefectivos debido a decisiones defectuosas de arquitectura y diseño. Esto es diferente de implementación insegura.
Fallos de Diseño para Probar
Limitación de Tasa Faltante:
# Probar vulnerabilidad de fuerza bruta
def test_login_rate_limiting():
for i in range(1000):
response = requests.post("/login", data={
"username": "admin",
"password": f"password{i}"
})
# Debería implementar limitación de tasa después de N intentos
if i > 5:
assert response.status_code == 429, "¡No se detectó limitación de tasa!"
Fallos de Lógica de Negocio:
# Ejemplo: Exploit de cantidad negativa
def test_negative_quantity_vulnerability():
# Agregar producto con cantidad negativa
response = requests.post("/cart/add", json={
"product_id": 123,
"quantity": -10,
"price": 100
})
# Verificar si se calcula total negativo (acreditando usuario)
cart_total = get_cart_total()
assert cart_total >= 0, "¡Exploit de cantidad negativa posible!"
Validación Insuficiente:
// Verificar reglas de negocio faltantes
test('no puede transferir más que el saldo de cuenta', async () => {
const transfer = {
from: 'account123',
to: 'account456',
amount: 999999 // Más que el saldo
};
const response = await api.post('/transfer', transfer);
expect(response.status).toBe(400);
});
A05: Configuración de Seguridad Incorrecta
La configuración de seguridad incorrecta ocurre cuando ajustes de seguridad no están definidos, implementados o mantenidos adecuadamente.
Configuraciones Incorrectas Comunes
Credenciales Por Defecto:
# Probar credenciales por defecto
common_defaults = [
("admin", "admin"),
("administrator", "password"),
("root", "root"),
("admin", "password123")
]
for username, password in common_defaults:
test_login(username, password)
Características Innecesarias Habilitadas:
# Probar métodos HTTP habilitados
OPTIONS /api/users HTTP/1.1
# Respuesta debería limitar métodos
Allow: GET, POST, DELETE # ¿PUT/PATCH deshabilitado?
Mensajes de Error Verbosos:
# Malo - expone detalles internos
try:
db.query(sql)
except Exception as e:
return f"Error de base de datos: {str(e)}" # Expone estructura de BD
# Bueno - mensaje de error genérico
try:
db.query(sql)
except Exception as e:
logger.error(f"Error de base de datos: {e}")
return "Ocurrió un error. Por favor intente nuevamente."
Listado de Directorios:
# Probar traversal de directorios
curl http://example.com/uploads/
curl http://example.com/../../../etc/passwd
Testing de Headers de Seguridad
def test_security_headers():
response = requests.get("https://example.com")
headers = response.headers
# Headers de seguridad requeridos
assert 'X-Content-Type-Options' in headers
assert headers['X-Content-Type-Options'] == 'nosniff'
assert 'X-Frame-Options' in headers
assert headers['X-Frame-Options'] in ['DENY', 'SAMEORIGIN']
assert 'Strict-Transport-Security' in headers
assert 'Content-Security-Policy' in headers
# No debería exponer información del servidor
assert 'Server' not in headers or 'nginx' not in headers.get('Server', '').lower()
A06: Componentes Vulnerables y Desactualizados
Usar componentes con vulnerabilidades conocidas (bibliotecas, frameworks, software) puede comprometer la seguridad de la aplicación.
Escaneo de Dependencias
NPM Audit:
# Verificar paquetes npm vulnerables
npm audit
# Corregir vulnerabilidades automáticamente
npm audit fix
# Generar reporte detallado
npm audit --json > audit-report.json
Requisitos Python:
# Verificar dependencias Python
pip install safety
safety check
# Escanear archivo requirements
safety check -r requirements.txt
Dependency-Check (OWASP):
# Escanear proyectos Java/JavaScript/Python
dependency-check --project "MyApp" --scan ./ --format HTML
Proceso de Testing
testing_dependencias:
escaneo_automatizado:
- npm_audit: "Diario en CI/CD"
- snyk: "Escaneo completo semanal"
- dependabot: "PR automatizado para actualizaciones"
revision_manual:
- base_datos_cve: "Verificar CVE para componentes críticos"
- verificacion_version: "Asegurar versiones estables últimas"
- verificacion_eol: "Identificar componentes fin-de-vida"
remediacion:
- actualizar_dependencias: "Aplicar parches de seguridad"
- eliminar_no_usados: "Eliminar bibliotecas no usadas"
- buscar_alternativas: "Reemplazar componentes vulnerables"
A07: Fallos de Identificación y Autenticación
Los mecanismos de autenticación a menudo se implementan incorrectamente, permitiendo a atacantes comprometer contraseñas, claves o tokens de sesión.
Testing de Autenticación
Credential Stuffing:
# Probar mecanismo de bloqueo de cuenta
def test_account_lockout():
failed_attempts = 0
for i in range(10):
response = login("user@example.com", f"wrong_password_{i}")
if response.status_code == 401:
failed_attempts += 1
# Cuenta debería bloquearse después de N intentos
if failed_attempts >= 5:
assert "account locked" in response.text.lower()
Gestión de Sesión:
def test_session_fixation():
# Obtener sesión antes de login
response1 = requests.get("https://example.com")
session_before = response1.cookies.get('sessionid')
# Login
response2 = requests.post("https://example.com/login",
cookies={'sessionid': session_before},
data={"username": "user", "password": "pass"})
# Sesión debería cambiar después de autenticación
session_after = response2.cookies.get('sessionid')
assert session_before != session_after, "¡Vulnerabilidad de fijación de sesión!"
Política de Contraseña Débil:
weak_passwords = [
"123456",
"password",
"admin",
"user123",
"qwerty"
]
for pwd in weak_passwords:
response = register_user("test@example.com", pwd)
assert response.status_code == 400, f"Contraseña débil aceptada: {pwd}"
Testing de Autenticación Multi-Factor
def test_mfa_bypass():
# Login con credenciales válidas
response = login("user@example.com", "ValidPassword123!")
# Verificar si MFA está forzado
assert "mfa_required" in response.json() or response.status_code == 401
# Intentar acceder recurso protegido sin MFA
response = requests.get("/api/sensitive-data",
headers={"Authorization": f"Bearer {partial_token}"})
# Debería denegarse sin completar MFA
assert response.status_code == 403
A08: Fallos de Integridad de Software y Datos
Esta categoría se enfoca en código e infraestructura que no protege contra violaciones de integridad, como pipelines CI/CD inseguros o actualizaciones automáticas sin verificación.
Testing de Problemas de Integridad
Actualizaciones Sin Firmar:
def test_update_signature_verification():
# Descargar paquete de actualización
update_package = download_update("https://cdn.example.com/update.zip")
# Verificar firma digital
signature = download_signature("https://cdn.example.com/update.zip.sig")
# Debería verificar antes de instalación
assert verify_signature(update_package, signature), "¡Actualización no firmada!"
Vulnerabilidades de Deserialización:
# Probar deserialización insegura
import pickle
import base64
# Payload malicioso
malicious_data = base64.b64encode(pickle.dumps(malicious_object))
response = requests.post("/api/process",
data={"data": malicious_data})
# Debería rechazar deserialización no confiable
assert response.status_code == 400
Seguridad del Pipeline CI/CD:
checklist_seguridad_ci_cd:
integridad_pipeline:
- "✓ Commits firmados requeridos"
- "✓ Ramas protegidas forzadas"
- "✓ Code review obligatorio"
- "✓ Escaneos de seguridad en pipeline"
integridad_artefactos:
- "✓ Artefactos de build firmados"
- "✓ Verificación de checksum"
- "✓ Rastreo de procedencia"
- "✓ Almacenamiento inmutable de artefactos"
A09: Fallos de Registro y Monitoreo de Seguridad
El registro y monitoreo insuficiente permite a atacantes mantener persistencia, pivotar a otros sistemas y manipular datos sin detección.
Testing de Requisitos de Registro
Eventos Críticos a Registrar:
critical_events = [
"intentos_login",
"autenticacion_fallida",
"fallos_control_acceso",
"fallos_validacion_entrada",
"intentos_escalada_privilegios",
"operaciones_admin",
"modificaciones_datos",
"fallos_criptograficos"
]
def test_logging_coverage():
for event_type in critical_events:
trigger_event(event_type)
# Verificar si evento está registrado
logs = get_application_logs()
assert any(event_type in log for log in logs),
f"Evento no registrado: {event_type}"
Verificación de Contenido de Log:
def test_log_content_quality():
# Disparar actividad sospechosa
failed_login("admin", "wrong_password", ip="192.168.1.100")
# Recuperar entrada de log
log_entry = get_latest_log()
# Verificar que información esencial está capturada
assert "username" in log_entry # Quién
assert "ip_address" in log_entry # Dónde
assert "timestamp" in log_entry # Cuándo
assert "action" in log_entry # Qué
assert "result" in log_entry # Resultado
# Asegurar que datos sensibles no están registrados
assert "password" not in log_entry
A10: Falsificación de Solicitudes del Lado del Servidor (SSRF)
Los fallos SSRF ocurren cuando una aplicación web obtiene un recurso remoto sin validar la URL proporcionada por el usuario, permitiendo a atacantes coaccionar a la aplicación para enviar solicitudes a destinos inesperados.
Testing SSRF
Detección SSRF Básica:
def test_ssrf_vulnerability():
# Intentar acceder recursos internos
internal_urls = [
"http://localhost/admin",
"http://127.0.0.1:8080/status",
"http://169.254.169.254/latest/meta-data/", # metadata AWS
"http://internal-service:3000/api"
]
for url in internal_urls:
response = requests.post("/api/fetch", json={"url": url})
# Debería bloquear URLs internas
assert response.status_code in [400, 403], f"SSRF posible: {url}"
Acceso a Metadata en la Nube:
# Probar acceso a metadata AWS (objetivo SSRF común)
def test_cloud_metadata_ssrf():
aws_metadata_url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
response = requests.post("/api/fetch-url",
json={"url": aws_metadata_url})
# No debería exponer credenciales de nube
assert "AWS" not in response.text
assert "AccessKeyId" not in response.text
Herramientas de Testing de Seguridad
Herramienta | Categoría | Mejor Para | Costo |
---|---|---|---|
OWASP ZAP | Testing Dinámico | Escaneo automatizado de vulnerabilidades | Gratis |
Burp Suite | Proxy/Scanner | Testing manual de seguridad | Gratis/Pago |
Nikto | Web Scanner | Escaneo de vulnerabilidades de servidor | Gratis |
SQLMap | Inyección | Detección de inyección SQL | Gratis |
Nmap | Network Scanner | Descubrimiento de puertos y servicios | Gratis |
Snyk | Escaneo de Dependencias | Detección de componentes vulnerables | Gratis/Pago |
SonarQube | Análisis Estático | Calidad y seguridad de código | Gratis/Pago |
Acunetix | Web Scanner | Escaneo completo de vulnerabilidades web | Pago |
Nessus | Vulnerability Scanner | Escaneo de infraestructura | Pago |
Ejemplo de Integración de Herramientas
# Pipeline de Seguridad CI/CD
security_pipeline:
pre_commit:
- git_secrets: "Escanear secretos hardcodeados"
- pre_commit_hooks: "Security linting"
build:
- sonarqube: "Análisis SAST"
- dependency_check: "Componentes vulnerables"
- container_scan: "Vulnerabilidades de imagen Docker"
test:
- owasp_zap: "Escaneo DAST"
- security_regression_tests: "Tests de seguridad automatizados"
pre_produccion:
- penetration_test: "Evaluación manual de seguridad"
- security_review: "Revisión de arquitectura"
Mejores Prácticas para Testing de Seguridad QA
1. Integrar Seguridad Temprano (Shift Left)
- Incluir requisitos de seguridad en historias de usuario
- Realizar modelado de amenazas durante fase de diseño
- Conducir revisiones de código de seguridad
- Ejecutar escaneos de seguridad automatizados en CI/CD
2. Mantener Casos de Prueba de Seguridad
# Ejemplo: Caso de prueba de seguridad en Gherkin
Feature: Seguridad de Autenticación
Scenario: Bloqueo de cuenta después de intentos fallidos
Given una cuenta de usuario registrada
When intento login con contraseña incorrecta 5 veces
Then la cuenta debería bloquearse por 30 minutos
And debería recibir un email de notificación de bloqueo
Scenario: Timeout de sesión
Given estoy logueado
When permanezco inactivo por 30 minutos
Then mi sesión debería expirar
And debería ser redirigido a página de login
3. Checklist de Testing de Seguridad
## Checklist de Seguridad Pre-Release
### Autenticación y Autorización
- [ ] Complejidad de contraseña forzada
- [ ] Bloqueo de cuenta implementado
- [ ] MFA disponible para acciones sensibles
- [ ] Timeout de sesión configurado
- [ ] Control de acceso adecuadamente forzado
### Protección de Datos
- [ ] Datos sensibles cifrados en reposo
- [ ] HTTPS forzado (HSTS habilitado)
- [ ] Cookies seguras configuradas
- [ ] Sin datos sensibles en logs/URLs
### Validación de Entrada
- [ ] Prevención de inyección SQL verificada
- [ ] Protección XSS implementada
- [ ] Tokens CSRF presentes
- [ ] Restricciones de carga de archivos forzadas
### Configuración
- [ ] Credenciales por defecto cambiadas
- [ ] Características innecesarias deshabilitadas
- [ ] Headers de seguridad configurados
- [ ] Manejo de errores no filtra info
### Dependencias
- [ ] Todos componentes actualizados
- [ ] Sin vulnerabilidades conocidas
- [ ] Bibliotecas no usadas eliminadas
- [ ] Cumplimiento de licencias verificado
4. Monitoreo Continuo
- Configurar alertas de seguridad en producción
- Monitorear patrones inusuales
- Rastrear fallos de autenticación
- Revisar logs de seguridad regularmente
5. Colaborar con Equipo de Seguridad
- Participar en sesiones de modelado de amenazas
- Compartir hallazgos de QA con equipo de seguridad
- Aprender de resultados de pruebas de penetración
- Mantenerse actualizado sobre amenazas emergentes
Conclusión
El testing de seguridad es una responsabilidad esencial para profesionales de QA modernos. Entender el OWASP Top 10 proporciona una base sólida para identificar y prevenir vulnerabilidades críticas. Al integrar testing de seguridad a lo largo del ciclo de vida del desarrollo, usar herramientas apropiadas y seguir mejores prácticas, los equipos de QA pueden reducir significativamente riesgos de seguridad y proteger tanto a usuarios como organizaciones.
Puntos clave:
- Entender cada categoría de vulnerabilidad OWASP Top 10 y enfoque de testing
- Integrar escaneo de seguridad automatizado en pipelines CI/CD
- Mantener casos de prueba de seguridad y checklists comprensivos
- Usar combinación de técnicas de testing manual y automatizado
- Mantenerse actualizado sobre nuevas vulnerabilidades y vectores de ataque
- Colaborar estrechamente con equipos de desarrollo y seguridad
Recuerda que la seguridad no es una actividad única sino un proceso continuo que requiere vigilancia, aprendizaje continuo y testing proactivo a lo largo del ciclo de vida de la aplicación.