Entendiendo la complejidad ciclomática

Introducción

El concepto de complejidad ciclomática, introducido por Thomas J. McCabe en 1976, es un concepto importante para comprender la complejidad del código y puede juega un papel importante en la ciberseguridad.

En este artículo voy a profundizar en la complejidad ciclomática, sus efectos en la calidad del código y su conexión con la ciberseguridad. También exploraremos ejemplos de cómo la complejidad ciclomática puede impactar la seguridad del software y formas de mitigar sus riesgos.

Entendiendo la complejidad ciclomática

La complejidad ciclomática es una métrica de software que mide la complejidad de un software.

Básicamente, la complejidad ciclomática mide la cantidad de bifurcaciones y bucles en un programa. Se basa en el grafo de control de flujo del programa y se calcula contando el número de caminos lineales independientes a través del código fuente.

Una complejidad ciclomática alta indica que el código es difícil de entender, mantener y probar. Por otro lado, una complejidad ciclomática baja indica que el código es más simple y fácil de mantener.

La complejidad ciclomática y la calidad del código

Un código con una complejidad ciclomática alta es más propenso a errores y vulnerabilidades de seguridad. Esto se debe a que la complejidad ciclomática alta dificulta la comprensión del código y aumenta la probabilidad de errores.

Vamos a examinar algunos ejemplos para ilustrar mejor cómo la complejidad ciclomática puede influir en la calidad del código.

Ejemplo en Python

Mala complejidad

En este fragmento de código se realizan varias tareas con múltiples condiciones, lo que dificulta su comprensión y mantenibilidad.

multiple conditions code

Buena complejidad

Descomponiendo funciones en partes más pequeñas y específicas.

more specific parts

La complejidad ciclomática se reduce significativamente al descomponer la función original en partes más pequeñas y específicas. Cada función es responsable de una tarea particular, lo que hace que el código sea más legible y más fácil de mantener.

Ejemplo en Golang

Mala complejidad

Este código tiene múltiples condiciones anidadas, lo que aumenta significativamente su complejidad ciclomática. Esto dificulta su comprensión, prueba y mantenimiento. Cada nueva condición agrega un camino adicional a través del código, aumentando la posibilidad de errores y dificultando la detección de problemas de seguridad.

bad complexity

Buena complejidad

Tras refactorizar el código se ha reducido su complejidad ciclomática. Partes de la lógica se han extraído en funciones separadas, lo que hace que cada función sea más sencilla y tenga una sola responsabilidad.

Los switches y las funciones separadas hacen que el código sea más fácil de entender y mantener, así como de probar y depurar.

Esta estructura también facilita la identificación y corrección de posibles vulnerabilidades de seguridad, ya que cada función se centra en un aspecto específico de la lógica de evaluación de riesgos.

good complexity

La complejidad ciclomática y la ciberseguridad

La complejidad ciclomática alta puede afectar significativamente la seguridad del software. Aquí hay algunas formas en que la complejidad ciclomática alta puede afectar la seguridad del software:

  1. Errores humanos: Un código más complejo es más complicado de entender, lo que lo hace más propenso a errores durante el desarrollo y el mantenimiento. Estos errores pueden crear vulnerabilidades no deseadas.
  2. Desafíos en la revisión de código: La revisión de código es crucial para identificar vulnerabilidades. Una complejidad ciclomática alta puede dificultar las revisiones de código y hacerlas menos efectivas.
  3. Pruebas y análisis de código: Las herramientas de prueba y análisis de código automatizadas pueden tener dificultades para analizar correctamente los códigos con alta complejidad, dejando vulnerabilidades potenciales sin detectar.

La complejidad ciclomática y las fallos de seguridad

Los bugs que terminan en fallos de seguridad, a menudo, se derivan de errores de codificación. Una complejidad ciclomática alta aumenta la probabilidad de fallas de seguridad. Algunas vulnerabilidades comunes relacionadas con la complejidad ciclomática incluyen:

  • Inyecciones SQL: Los códigos complejos pueden no manejar adecuadamente las entradas de los usuarios, lo que lleva a vulnerabilidades de inyección SQL.
  • Errores de desbordamiento de búfer: La complejidad excesiva en el manejo de datos puede llevar a malas comprobaciones de límites, lo que resulta en errores de desbordamiento de búfer.
  • Fallos en la gestión de la autenticación y las sesiones: Los códigos complejos en los sistemas de autenticación pueden contener fallas críticas, comprometiendo la seguridad del usuario.
  • Problemas de control de acceso: La alta complejidad ciclomática puede llevar a errores en la gestión de permisos y control de acceso, a menudo la fuente de escaladas de privilegios verticales.
  • Vulnerabilidades de inyección de código: La alta complejidad ciclomática puede llevar a un control de flujo complejo, lo que resulta en vulnerabilidades de inyección de código debido a un mal manejo de las entradas de los usuarios.
  • Problemas de gestión de sesiones: La alta complejidad ciclomática puede llevar a errores en la gestión de sesiones y la

