Dominio 5a: Prácticas de codificación segura

Por: Artiko
csslpisc2seguridadcodificacion-seguraowaspcwe

Dominio 5a: Prácticas de codificación segura

El Dominio 5 — Secure Software Implementation pesa un 14% del examen y es donde el diseño seguro se convierte en código real. ISC2 lo divide en seis subdominios; este capítulo cubre los tres que tratan de cómo escribir código seguro:

Los subdominios 5.2 (análisis de código), 5.5 (componentes) y 5.6 (build seguro) se tratan en el capítulo 8.

💡 Tip de examen: el CSSLP no te pide arreglar sintaxis ni encontrar el bug de una función en pantalla. Te pregunta qué principio o control aplica, dónde en el ciclo se introduce el defecto y cuál es la mitigación correcta. Piensa como arquitecto de seguridad, no como quien depura una línea.

flowchart LR
    A[5.1 Prácticas de<br/>codificación segura] --> D[Código seguro]
    B[5.3 Implementar<br/>controles] --> D
    C[5.4 Abordar riesgos<br/>identificados] --> D
    D --> E[Análisis y build<br/>Cap. 8]

1. La mentalidad de la codificación defensiva

La codificación segura parte de una premisa: toda entrada es hostil hasta que se demuestre lo contrario y todo componente puede fallar. En lugar de confiar en que el usuario, la red o un sistema vecino se comporten bien, el código valida, controla y falla de forma segura.

Cuatro ideas transversales sostienen todo el dominio:

IdeaQué significa en el código
Defense in depthNunca depender de un solo control; validar en múltiples capas
Fail secureAnte un error o duda, denegar el acceso, no concederlo
Least privilegeEl proceso corre con los permisos mínimos; nada de root “por si acaso”
Economía de mecanismoCódigo simple y auditable; la complejidad esconde vulnerabilidades

💡 Tip de examen: si una pregunta describe un componente que falla y deja el sistema abierto (concede acceso al caer), la respuesta correcta casi siempre invoca fail secure / fail closed. Excepción típica: sistemas de seguridad de la vida humana (puertas de emergencia) que deben fail safe / fail open para no atrapar personas.


2. Validación de entradas

La validación de entradas es el control preventivo más importante contra la familia de inyecciones y contra la corrupción de datos. La regla base: valida todo dato que cruza un límite de confianza (trust boundary).

2.1 Allowlist vs blocklist

EnfoqueDefiniciónVeredicto
Allowlist (whitelist)Se define lo que es válido y se rechaza el restoPreferido — seguro por defecto
Blocklist (blacklist)Se define lo que es peligroso y se acepta el restoFrágil — siempre olvidas un caso

El allowlist es superior porque el conjunto de entradas válidas es finito y conocido, mientras que el conjunto de entradas maliciosas es infinito y evoluciona. Un blocklist que filtra <script> no detiene <img onerror=...>.

2.2 Qué validar

2.3 Canonicalización primero

La canonicalización es reducir una entrada a su forma más simple y estándar antes de validarla. Es un paso crítico y frecuentemente olvidado: un atacante puede codificar el mismo payload de muchas formas (%2e%2e%2f, ..%c0%af, doble URL-encoding) para evadir un filtro que solo mira la forma literal.

flowchart LR
    A[Entrada cruda] --> B[Canonicalizar<br/>decodificar, normalizar]
    B --> C{¿Coincide con<br/>allowlist?}
    C -->|Sí| D[Procesar]
    C -->|No| E[Rechazar + log]

💡 Tip de examen: el orden correcto es canonicalizar → validar → usar. Validar antes de canonicalizar es un error clásico que permite bypass de filtros mediante encoding. Relaciónalo con path traversal y con la debilidad CWE-22.

2.4 ¿Dónde validar?


3. Output encoding y sanitización

Validar la entrada no basta: el mismo dato puede ser inofensivo en un contexto y peligroso en otro. El output encoding neutraliza los datos en el momento de emitirlos hacia un intérprete (navegador, shell, motor SQL).

