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)

RankVulnerabilidadImpactoPrevalencia
A01Control de Acceso RotoAltoMuy Común
A02Fallos CriptográficosAltoComún
A03InyecciónCríticoComún
A04Diseño InseguroAltoComún
A05Configuración de Seguridad IncorrectaMedioMuy Común
A06Componentes Vulnerables y DesactualizadosAltoComún
A07Fallos de Identificación y AutenticaciónAltoComún
A08Fallos de Integridad de Software y DatosAltoMenos Común
A09Fallos de Registro y Monitoreo de SeguridadMedioComún
A10Falsificación de Solicitudes del Lado del ServidorMedioMenos 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:

  1. Intentar acceder a URLs restringidas directamente
  2. Modificar parámetros de URL (IDs, nombres de usuario)
  3. Probar diferentes roles y permisos de usuario
  4. Verificar controles de acceso a nivel de función faltantes
  5. 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:

  1. Inspeccionar tráfico de red para datos sensibles sin cifrar
  2. Verificar almacenamiento del navegador (localStorage, sessionStorage, cookies)
  3. Revisar mensajes de error para divulgación de información
  4. Probar mecanismos de almacenamiento de contraseñas
  5. 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

HerramientaCategoríaMejor ParaCosto
OWASP ZAPTesting DinámicoEscaneo automatizado de vulnerabilidadesGratis
Burp SuiteProxy/ScannerTesting manual de seguridadGratis/Pago
NiktoWeb ScannerEscaneo de vulnerabilidades de servidorGratis
SQLMapInyecciónDetección de inyección SQLGratis
NmapNetwork ScannerDescubrimiento de puertos y serviciosGratis
SnykEscaneo de DependenciasDetección de componentes vulnerablesGratis/Pago
SonarQubeAnálisis EstáticoCalidad y seguridad de códigoGratis/Pago
AcunetixWeb ScannerEscaneo completo de vulnerabilidades webPago
NessusVulnerability ScannerEscaneo de infraestructuraPago

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.