test español ejemplo driven development security unit-testing testing tdd fuzzing

security - ejemplo - test driven development español



¿Cómo puede garantizar una codificación segura con Test Driven Development? (5)

Me he estado acercando a la última tendencia que es Test Driven Development (TDD). La mayor parte del desarrollo que hago es en C o C ++. Me parece que hay un conflicto muy obvio entre las prácticas comunes de TDD y las prácticas comunes de codificación segura. En el fondo, TDD le dice que no debe escribir código nuevo para algo para lo que no tiene una prueba fallida. Para mí, eso significa que no debo escribir código de seguridad a menos que tenga pruebas de unidad para ver si mi código es seguro.

Eso plantea dos cuestiones:

  1. ¿Cómo puedo escribir de manera efectiva las pruebas unitarias para probar desbordamientos de búfer, desbordamientos de pila, desbordamientos de pila, errores de índice de matriz, errores de cadena de formato, errores de cadena ANSI vs Unicode vs MBCS, un manejo seguro de cadenas (de Howard y LeBlanc "Escribiendo el Código de Seguridad" )?

  2. ¿En qué momento de la práctica estándar de TDD deberían incluirse estas pruebas, ya que gran parte de la seguridad no es funcional?

Sorprendentemente, he encontrado muy poca investigación sobre TDD y la seguridad. La mayoría de lo que me he encontrado son documentos de TDD que mencionan a un nivel muy alto que TDD "hará que su código sea más seguro".

Estoy buscando respuestas directas a los problemas anteriores, cualquier investigación relacionada con esto (ya busqué y no encontré mucho), o cualquier lugar en el que viva el gurú de TDD para poder llamar a su puerta (virtualmente) y A ver si tienen alguna buena respuesta.

¡Gracias!

EDITAR:

Ha surgido el tema de Fuzzing, que creo que es un gran enfoque para este problema (en general). Esto plantea las preguntas: ¿Encaja Fuzzing en TDD? ¿En qué parte del proceso TDD encaja el fuzzing?

La prueba de unidad parametrizada (posiblemente automatizada) también ha pasado por mi mente. Esta podría ser una manera de obtener resultados similares a fuzzing antes en el proceso de prueba. Tampoco estoy seguro de dónde encaja eso en TDD.

EDIT 2:

Gracias a todos por sus respuestas hasta ahora. En este punto, estoy extremadamente interesado en cómo podemos aprovechar las pruebas parametrizadas para servir como pseudo difusores para nuestras funciones. Pero, ¿cómo determinamos qué pruebas escribir para probar la seguridad? ¿Y cómo podemos estar seguros de que cubrimos adecuadamente el espacio de ataque?

Es un problema bien conocido en la seguridad del software que si protege contra 5 escenarios de ataque, el atacante solo buscará y utilizará un 6º ataque. Es un juego de gato y ratón muy difícil. ¿TDD nos da alguna ventaja en contra de esto?


Ha surgido el tema de Fuzzing, que creo que es un gran enfoque para este problema (en general). Esto plantea las preguntas: ¿Encaja Fuzzing en TDD? ¿En qué parte del proceso TDD encaja el fuzzing?

Creo que podría encajar bastante bien! Hay fuzzers como american fuzzy lop que pueden programarse y adaptarse a las modificaciones en el formato de E / S por sí solos. En este caso particular, podría integrarlo con Travis CI , almacenar los casos de prueba de entrada que usó y ejecutar pruebas de regresión contra esos.

Podría extender esta respuesta si tiene alguna pregunta para detalles en los comentarios.


Sí, TDD es una herramienta / técnica que puede ayudar a garantizar una codificación segura.

Pero como con todas las cosas en esta industria: asume que es una bala de plata y te dispararás en el pie.

Amenazas desconocidas

Como indicaste en la Edición 2: "proteges contra 5 escenarios de ataque, el atacante solo buscará y utilizará un 6º ataque". TDD no te protegerá de amenazas desconocidas. Por su propia naturaleza, tienes que saber qué quieres probar para poder escribir la prueba en primer lugar.