Bueno, hay otras vulnerabilidades, pero estas son algunas de las más comunes relacionadas con la complejidad ciclomática.

Ejemplos de vulnerabilidades relacionadas con la complejidad ciclomática (Python)

Flujos de autenticación web

Alta complejidad ciclomática

En el siguiente ejemplo, un código de autenticación complejo puede contener vulnerabilidades de seguridad. La alta complejidad ciclomática puede dificultar la detección y corrección de estas vulnerabilidades.

high cyclomatic complexity

  • Este código tiene una alta complejidad ciclomática debido a múltiples condiciones y bifurcaciones anidadas.
  • Utiliza MD5 para la contraseña, que no es segura.
  • La validación de la contraseña se realiza después de verificar si el usuario existe y si la contraseña coincide, lo cual no es una práctica recomendada.
  • La contraseña se almacena en texto plano, lo que es inseguro.

Baja complejidad ciclomática

En este ejemplo, el código de autenticación se ha simplificado y refactorizado para reducir la complejidad ciclomática. Esto facilita su comprensión, prueba y mantenimiento, disminuyendo la probabilidad de vulnerabilidades de seguridad.

low cyclomatic complexity

  • La complejidad ciclomática se reduce significativamente debido a la eliminación de condiciones anidadas innecesarias.
  • Utiliza werkzeug.security.check_password_hash para una verificación de contraseña más segura.
  • Mejora la seguridad al no revelar si un nombre de usuario específico existe o no.

Ejecución de comandos

Alta complejidad ciclomática

Este ejemplo vemos un script que realiza operaciones basadas en la entrada del usuario, con una estructura de control compleja y una vulnerabilidad crítica:

complex control structure

  • Este código tiene una alta complejidad ciclomática debido a múltiples ramas de decisión.
  • Utiliza os.system, que es peligroso si se combina con entradas no sanitizadas, permitiendo la ejecución de comandos arbitrarios.

Baja complejidad ciclomática

Aquí hay un enfoque más seguro con una menor complejidad ciclomática.

lower cyclomatic complexity

  • La complejidad ciclomática se reduce al dividir la lógica en funciones más pequeñas y claras.
  • Utiliza subprocess.run, es más seguro que os.system, y evita la ejecución insegura de comandos.
  • Separar las operaciones en diferentes funciones hace que el código sea más fácil de entender y mantener.

Ejemplos de vulnerabilidades relacionadas con la complejidad ciclomática (Golang)

Ejecución arbitraria de comandos

Este ejemplo en Go demuestra una función que procesa la entrada del usuario para ejecutar comandos del sistema. La complejidad surge de múltiples ramas condicionales y la ejecución directa de comandos basada en la entrada del usuario.

Alta complejidad ciclomática

high cyclomatic complexity 2

  • La función executeCommand procesa la entrada del usuario y ejecuta directamente comandos del sistema basados en esta entrada.
  • El uso de exec.Command con entrada de usuario no sanitizada introduce un riesgo significativo de seguridad, ya que puede llevar a la ejecución arbitraria de comandos.
  • La complejidad de manejar diferentes comandos en la misma función aumenta el riesgo de errores y hace que el código sea más difícil de auditar en busca de problemas de seguridad.

Baja complejidad ciclomática

Aquí hay un enfoque más seguro con una menor complejidad ciclomática:

low cyclomatic complexity 2

  • La complejidad ciclomática se reduce significativamente al dividir la función original executeCommand en funciones más pequeñas (executeEcho y listDirectory).
  • Cada función es responsable de una tarea específica, lo que hace que el código sea más legible y más fácil de mantener.
  • Aunque el uso de exec.Command sigue planteando riesgos. Separar los comandos en funciones dedicadas permite un manejo más controlado y seguro de la entrada del usuario.

Complejidad ciclomática óptima

Los valores ideales de complejidad ciclomática varían según el contexto y el lenguaje de programación. Generalmente, un valor de complejidad ciclomática de 10 o menos es deseable para mantener un código claro y fácil de entender.

Desde mi experiencia, los valores de complejidad ciclomática se pueden categorizar en términos de seguridad de la siguiente manera:

  • 1-10: Considerado un rango ideal. El código dentro de este rango suele ser fácil de entender y mantener. Las pruebas son más sencillas, y la probabilidad de introducir fallos de seguridad es menor.
  • 10-20: Indica una complejidad moderada. Aunque no es ideal, es manejable. Las pruebas rigurosas y las revisiones de código son importantes para garantizar la calidad y la seguridad.
  • 20-40: Considerado un alto nivel de complejidad. El código en este rango puede ser difícil de entender y probar adecuadamente, aumentando el riesgo de errores y vulnerabilidades de seguridad.
  • > 40: Extremadamente complejo y debe evitarse. El código es difícil de entender y probar, aumentando significativamente el riesgo de errores y vulnerabilidades de seguridad.

