Con la quinta entrega de nuestra pequeña serie de artículos sobre los conceptos básicos de seguridad digital, queremos introducir las buenas prácticas que nos facilitan crear cada día software un poco más seguro.
Antes de seguir leyendo, recomiendo leer las anteriores entregas de esta serie "Hablemos de Seguridad":
- Parte 1: Introducción
- Parte 2: Medidas de seguridad
- Parte 3: Protocolos
- Parte 4: Estado del mundo software
Este conocimiento nos ayudará.
Al grano - lo que hay que hacer sí o sí ...
De aquellos que se dedican a crear software, los demás (clientes, usuarios, auditores, legislación, etc.) suelen esperar un mínimo de las siguientes actividades de seguridad en cada fase del ciclo de vida:
- Plan - Diseño seguro, Security by design
- Code - Codificación segura, Secure Coding standards
- Build - Construcción determinista y autenticación de la cadena de construcción Deterministic compilation
- Test - Pruebas de seguridad (caja blanca, caja negra, red team, blue team, intrusión, etc.), Security Testing
- Release y Deploy - Mantenimiento activo del software, garantizado y planificado con distribución securizada, Mantenimiento activo garantizado
- Operate y Monitor - Monitorización de activos, seguridad perimetral, detección y segregación de amenazas, information security operations center (SOC), Gestión de incidencias responsable, computer emergency response team (CERT)
Suena muy bien, así que vamos a empezar viendo cómo cumplir con estas buenas prácticas ya que, además, hay que integrar todas las actividades en el ciclo de vida de nuestros productos software. Este movimiento del shift left del ámbito de la seguridad se denomina también como DevSecOps que promueve una cultura y prácticas orientado a los principios de su manifesto. Pero luego veremos más de esto...
Plan - Security by design
El término Security by design se refiere a que una aplicación software fue diseñada y desarrollada aceptando un uso mal intencionado e implementado con métodos para minimizar el impacto de cualquier posible vulnerabilidad (ver Secure by design).
Los principios de security by design son (ver Security by Design Principles para más detalles):
Minimizar la superficie de ataque:
Diseñar tanto las funcionalidades de la aplicación como el stack tecnológico de tal manera que se reduzcan sus posibilidades de uso, en vez de aumentar. Es decir, que el sistema solo tenga o ofrezca aquellas funcionalidades que sean estrictamente necesarias para el usuario y para toda implementación subyacente.
Establecer valores por defecto seguros:
Un sistema debe funcionar de forma segura por defecto y dejar la posibilidad al usuario de reducir las medidas de seguridad si quiere asumir más riesgos.
Principio del privilegio mínimo:
Diseñar el sistema para que los usuarios o actores identificados puedan ejecutar la funcionalidad deseada solo y exclusivamente con los permisos necesarios.
Principio de defensa en profundidad:
Dónde un control de seguridad sería suficiente es recomendable aplicar más controles a distintos niveles o contextos. De esta manera hacen falta vulnerabilidades de varios controles para convertirse en explotable, lo que convierte el ataque en más complicado o improbable.
Fallar de forma segura:
Las aplicaciones pueden llegar a fallar de muchas maneras. La forma de fallar puede originar un problema de seguridad. Por ejemplo, el mismo contexto de seguridad puede convertirse en inapropiado en un módulo funcional después de un fallo en un módulo anterior.
No confiar en servicios de terceros:
Muchas aplicaciones dependen de aplicaciones de terceros. La integración no debe basarse en la confianza sino en los datos de respuesta de un servicio de terceros, que se debe validar como cualquier otro dato de entrada.
Separación de tareas:
En una aplicación suele haber distintos roles de usuario con distintos permisos asociados. Por ejemplo, es recomendable que un usuario con rol Administrador pueda reiniciar el sistema pero no debe poder suplantar la identidad de un usuario normal y actuar en su nombre.
Evitar seguridad por oscuridad:
Los controles de seguridad basados en oscuridad o conocimiento secreto, son controles muy frágiles. Siempre que sea la única medida de seguridad tenemos el fallo casi asegurado. En sí no es mala idea mantener cierta confidencialidad sobre aspectos de un sistema, pero nunca debe ser la única medida. Concretamente la seguridad de un producto software no debe depender de mantener secreto su código fuente. Su seguridad debe depender de muchos otros factores descritos en esta misma lista.
La seguridad debe ser simple (KISS):
La superficie de ataque está directamente relacionada con la complejidad del sistema. En el momento de diseñar y construir sistemas de software, se deben preferir siempre soluciones simples ante soluciones más complejas.
Arreglar problemas de seguridad correctamente:
En el caso de encontrar un problema de seguridad en una aplicación o sistema, es muy importante desarrollar una prueba para ello y entender el problema en su totalidad. Sobre todo en aquellos casos donde se ven afectados patrones o componentes comunes que hay que arreglar y probar en todos los contextos.
En el momento de diseñar y desarrollar un sistema se suelen emplear patrones de diseño comunes en la ingeniería de software. Si queremos diseñar un sistema con la seguridad en mente, hay que repasar estos patrones.
Para conseguir nuestro objetivo de diseñar de forma segura, nos puede ayudar la publicación Secure Design Patterns del Software Engineering Institute de la Universidad Carnegie Mellon.
Code - Secure Coding Standards
A pesar de que nuestro Software se haya diseñado de forma segura, podemos implementarlo de forma insegura. Con el fin de detectar los problemas de codificación susceptibles de provocar problemas de seguridad, existen los llamados Secure Coding Standards para distintos lenguajes de programación como Java, C, C++, Android y Perl.
Para ayudar a cumplir con estas buenas prácticas de codificación, existen herramientas de revisión estática. Estas herramientas se deben integrar tanto con nuestro entorno de desarrollo, para arreglar los posibles problemas cuanto antes, como con nuestro circuito de integración continua donde deben romper el build cuanto antes mejor.
Posibles herramientas y soluciones para un análisis continuo se enumeran en este articulo de Wikipedia llamado List of tools for static code analysis o en el proyecto OWASP.
Las herramientas más conocidas en España son seguramente SonarQube y kiuwan.
Para facilitar la programación segura también existe el enfoque de incorporar aspectos genéricos de seguridad como la gestión segura de memoria en el propio lenguaje de programación, siendo Rust y Go un ejemplo de ello.
Build - Deterministic compilation
La construcción determinista, también llamada construcción reproducible, es un proceso de compilación donde el mismo código fuente produce siempre el mismo binario.
Se crea un proceso de construcción determinista cuando queremos mitigar el riesgo de ataques a nuestra cadena de construcción, p.ej. la modificación deliberada del compilador para introducir puertas traseras en el binario (back doors).
De esta manera se puede establecer una cadena de confianza donde el código fuente se firma y la compilación determinista puede demostrar que su binario ha salido de código fuente de confianza.
Para ver el tema con más detalle recomiendo empezar con la página de reproducible-builds.org donde se puede ver también una lista muy interesante de los proyectos y equipos implicados (ver Who is involved?).
De una forma mucho más concreta, podemos empezar a dar el primer paso de autenticar nuestra cadena de construcción, creando una construcción determinista de nuestro software con herramientas como Bazel de Google o Gitian que se utiliza p.ej. para la construcción determinista de Bitcoin Core.
Si nuestra construcción se basa en Maven, podemos empezar con el Reproducible Build Maven Plugin. Existe una presentación de iniciación al respecto, ver Bit-for-bit reproducible builds with Maven.
Cada día más, nuestros desarrollos se basan en componentes y frameworks de terceros que se incorporan en nuestro binario o desplegable. Lo que es una maravilla para la productividad, se convierte en una pesadilla de seguridad. Cada dependencia puede introducir en cualquier momento una vulnerabilidad a nuestro software.
Para mitigar este peligro hay que incluir en nuestro sistema de gestión de dependencias la revisión de vulnerabilidades conocidas (CVE) de cada componente - preferentemente en cada construcción.
Aquellos dueños y comunidades de componentes de software, interesado en la seguridad de su software, mantienen listas de vulnerabilidades conocidas llamados Common Vulnerabilities and Exposure (CVE). Estos CVE se publican en listas para ser consumidas por herramientas.
Un punto de partida es el CVE del MITRE. Hay que recordar que se listan solo aquellas vulnerabilidades que hayan sido publicadas. Para más detalle ver la Parte 4 de esta serie.
Para comprobar si existen vulnerabilidades conocidas en las dependencias de nuestro software podemos integrar en nuestra herramienta de construcción (CLI, Maven, Gradle, Jenkins, Ant, etc.) herramientas como el OWASP Dependency Checker.
Existen también productos comerciales como BLACKDUCK de Synopsys o Nexus Firewall y Nexus Lifecyce de sonatype.
Test - Security Testing
La parte de security testing suele ser la medida de seguridad más extendida. Se trata de realizar pruebas específicas de seguridad, aparte de las demás pruebas de aseguramiento de de la calidad del software (unitario, integración, funcional, rendimiento, etc.).
Igual que el resto de pruebas, las de seguridad se pueden realizar con distintos enfoques como caja blanca o caja negra.
Las pruebas en modo caja blanca comprueban el funcionamiento interno (estructura y dinámica) de aplicación, contando con todo el conocimiento necesario (p.ej. código fuente, arquitectura, integraciones, etc.). Con este conocimiento se intentan elaborar casos de pruebas que rompan el sistema de seguridad de la aplicación.
Por el otro lado, las pruebas en modo caja negra se centran en examinar la funcionalidad de la aplicación sin conocimiento o análisis de su estructura interna. Los casos de prueba de este enfoque se centran en explotar la interacción con la aplicación desde el exterior (p.ej. APIs, BBDD, ficheros, protocolos, datos de entrada etc.) para romper las medidas de seguridad de la aplicación.
Las pruebas de seguridad en sí se suelen llamar también pruebas de intrusión o penetration test. Dichas pruebas se realizan en modo caja blanca o caja negra y tienen como objetivo romper las medidas de seguridad de un sistema.
Para organizar y optimizar las pruebas de seguridad de forma continua, se suelen emplear equipos específicos de atacantes o adversarios llamados Red Team y equipos de defensores llamados Blue Team.
Los miembros del Red team se ponen explícitamente en el rol de un adversario o atacante y operan de forma independiente y continuo. Los miembros del Blue team se centran en la monitorización y defensa de un sistema contra los ataques del Red Team.
En cuanto el Blue team deja de actuar en la fase de pruebas, puede formar parte de la defensa de sistemas productivos.
Si nuestro sistema en producción es especialmente crítico y en el centro de atención de los atacantes, se suelen emplear los llamados Honeypots. Los honeypots son sistemas específicos montados en producción para atraer los atacantes, analizar sus estrategias, desarrollar nuevas formas de defensa y reducir los ataques a nuestro sistema productivo real a los realmente intencionados.
Release y Deploy - Mantenimiento activo garantizado
La acción de release sirve para identificar y comunicar los cambios que se hayan introducido en un paquete de software.
Sin entrar aquí en detalle sobre los procesos de release, nos conviene conocer el concepto de versionado semántico. Esta forma de versionar, carga la etiqueta de la versión con la semántica de cambios rupturistas (major version), introducción de nuevas funcionalidades (minor version) y arreglos de funcionalidades existentes (patch version).
Suponiendo que cualquier componente de software puede contener vulnerabilidades, nuestro ciclo de release debe ser capaz de entregar y publicar los arreglos de seguridad lo más rápido posible, p.ej. como patch para la versión menor actual y a la vez como parte de la siguiente versión menor.
Es importante tomar consciencia de que los arreglos de problemas de seguridad suelen ser urgentes, incluso con ataques ejecutándose activamente, para ser distribuidos independiente de la siguiente release planificada de nuestro software.
Por lo tanto, una gestión adecuada del ciclo de vida (branching) y del versionado de nuestro software es imprescindible desde el punto de vista de seguridad.
Para más detalle os recomiendo el artículo de Antonio García Candil llamado Git - Como gestionar y cuidar nuestro código.
Cuando un paquete de software no sólo se utiliza de forma interna sino que se distribuye fuera de nuestro control (bien como software libre o licenciado), es importante para nuestros usuarios o clientes conocer nuestra planificación y compromiso con el software.
Es decir, los usuarios o clientes deben poder conocer el roadmap de nuestro software.
Respecto a la seguridad, el roadmap debe contener información acerca de cada cuánto tiempo se cuenta con actualizaciones de seguridad, especialmente importante en el caso de software propietario.
En cuanto tengamos una nueva release de nuestro software, afrontaremos el reto de distribuir la nueva release a nuestros clientes o usuarios de forma segura. Es decir, asegurarnos que nuestro software no es alterado en el camino de distribución.
La distribución segura de actualizaciones suele incluir firmas digitales de los paquetes binarios, la autenticación mutua entre fuente y receptor y la encriptación de los canales de comunicación.
Para facilitar la implementación de un sistema seguro de distribución de software se ha creado The Update Framework (TUF). La especificación se encuentra aquí.
Operate y Monitor - SOC y CERT
Un centro de operaciones de seguridad de la información (ISOC o SOC) es una instalación donde se monitorizan, evalúan y defienden los sistemas de información empresarial (sitios web, aplicaciones, bases de datos, centros de datos y servidores, redes, equipos de escritorio y otros puntos de acceso).
En un SOC, equipos especialicados de analistas, ingenieros de seguridad y expertos de comunicaciones e IT trabajan con herramientas como SIEM, sistemas de monitorización a todos los niveles, sistemas de detección y prevención de intrusión (IDS, IPS), sistemas centralizados de logs y otros.
Ver más detalle sobre el concepto de SOC aquí.
La implantación y gestión de un SOC es costoso y por lo tanto deben existir buenas razones para justificar esta inversión. Como razones para mantener un SOC se suelen encontrar:
- Necesidad de proteger datos muy sensibles.
- Consideración como infraestructura crítica (ver Centro Nacional de Protección de Infraestructuras y Ciberseguridad).
- Cumplimiento de regularización industrial (p.ej. PCI-DSS).
- Cumplimiento de regularización legal (p.ej. RD 3/2010 - Esquema Nacional de Seguridad en el ámbito de la Administración Electrónica).
La parte de monitorización de incidentes de seguridad debe estar integrado con un Equipo de Respuesta ante Emergencias Informáticas (CERT).
Un equipo CERT se encarga de gestionar los incidentes de seguridad de los sistemas de información bajo su control, por ejemplo alertado por un SOC de una empresa o a raíz de la publicación de un aviso CVE. Las acciones concretas de gestión dependen del ámbito de acción del CERT y suelen incluir:
- Ayudar al público objetivo a atenuar y prevenir incidentes graves de seguridad.
- Ayudar a proteger informaciones valiosas.
- Coordinar de forma centralizada la seguridad de la información.
- Guardar evidencias, por si hubiera que recurrir a pleitos.
- Apoyar y prestar asistencia a usuarios para recuperarse de las consecuencias de los incidentes de seguridad.
- Dirigir de forma centralizada la respuesta a los incidentes de seguridad - El objetivo es promover la confianza de que alguien controla la situación.
- Ayudar a difundir la cultura de seguridad informática y crear medios de difusión para organizaciones e individuos.
Para más detalle ver Equipo de Respuesta ante Emergencias Informáticas.
Como las amenazas de seguridad no conocen fronteras en el mundo digital, muchos CERT de todo el mundo se organizan en el FIRST (Forum of Incident Response and Security Teams).
La Seguridad es un proceso continuo
Después de entrar un poco en detalle de cada fase del ciclo de vida de nuestro software y sistemas IT, debemos recordar que todas las acciones de aumentar la seguridad, es decir mitigar riesgos, deben estar orientado a un proceso continuo.
Por lo tanto, hay que pensar siempre en la mejora continua de los procesos o herramientas de nuestro S-SDLC: "La única constante es el cambio."
De forma práctica, es recomendable incluir los aspectos y necesidades de seguridad en los requisitos funcionales de cualquier desarrollo. Igual que la cultura DevOps acerca la parte de operaciones y calidad a las prácticas y herramientas de desarrollo, el movimiento del shift left sigue con la parte de seguridad y se denomina DevSecOps que cuenta con el siguiente manifiesto:
- Leaning in over Always Saying “No”.
- Data & Security Science over Fear, Uncertainty and Doubt.
- Open Contribution & Collaboration over Security-Only Requirements.
- Consumable Security Services with APIs over Mandated Security Controls & Paperwork.
- Business Driven Security Scores over Rubber Stamp Security.
- Red & Blue Team Exploit Testing over Relying on Scans & Theoretical Vulnerabilities.
- 24x7 Proactive Security Monitoring over Reacting after being Informed of an Incident.
- Shared Threat Intelligence over Keeping Info to Ourselves.
- Compliance Operations over Clipboards & Checklists.
Para los que quieran entrar más en detalle con el movimiento DevSecOps y ponerse manos a la obra, recomiendo Awesome DevSecOps.
¿Hay algo más en el futuro respecto a seguridad?
Para ver lo que nos espera en un futuro no muy lejano, recomiendo bucear un poco por los siguientes enlaces:
- Minimizar superficie de ataque:
- Nabla Containers
- Unikernel
- Micro-kernel OS Google Fuchsia
- Autenticar comunicaciones STS:
- SPIFFIE
- Privacidad en un mundo decentralizado:
- Proxy re-encryption
- Identity-based conditional proxy re-encryption
- NuCypher
- Cifrado y computación cuántica:
- Post-quantum cryptography
- Wikipedia: Post-quantum cryptography
- Verificación de software:
- The science of deep specification
Conclusiones
Está claro que no siempre se pueden realizar todas las actividades de seguridad descritas, pero es trabajo de cualquier técnico de concienciar y dejar bien claro a los no-técnicos las limitaciones de un sistema de información para evitar futuras sorpresas.
Siempre hay que concienciar y mitigar a la vez. Poco a poco...
Con este articulo termina esta pequeña serie de introducción al mundo de la seguridad digital. ¡Espero que os haya gustado!
Para estar al día de nuevas series y posts, síguenos en Twitter.