Así que supongamos que se descubre la amenaza número 6 (con suerte no debido a una violación, sino más bien internamente debido a otra herramienta / técnica que intenta encontrar posibles vectores de ataque).

TDD ayudará de la siguiente manera:

  • Se pueden escribir pruebas para verificar la amenaza.
  • Se puede implementar una solución para bloquear la amenaza, y se puede confirmar rápidamente que funciona.
  • Más importante aún, siempre que todas las demás pruebas pasen, puedes verificar rápidamente que:
    • Todas las demás medidas de seguridad todavía se comportan correctamente.
    • Todas las demás funcionalidades todavía se comportan correctamente.
  • Básicamente, TDD ayuda a permitir un tiempo de respuesta rápido desde que se descubre una amenaza hasta que una solución está disponible.
  • TDD también proporciona un alto grado de confianza de que la nueva versión se comporta correctamente.

Código comprobable

He leído que TDD a menudo se interpreta erróneamente como una metodología de prueba, cuando en realidad es más una metodología de diseño. TDD mejora el diseño de su código, haciéndolo más comprobable .

Pruebas especializadas

Una característica importante de los casos de prueba es su capacidad de correr sin efectos secundarios. Lo que significa que puede ejecutar pruebas en cualquier orden, cualquier número de veces, y nunca deben fallar. Como resultado, varios otros aspectos de un sistema se vuelven más fáciles de probar simplemente como resultado de la capacidad de prueba. Por ejemplo: Performance, Memory Utilization.

Esta prueba generalmente se implementa mediante la ejecución de comprobaciones especiales de un conjunto de pruebas completo, sin afectar directamente a la propia suite.

Un módulo de prueba de seguridad similar podría superponerse a un conjunto de pruebas y buscar problemas de seguridad conocidos, como los datos seguros que quedan en la memoria, la saturación del búfer o cualquier nuevo vector de ataque que se conozca. Una superposición de este tipo tendría un grado de confianza, ya que se ha comprobado todas las funciones conocidas del sistema.

Diseño mejorado

Una de las mejoras de diseño clave que surgen como efecto secundario de TDD son las dependencias explícitas . Muchos sistemas sufren bajo el peso de dependencias implícitas o derivadas. Y esto haría las pruebas prácticamente imposibles. Como resultado, los diseños de TDD tienden a ser más modulares en los lugares correctos . Desde una perspectiva de seguridad, esto le permite hacer cosas como:

  • Pruebe los componentes que reciben datos de la red sin tener que enviarlos a través de la red.
  • Uno puede simular fácilmente que los objetos se comporten de maneras inesperadas / "poco realistas" como podría ocurrir en los escenarios de ataque.
  • Componentes de prueba en aislamiento.
  • O con cualquier mezcla deseada de componentes de producción.

Examen de la unidad

Una cosa que se debe tener en cuenta es que TDD favorece la alta localización (prueba de unidad). Como resultado, usted podría fácilmente probar que:

  • SecureZeroMemory() borraría correctamente una contraseña de la RAM.
  • O que GetSafeSQLParam() protegería correctamente contra la inyección de SQL.

Sin embargo, se vuelve más difícil verificar que todos los desarrolladores hayan utilizado el método correcto en cada lugar que sea necesario.
Una prueba para verificar una nueva característica relacionada con SQL confirmaría que la característica funciona; funcionaría igual de bien con las versiones "segura" e "insegura" de GetSQLParam.

Es por esta razón que no debe descuidar otras herramientas / técnicas que se pueden usar para "garantizar una codificación segura".

  • Normas de Codificación
  • Revisiones de Código
  • Pruebas

TDD es la mejor manera de construir un sistema seguro. Todo el software desarrollado por Microsoft está difuso y esta podría ser la razón número uno para la reducción dramática en las vulnerabilidades encontradas. Recomendé encarecidamente el uso de Peach Framework para este propósito. Personalmente, he usado Peach con gran éxito en la búsqueda de desbordamientos de búfer.

