Secure SDLC: integrar la seguridad en el ciclo de vida del software
Durante años se aprendía a escribir software y, al final del proceso, se añadía un paso de revisión de seguridad. Ese modelo, conocido informalmente como security at the end, tiene un problema que no se arregla con más horas de revisión: cuando la seguridad entra en la última fase, ya no se puede cambiar la arquitectura, ni el modelo de datos, ni las dependencias, ni los contratos de API. Lo que queda es cubrir con parches un diseño que nunca se diseñó pensando en la seguridad.
El Secure SDLC propone lo contrario: cada fase del ciclo de vida tiene actividades de seguridad, artefactos y responsables. La diferencia no está en las herramientas, sino en el momento en el que se toman las decisiones.
Las fases y su responsable de seguridad
| Fase | Actividad de seguridad | Evidencia que se produce |
|---|---|---|
| Requisitos | Qué se protege, quién es el atacante, qué datos son sensibles | Lista de amenazas y criterios de aceptación |
| Diseño | Modelado de amenazas y revisión de arquitectura | Diagrama de flujos de datos y mitigaciones |
| Implementación | Estándares de código y análisis estático | Informe de SAST sin hallazgos críticos |
| Pruebas | Pruebas de seguridad funcionales y automáticas | Evidencia de DAST y de cobertura de casos |
| Despliegue | Verificación de artefactos y configuración segura | SBOM, escaneo de imagen y configuración |
| Operación | Monitorización, respuesta a incidentes y parcheo | Registros, alertas y tiempos de corrección |
La clave es que la evidencia existe en cada fase. Una organización que solo puede mostrar el informe de la última fase no tiene un Secure SDLC, tiene un control de calidad tardío.
Fase 1: requisitos
El error más común de esta fase es escribir requisitos de seguridad como frases genéricas: el sistema debe ser seguro. Eso no se puede verificar ni implementar.
Los requisitos de seguridad deben ser específicos y comprobables, idealmente escritos junto a los criterios de aceptación de la historia de usuario.
| Requisito mal escrito | Requisito verificable |
|---|---|
| Las contraseñas deben ser seguras | Las contraseñas se almacenan con Argon2id y nunca en texto plano ni con MD5 |
| Los datos personales están protegidos | Los campos marcados como personales se cifran en reposo y no aparecen en logs |
| Solo administradores ven los reportes | Cada consulta de reportes comprueba el rol en el servidor, no en el cliente |
En esta fase conviene también aclarar los límites de confianza: qué datos entran desde el exterior, qué se considera interno y qué componentes de terceros se integran. Esa información es la base del modelado de amenazas de la fase siguiente.
Fase 2: diseño y modelado de amenazas
El modelado de amenazas responde a cuatro preguntas que se pueden aplicar en una reunión de media hora:
- ¿Sobre qué estamos trabajando?
- ¿Qué puede salir mal?
- ¿Qué se va a hacer para evitarlo?
- ¿Suficiente?
Para la segunda pregunta, la metodología STRIDE ofrece un catálogo de amenazas por categoría que evita empezar de cero:
| Categoría | Pregunta que ayuda a responder | Ejemplo en una tienda en línea |
|---|---|---|
| Spoofing (suplantación) | ¿Puede alguien hacerse pasar por otro? | Login sin verificación de identidad |
| Tampering (manipulación) | ¿Se pueden alterar los datos en tránsito o reposo? | Modificar el precio de un carrito |
| Repudiation (repudio) | ¿Podemos demostrar quién hizo cada acción? | Un cambio de precio sin registro de autor |
| Information disclosure | ¿Se puede ver lo que no le corresponde? | Endpoint que devuelve datos de otro cliente |
| Denial of service | ¿Se puede agotar el servicio? | Búsqueda sin paginación ni límites |
| Elevation of privilege | ¿Puede alguien obtener más permisos de los fijados? | Un endpoint sin control de rol |
El resultado debe quedar documentado junto al diseño, y cada amenaza con mitigación conocida se traduce en un requisito o en una tarea de implementación. Un modelo de amenazas que no genera tareas se convierte en un documento decorativo que nadie vuelve a leer.
En esta fase también se decide la superficie de dependencias: qué librerías entran, de dónde salen, qué permisos necesitan y cómo se van a actualizar. Generar el inventario de software (SBOM) desde el principio es mucho más barato que reconstruirlo en una auditoría.
Fase 3: implementación
En la fase de codificación la seguridad se apoya en dos columnas: evitar el origen del problema y detectar el error lo antes posible.
No construir el fallo
Los dos errores que más se repiten en código propio son la concatenación de entradas en consultas SQL y el acceso a archivos sin validar la ruta.
// Vulnerable: la entrada del usuario se concatena en la consulta
String sql = "SELECT * FROM usuarios WHERE email = '" + email + "'";
try (Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(sql)) {
// ...
}
Un correo con un apóstrofe ya rompe la consulta, y con una unión se puede leer información de otras tablas. La corrección no es filtrar caracteres, es usar consultas parametrizadas:
// Seguro: los valores viajan como parametros, nunca como codigo SQL
String sql = "SELECT id, nombre, email FROM usuarios WHERE email = ?";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, email);
try (ResultSet rs = statement.executeQuery()) {
// ...
}
}
El segundo patrón, la traversal de rutas, aparece en cuanto la aplicación acepta un nombre de archivo:
// Vulnerable: ../../etc/passwd escaparia del directorio permitido
String ruta = Paths.get("/var/www/uploads", nombreArchivo).toString();
return Files.readAllBytes(Path.of(ruta));
La versión segura normaliza ambas rutas y comprueba que el resultado sigue dentro del directorio permitido:
Path base = Path.of("/var/www/uploads").toAbsolutePath().normalize();
Path destino = base.resolve(nombreArchivo).normalize();
if (!destino.startsWith(base)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Ruta no permitida");
}
return Files.readAllBytes(destino);
normalize() resuelve los .. y startsWith verifica el resultado. Es una comprobación de cuatro líneas que evita una lectura arbitraria de archivos.
Detectar el error temprano
El análisis estático de aplicaciones (SAST) examina el código sin ejecutarlo. Una regla que busca credenciales en el código puede verse así:
# .semgrep/secrets.yml
rules:
- id: hardcoded-credential
languages: [java, javascript, typescript, python, php]
severity: ERROR
message: Posible credencial escrita directamente en el codigo
patterns:
- pattern-either:
- pattern: $SECRET = "..."
- pattern: password = "..."
- pattern: $APIKEY = "..."
metadata:
cwe: "CWE-798: Use of Hard-coded Credentials"
Lo interesante de esta clase de herramientas es que el conjunto de reglas es texto plano, versionable y revisable en una revisión de código. Un SAST no adivina, aplica reglas, y eso se puede discutir con el equipo.
Fase 4: pruebas de seguridad
Las pruebas funcionales verifican que el sistema haga lo que debe. Las pruebas de seguridad verifican que no haga lo que no debe, y hay que escribirlas explícitamente: no aparecen por accidente.
El caso más rentable es la matriz de autorización: por cada endpoint, comprobar quién puede invocarlo.
| Endpoint | Anónimo | Cliente | Administrador |
|---|---|---|---|
GET /api/perfil | No | Sí, solo el suyo | Sí, todos |
GET /api/facturas | No | Sí, solo las suyas | Sí, todas |
DELETE /api/facturas/:id | No | Propietario | Sí |
GET /api/admin/usuarios | No | No | Sí |
Cada celda es un caso de prueba. Esta tabla detecta la vulnerabilidad más común en APIs modernas, la referencia a objetos directos (IDOR o BOLA), que existe cuando el servidor filtra por propietario y olvida comprobar que el recurso pertenece a quien pregunta.
@Test
void unClienteNoPuedeLeerLaFacturaDeOtro() {
// Sesion del cliente 1
var sesion = clienteAutenticadoComo("cliente-1");
// La factura 900 pertenece al cliente 2
assertThrows(ResponseStatusException.class, () -> sesion.get("/api/facturas/900"));
}
Además de las pruebas escritas a mano, el análisis dinámico (DAST) examina la aplicación en ejecución. ZAP puede ejecutarse de forma continua en integración continua:
name: Security
on:
push:
branches: [main]
jobs:
dast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Arrancar la aplicacion
run: docker compose up -d --wait
- name: Escaneo dinamico con OWASP ZAP
uses: ghcr.io/zaproxy/zaproxy-action@0
with:
configFile: zaf.yaml
La diferencia con un escaneo ejecutado a mano es que el resultado acaba en el pipeline, junto al resto de evidencias de la entrega. Si el escaneo falla, la entrega se detiene.
Fase 5: despliegue
Antes de publicar hay que verificar lo que se va a publicar, no solo el repositorio. Un artefacto puede contener archivos que nunca se versionaron, pero que sí acaban dentro de la imagen o del paquete.
# Inventario de dependencias en formato CycloneDX
syft dir:. -o cyclonedx-json > sbom.json
# Secretos en todo el historial del repositorio
gitleaks git --redact --no-banner
# Vulnerabilidades conocidas en la imagen ya construida
trivy image --severity HIGH,CRITICAL mi-imagen:1.4.0
La gestión de dependencias también se configura como código. En lugar de esperar a que alguien actualice, el sistema propone las actualizaciones de forma continua:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
Sobre la imagen y el entorno, la revisión se concentra en cuatro puntos: usuario de ejecución sin privilegios, sistema de archivos de solo lectura cuando es posible, ausencia de secretos en variables de imagen y configuración de TLS y cabeceras en el servidor web.
Fase 6: operación y mantenimiento
La seguridad de un sistema que ya está en producción tiene un componente operativo que ninguna fase anterior cubre: la capacidad de detectar y corregir. La clave de rotación, la reversión de un despliegue y el análisis forense de un incidente tienen que estar escritos antes de que ocurran.
Las señales mínimas son un registro de accesos fiable, alertas sobre cambios de configuración y una ruta de escalado conocida. La rotación periódica de credenciales y la actualización de dependencias son tareas de mantenimiento, no accidentales: si no están en el calendario, no se hacen.
Frameworks de referencia
Los pasos anteriores no son una invención. Existen marcos públicos que describen conjuntos similares de prácticas:
| Marco | Enfoque |
|---|---|
| NIST SSDF (SP 800-218) | Prácticas de seguridad en cada fase del ciclo de vida |
| OWASP SAMM | Modelo de madurez para medir el nivel de la organización |
| OWASP ASVS | Requisitos de verificación concretos y comprobables |
| ISO 27001 | Control de la seguridad de la información en la organización |
Marcos como NIST SSDF y OWASP ASVS son especialmente útiles porque traducen la seguridad a listas comprobables, que es justo lo que falta cuando el proceso se apoya en la buena voluntad del equipo.
Métricas
Medir sin cuidado genera theatre. Las tres señales que mejor funcionan:
- Porcentaje de las entregas que pasan por análisis estático, dinámico y de dependencias en el pipeline.
- Tiempo medio entre la publicación de una vulnerabilidad y su corrección.
- Porcentaje de amenazas del modelo que tienen mitigación implementada y verificada.
Las tres miden el proceso, no la intención. Un equipo con un porcentaje alto de amenazas modeladas y sin mitigaciones implementadas tiene un documento, no un Secure SDLC.
Errores frecuentes
- La seguridad al final del proyecto. Cuando la revisión llega tarde, ya no se puede cambiar el diseño y solo quedan parches.
- Checklists que nadie cumple. Una lista de treinta puntos aplicada al final de cada entrega se cumple en cinco minutos. Las listas de verificación solo funcionan si se reducen a lo imprescindible y se validan de forma automática.
- Modelar amenazas sin convertir nada en trabajo. Cada amenaza necesita una mitigación, un responsable y una fecha.
- Escáneres sin contexto. Una herramienta que marca veinte hallazgos irrelevantes y nadie revisa, o un hallazgo crítico entre ellos, porque el resultado siempre es el mismo.
- Ignorar los artefactos. La imagen, el paquete y los archivos de configuración son parte del producto y también se revisan.
- Medir el estado en lugar del proceso. El número de vulnerabilidades pendientes depende del inventario, no solo del esfuerzo.
Conclusión
El Secure SDLC no es un conjunto de herramientas, es una forma de tomar decisiones. Su valor está en que cada decisión de seguridad se toma en la fase en la todavía puede cambiar el resultado:
| Momento | Decisión que se toma y que después ya no se puede corregir |
|---|---|
| Requisitos | Qué datos son sensibles y qué se protege |
| Diseño | Cómo se aíslan los componentes y dónde están los límites |
| Implementación | Si la entrada se trata como dato o como código |
| Pruebas | Si la autorización se comprueba de forma automatizada |
| Despliegue | Si el artefacto que se publica es el que se revisó |
La transición de un modelo tardío a uno integrado suele empezar con un cambio pequeño: mover el análisis de dependencias y el escaneo de secretos al pipeline, y escribir las primeras pruebas de autorización. Ninguna de las dos tareas necesita un proceso nuevo, y a partir de ahí el resto tiene dónde apoyarse.
© Arturo Juárez Monroy