3.1 Encoding contextual

El encoding debe corresponder al contexto de salida. El mismo carácter < se codifica distinto según dónde se coloque:

Contexto de salidaTécnica
Cuerpo HTMLHTML entity encoding (&lt;, &gt;)
Atributo HTMLAttribute encoding + comillas
JavaScriptUnicode/JS encoding (\uXXXX)
URL / query stringPercent-encoding (URL encoding)
CSSCSS hex encoding

Usar el encoding equivocado (por ejemplo, HTML-encoding dentro de un contexto JavaScript) deja el XSS abierto.

3.2 Encoding vs sanitización vs escaping

💡 Tip de examen: ante XSS, la mejora primaria es output encoding contextual, complementada con validación de entrada y una Content Security Policy (CSP) como defensa en profundidad. Para contenido que legítimamente lleva HTML, la respuesta es sanitización con allowlist, no encoding.


4. Manejo seguro de errores y excepciones

Los errores son inevitables; cómo se manejan determina si filtran información o dejan el sistema en un estado inseguro.

4.1 Fail secure

Cuando ocurre una excepción no prevista, el estado por defecto debe ser denegar. Un bloque de autorización que concede acceso en la rama de error es una vulnerabilidad grave (CWE-636, “Not Failing Securely”).

4.2 No filtrar información en los mensajes

Los mensajes de error dirigidos al usuario deben ser genéricos. Los detalles técnicos —stack traces, consultas SQL, rutas de archivos, versiones de framework— alimentan el reconocimiento del atacante (mapea a CWE-209, Information Exposure Through an Error Message).

Mostrar al usuarioRegistrar internamente
”Ocurrió un error. ID: 8f3a2”Stack trace completo
”Credenciales inválidas”Qué campo falló y por qué
Página de error genéricaConsulta, parámetros, contexto

💡 Tip de examen: un mensaje de login que distingue “usuario inexistente” de “contraseña incorrecta” habilita account enumeration. La respuesta correcta es un mensaje genérico e idéntico para ambos casos.


5. Logging seguro

El logging es a la vez un control de detección y una posible fuente de fuga. La clave está en qué se registra y qué no.

5.1 Qué registrar

5.2 Qué NO registrar (jamás)

Nunca en logsPor qué
Contraseñas, PINs, secretosConvierte el log en un botín
Tokens de sesión / API keysPermiten secuestro de sesión
Datos de tarjeta (PAN, CVV)Viola PCI DSS
PII/PHI sin necesidadViola GDPR/HIPAA
Datos criptográficos (llaves)Rompe toda la protección

5.3 Integridad y protección del log

💡 Tip de examen: dos trampas frecuentes juntas: (1) registrar secretos/PII es fuga de datos; (2) escribir entrada de usuario sin neutralizar habilita log injection. Un buen log es completo en eventos de seguridad pero estéril en datos sensibles.


6. Gestión de sesiones, autenticación y credenciales

6.1 Gestión de sesiones

6.2 Almacenamiento seguro de credenciales

Las contraseñas nunca se almacenan en claro ni cifradas de forma reversible. Se almacena un hash con estas propiedades:

No usarUsar
MD5, SHA-1Argon2id / bcrypt / scrypt
SHA-256 “a secas” para passwordsFunción lenta con salt
Cifrado reversible (AES)Hashing unidireccional
Sal fija/compartidaSalt aleatorio por usuario

💡 Tip de examen: SHA-256 es excelente para integridad, pero inadecuado para contraseñas porque es rápido. Para credenciales, la respuesta correcta es siempre un KDF lento con salt (bcrypt/scrypt/Argon2). Distingue hashing (unidireccional, para verificar) de cifrado (reversible, para confidencialidad).