Los archivos Peach Pit ofrecen una forma de describir los datos utilizados por su aplicación. Puedes elegir qué interfaz quieres probar. ¿Tu aplicación lee archivos? ¿Tiene un puerto abierto? Después de decirle a Peach cómo se ve la entrada y cómo comunicarse con su aplicación, puede soltarla y conozco toda la información desagradable para hacer que su aplicación vomite por completo.

Para hacer que todo funcione, Peach tiene un excelente testing harness . Si su aplicación falla, Peach lo sabrá porque tiene un depurador adjunto. Cuando su aplicación falla, Peach la reiniciará y seguirá probando. Peach puede clasificar todos los bloqueos y hacer coincidir los volcados del núcleo con la entrada que utilizó para bloquear la aplicación.


Voy a tomar su segunda pregunta primero. Sí, las obras de TDD se pueden utilizar con requisitos no funcionales. De hecho, se utiliza a menudo como tal. El beneficio más común de un diseño modular mejorado, que no es funcional, pero visto por todos los que practican TDD. Otros ejemplos que he usado TDD para verificar: multiplataforma, base de datos cruzada y rendimiento.

Para todas sus pruebas, es posible que deba reestructurar el código para que pueda comprobarse. Este es uno de los mayores efectos de TDD: realmente cambia la forma en que estructuras tu código. Al principio parece que esto está perturbando el diseño, pero pronto te das cuenta de que el diseño comprobable es mejor. De todas formas...

Los errores de interpretación de cadenas (Unicode vs. ANSI) son particularmente agradables de probar con TDD. Por lo general, es sencillo enumerar los insumos buenos y malos y hacer valer sus interpretaciones. Es posible que necesite reestructurar un poco su código para "hacerlo comprobable"; Me refiero a métodos de extracción que aíslan el código específico de la cadena.

Para los desbordamientos de búfer, asegurarse de que las rutinas respondan correctamente si se proporcionan demasiados datos también es bastante sencillo de probar. Solo escribe una prueba y envíales demasiados datos. Afirma que hicieron lo que esperabas. Pero algunos desbordamientos de búfer y desbordamientos de pila son un poco más complicados. Debe poder hacer que esto suceda, pero también debe descubrir cómo detectar si sucedieron . Esto puede ser tan simple como asignar un búfer con bytes adicionales en ellos y verificar que esos bytes no cambian durante las pruebas ... O puede que algunas otras técnicas creativas.

Aunque no estoy seguro de que haya una respuesta simple. Las pruebas requieren creatividad, disciplina y compromiso, pero por lo general vale la pena.

  • Aísla el comportamiento que necesitas para probar.
  • Asegúrate de que puedes detectar el problema
  • Sabes lo que quieres que pase para el caso de error.
  • escribe la prueba y ve que falle

Espero que esto ayude


Pruebas parametrizadas

Si bien no estamos realizando una prueba de saturación de búfer en mi trabajo, tenemos la noción de pruebas de plantilla. Estas pruebas están parametrizadas para requerir los datos específicos para el caso que queremos probar. Luego utilizamos la metaprogramación para crear dinámicamente las pruebas reales aplicando los parámetros para cada caso a la plantilla. Esto tiene la ventaja de ser determinista y se ejecuta como parte de nuestro conjunto de pruebas automatizadas.

Mi práctica de TDD

Hacemos pruebas de aceptación impulsadas por el desarrollo en mi trabajo. La mayoría de nuestras pruebas están cerca de las pruebas funcionales de pila completa. La razón es que descubrimos que era más valioso probar y asegurar el comportamiento de las acciones dirigidas por el usuario. Utilizamos técnicas como la generación de pruebas dinámicas a partir de pruebas parametrizadas para brindarnos más cobertura con un mínimo de trabajo. Hacemos esto para ASCII vs UTF8, convenciones API y pruebas de variante bien conocidas.