Estos valores no deben tomarse como reglas estrictas, sino como pautas generales. La complejidad ciclomática debe evaluarse en el contexto específico del código y del proyecto.

Medidas de mitigación y buenas prácticas

La complejidad ciclomática a menudo no se considera una métrica para el desarrollo seguro de software. Sin embargo, es una métrica importante que puede afectar significativamente la seguridad del software. Como se demostró en los ejemplos anteriores, una alta complejidad ciclomática puede aumentar la probabilidad de errores y vulnerabilidades de seguridad.

Aunque es sencillo, el proceso para medir la complejidad ciclomática y su mitigación puede variar según el lenguaje de programación y las herramientas disponibles. Implementarla, sin embargo, puede ser más complejo y, en algunos casos, requerir cambios significativos en el código.

Medición de la complejidad ciclomática

La medición de la complejidad ciclomática es el primer paso para abordar este problema. Algunas herramientas pueden ayudar a medir la complejidad ciclomática del código. Algunas de estas herramientas incluyen:

  • Python: Radon es una excelente herramienta para Python. Analiza el código fuente para calcular la complejidad ciclomática, entre otras métricas como el índice de mantenibilidad. Flake8 también se puede utilizar con opciones para medir la complejidad ciclomática.
  • Java: Utilice Checkstyle con su módulo de complejidad ciclomática. Esta herramienta ayuda a adherirse a estándares de codificación específicos en Java y puede calcular la complejidad ciclomática para métodos y clases.
  • JavaScript: ESLint es una excelente opción para JavaScript. Conocido más como un linter para encontrar y corregir problemas de código, también se puede configurar para informar sobre la complejidad ciclomática.
  • C++: Cppcheck es una herramienta de análisis estático que se puede utilizar para medir la complejidad ciclomática y detectar varios tipos de errores de código.
  • C#: Visual Studio ofrece capacidades integradas para analizar la complejidad ciclomática directamente en el entorno de desarrollo para C#.
  • PHP:
    • MD (PHP Mess Detector) ayuda a encontrar problemas de codificación en PHP, incluida la complejidad ciclomática.
    • PhpMetrics proporciona un análisis detallado de la calidad del código PHP, incluida la complejidad ciclomática y otras métricas relevantes.
  • Golang:
    • Gocyclo está específicamente diseñado para calcular la complejidad ciclomática de las funciones en Go.
    • Go Report Card, aunque más amplio en sus funciones, incluye una revisión de la complejidad ciclomática como parte de su análisis de código Go.

Medidas de mitigación y mejores prácticas

Para abordar la complejidad ciclomática y sus implicaciones en la ciberseguridad, se pueden implementar las siguientes medidas y mejores prácticas:

  • Refactorización de código: Revisar y simplificar el código regularmente.
  • Pruebas rigurosas: Implementar pruebas
  • Principios de diseño de software: Aplicar principios como SOLID para crear un código más modular y mantenible.
  • Formación: Muchos desarrolladores no están familiarizados con la complejidad ciclomática y su impacto en la seguridad. La formación en este tema puede ayudar a los equipos a entender y abordar la complejidad ciclomática.
  • Revisión de código: Realizar revisiones de código regulares para identificar y corregir problemas de complejidad.
  • Herramientas de análisis estático: Utilizar herramientas de análisis estático para identificar áreas de alto riesgo.
  • Pruebas de seguridad automatizadas: Implementar pruebas de seguridad automatizadas para detectar vulnerabilidades.

El orden de prioridad de estas medidas puede variar según el contexto y las necesidades específicas del proyecto. Sin embargo, si tuviera que elegir una medida, sería refactorizar aquellas partes del código que están directamente vinculadas a las entradas de los usuarios, ya que aquí es donde se encuentran la mayoría de las vulnerabilidades.

Conclusiones

La complejidad ciclomática es una métrica algo desconocida en el desarrollo de software, pero es una métrica importante que puede afectar significativamente la seguridad del software.

Esta métrica a menudo se pasa por alto como una buena práctica en el desarrollo seguro de software, pero es una métrica importante que puede afectar significativamente la seguridad del software.

Medir la complejidad ciclomática es sencillo, y algunas herramientas pueden ayudar a medir la complejidad ciclomática del código. Sin embargo, su implementación puede ser más compleja y, en algunos casos, requerir cambios significativos en el código. Incluso en el peor de los casos, con la medición, sabremos qué partes del código son más complejas y, por lo tanto, más propensas a errores y posibles vulnerabilidades, lo que nos permitirá priorizar y centrar nuestros esfuerzos en las áreas más críticas.