flowchart LR
    A[Contraseña] --> B[+ salt único]
    B --> C[Argon2id / bcrypt<br/>factor de trabajo alto]
    C --> D[(Hash almacenado)]
    E[Login: contraseña] --> F[Mismo salt + KDF]
    F --> G{¿Hash coincide?}
    G -->|Sí| H[Autenticado]
    G -->|No| I[Rechazar - mensaje genérico]

7. Inyecciones y parametrización

Las inyecciones ocurren cuando datos no confiables se interpretan como código/comandos por un intérprete. Son la familia de vulnerabilidades más dañina y la mitigación es transversal: separar código de datos.

Tipo de inyecciónIntérprete atacadoMitigación primaria
SQL injectionMotor de base de datosConsultas parametrizadas / prepared statements
Command injectionShell del SOEvitar shell; APIs que separan comando y args; allowlist
LDAP injectionDirectorio LDAPEscapado LDAP + validación
XPath injectionParser XML/XPathConsultas XPath parametrizadas
NoSQL injectionMotor NoSQLOperadores tipados, evitar concatenar

7.1 La regla de oro: parametrización

Las consultas parametrizadas (prepared statements) son la defensa definitiva contra SQL injection porque el motor recibe la estructura de la consulta por separado de los valores; los datos nunca se compilan como parte del comando.

-- Vulnerable: concatenación
"SELECT * FROM users WHERE user = '" + input + "'"

-- Seguro: parametrizado (el driver separa código y dato)
SELECT * FROM users WHERE user = ?

Complementos (defense in depth): stored procedures bien escritos, ORM que parametriza por defecto, least privilege en la cuenta de BD (el usuario de la app no debería poder hacer DROP), y validación de entrada como capa adicional.

💡 Tip de examen: para SQL injection, la respuesta canónica es parameterized queries / prepared statements. El escaping manual y los blocklists de comillas son mitigaciones débiles. Para command injection, lo ideal es no invocar un shell: usar APIs que reciban el ejecutable y sus argumentos como lista.


8. Concurrencia y race conditions

Cuando varios hilos o procesos acceden a un recurso compartido sin la sincronización adecuada, aparecen race conditions con consecuencias de seguridad.

8.1 TOCTOU

Time-of-Check to Time-of-Use (CWE-367) es la race condition clásica: el programa verifica una condición (p. ej. “¿el usuario puede escribir este archivo?”) y después actúa, pero entre ambos instantes el atacante cambia el recurso (reemplaza el archivo por un symlink a /etc/passwd). La ventana entre check y use es la vulnerabilidad.

sequenceDiagram
    participant App
    participant Atacante
    participant FS as Sistema de archivos
    App->>FS: 1. Check: ¿puedo usar /tmp/data?
    FS-->>App: OK, permisos válidos
    Atacante->>FS: 2. Reemplaza /tmp/data por symlink
    App->>FS: 3. Use: escribe en /tmp/data
    Note over FS: Escribe en el objetivo del symlink

8.2 Mitigaciones

💡 Tip de examen: TOCTOU = brecha temporal entre verificar y usar. La mitigación es atomicidad (o bloqueo), no “verificar dos veces”. Reconócelo cuando el escenario mencione un archivo/recurso comprobado y luego usado por ruta.


9. Manejo seguro de memoria

En lenguajes de bajo nivre como C/C++, la gestión manual de memoria es fuente de vulnerabilidades críticas de ejecución de código.

DebilidadCWEDescripción
Buffer overflowCWE-120/787Escribir más allá del búfer, sobrescribiendo memoria adyacente
Use-after-freeCWE-416Usar un puntero a memoria ya liberada
Double freeCWE-415Liberar dos veces el mismo bloque
Integer overflowCWE-190Desbordamiento que lleva a asignaciones erróneas

9.1 Defensas

💡 Tip de examen: si el escenario permite elegir tecnología para código crítico expuesto a entradas no confiables, un lenguaje memory-safe elimina categorías enteras de bugs. Las protecciones de compilador (ASLR, DEP, canaries) son mitigaciones, no eliminaciones: elevan el costo del exploit pero no cierran la debilidad de fondo.


10. OWASP Top 10 2021

El OWASP Top 10 es la referencia de concienciación sobre los riesgos más críticos en aplicaciones web. La edición 2021 introdujo tres categorías nuevas y reordenó las existentes.

IDCategoríaMitigación clave
A01Broken Access ControlDeny by default, controles del lado servidor, RBAC/ABAC, pruebas de autorización
A02Cryptographic FailuresCifrar datos sensibles en tránsito y reposo, algoritmos fuertes, gestión de llaves
A03Injection (incluye XSS)Parametrización, output encoding contextual, validación con allowlist
A04Insecure DesignThreat modeling, patrones seguros, requisitos de seguridad desde el diseño
A05Security MisconfigurationHardening, deshabilitar defaults, revisar configs, mínima superficie
A06Vulnerable and Outdated ComponentsSCA, inventario (SBOM), parcheo, eliminar dependencias sin uso
A07Identification and Authentication FailuresMFA, gestión de sesión robusta, políticas de credenciales
A08Software and Data Integrity FailuresFirmas, verificación de integridad, CI/CD confiable (deserialización insegura)
A09Security Logging and Monitoring FailuresRegistrar eventos de seguridad, alertar, monitorear
A10Server-Side Request Forgery (SSRF)Allowlist de destinos, validar URLs, segmentación de red

💡 Tip de examen: A04 Insecure Design fue la gran novedad de 2021 y liga directamente con el Dominio 4: los defectos de diseño no se arreglan codificando mejor; requieren threat modeling y patrones seguros aguas arriba. Recuerda también que XSS quedó absorbido dentro de A03 Injection y que SSRF (A10) entró por votación de la comunidad.

flowchart TD
    subgraph Nuevas_2021
        A04[A04 Insecure Design]
        A08[A08 Integrity Failures]
        A10[A10 SSRF]
    end
    A01[A01 Broken Access Control<br/>subió al #1] --> Top[OWASP Top 10 2021]
    A03[A03 Injection<br/>absorbe XSS] --> Top
    Nuevas_2021 --> Top

11. CWE Top 25

Mientras el OWASP Top 10 organiza riesgos a alto nivel, la CWE (Common Weakness Enumeration) es un catálogo taxonómico de debilidades concretas de software mantenido por MITRE. El CWE Top 25 Most Dangerous Software Weaknesses es la lista anual de las debilidades más peligrosas, calculada a partir de datos reales de CVEs (frecuencia × severidad).

Ejemplos destacados que aparecen recurrentemente en el Top 25:

CWEDebilidad
CWE-79Cross-Site Scripting (XSS)
CWE-89SQL Injection
CWE-787Out-of-bounds Write
CWE-416Use After Free
CWE-20Improper Input Validation
CWE-78OS Command Injection
CWE-22Path Traversal
CWE-352Cross-Site Request Forgery (CSRF)

💡 Tip de examen: distingue los dos catálogos: CWE = debilidad (la causa, tipo de defecto), CVE = vulnerabilidad (una instancia concreta y explotable en un producto específico, con identificador). El OWASP Top 10 agrupa riesgos; el CWE Top 25 enumera tipos de debilidad. No memorices los 25 números; entiende qué es cada catálogo y cómo se relacionan.


12. CERT Secure Coding Standards

Los CERT Secure Coding Standards (del CERT Coordination Center, Carnegie Mellon SEI) son conjuntos de reglas y recomendaciones específicas por lenguaje para escribir código sin las debilidades conocidas. Existen estándares para C, C++, Java, Android y Perl.

Adoptar un estándar de codificación segura por lenguaje da al equipo un criterio objetivo y auditable para las revisiones de código y para configurar las herramientas de análisis estático.

💡 Tip de examen: cuando la pregunta busque una fuente autoritativa de reglas de codificación por lenguaje, la respuesta es CERT Secure Coding Standards (junto con OWASP y las guías de MISRA para sistemas embebidos/seguros). Son insumo directo del subdominio 5.1 (adherir a prácticas de codificación segura).


12.5 Otras debilidades de implementación frecuentes

Además de las grandes familias, hay debilidades que aparecen a nivel de código y que ISC2 espera que reconozcas y mitigues:

DebilidadRiesgoMitigación en el código
Insecure deserializationEjecución remota al deserializar datos no confiables (OWASP A08)No deserializar datos no confiables; usar formatos de datos (JSON) sin gadgets; validar tipos e integridad (firma)
File upload inseguroSubir un ejecutable/web shellValidar tipo real (magic bytes), renombrar, almacenar fuera del webroot, límite de tamaño, escaneo antimalware
SSRFEl servidor hace peticiones a destinos internos (OWASP A10)Allowlist de destinos, resolver y validar la URL, bloquear rangos internos/metadata
CSRFAcción no deseada con la sesión de la víctimaTokens anti-CSRF, SameSite, verificar origen
Open redirectRedirigir a un dominio maliciosoAllowlist de destinos de redirección; no confiar en parámetros
Mass assignmentSobrescribir campos sensibles (rol, saldo) vía binding automáticoAllowlist de campos vinculables (DTOs explícitos)

💡 Tip de examen: la deserialización insegura es la debilidad estrella de A08 (Integrity Failures) y una vía común de RCE; la regla es no deserializar datos no confiables. Para SSRF, la defensa correcta es allowlist de destinos + validación de URL, no un blocklist de IPs.


13. Implementar controles y abordar los riesgos identificados

Los subdominios 5.3 y 5.4 conectan el código con la gestión de riesgos que viene de fases anteriores (requisitos, threat modeling).

13.1 Controles según el riesgo (5.3)

Un control de seguridad se implementa en respuesta a un riesgo identificado, no “porque sí”. La intensidad del control debe ser proporcional al riesgo y al valor del activo. Los controles se clasifican por función:

TipoFunciónEjemplo en código
PreventivoImpedir que ocurraValidación de entrada, parametrización
DetectivoDescubrir que ocurrióLogging de seguridad, alertas
CorrectivoRestaurar tras el incidenteRollback, invalidar sesiones
DisuasivoDesincentivarBanners, rate limiting visible
CompensatorioSustituto cuando el ideal no es viableWAF mientras se corrige el código

13.2 Defense in depth aplicada al código

Ningún control es infalible, así que se apilan capas independientes. Para XSS, por ejemplo: validación de entrada + output encoding contextual + CSP + cookies HttpOnly. Si una capa falla, la siguiente contiene el daño.

13.3 Abordar hallazgos y deuda de seguridad (5.4)

Cuando SAST, revisión de código o pentesting reportan hallazgos, hay que decidir el tratamiento del riesgo (igual que en gestión de riesgos):

La deuda de seguridad (security debt) es el conjunto de hallazgos conocidos y no corregidos. Debe rastrearse (en el bug tracker, con severidad y SLA de remediación), priorizarse por riesgo, y no dejarse crecer en silencio: la deuda sin gestionar es una brecha esperando a suceder.

flowchart LR
    A[Hallazgo de seguridad] --> B{¿Se puede<br/>corregir ya?}
    B -->|Sí| C[Remediar en código]
    B -->|No| D[Mitigar: control<br/>compensatorio]
    D --> E[Registrar como<br/>deuda de seguridad]
    E --> F[Priorizar por riesgo<br/>SLA de remediación]
    C --> G[Verificar / retest]
    F --> G

💡 Tip de examen: aceptar un riesgo es una decisión de negocio formal del dueño del riesgo, no del desarrollador. Un control compensatorio (WAF) es una mitigación temporal, no una corrección: no elimina la debilidad subyacente. La deuda de seguridad debe rastrearse y priorizarse por riesgo, nunca ignorarse.


Puntos clave