stringtranslate.com

Programación defensiva

La programación defensiva es una forma de diseño defensivo destinado a desarrollar programas que sean capaces de detectar posibles anomalías de seguridad y dar respuestas predeterminadas. [1] Garantiza el funcionamiento continuo de una pieza de software en circunstancias imprevistas. Las prácticas de programación defensiva se utilizan a menudo cuando se necesita alta disponibilidad , seguridad o protección .

La programación defensiva es un enfoque para mejorar el software y el código fuente , en términos de:

Sin embargo, una programación demasiado defensiva puede proteger contra errores que nunca se encontrarán, incurriendo así en costos de tiempo de ejecución y mantenimiento.

Programación segura

La programación segura es el subconjunto de la programación defensiva relacionada con la seguridad informática . La seguridad es la preocupación, no necesariamente la seguridad o la disponibilidad ( se puede permitir que el software falle de ciertas maneras). Como ocurre con todo tipo de programación defensiva, evitar errores es el objetivo principal; sin embargo, la motivación no es tanto reducir la probabilidad de fallas en el funcionamiento normal (como si la seguridad fuera la preocupación), sino reducir la superficie de ataque: el programador debe asumir que el software podría ser mal utilizado activamente para revelar errores, y que Los errores podrían explotarse de forma maliciosa.

int programación_riesgosa ( char * entrada ) { char str [ 1000 ]; // ... strcpy ( cadena , entrada ); // Copiar entrada. //... }              

La función dará como resultado un comportamiento indefinido cuando la entrada tenga más de 1000 caracteres. Es posible que algunos programadores no sientan que esto es un problema, suponiendo que ningún usuario ingresará una entrada tan larga. Este error en particular demuestra una vulnerabilidad que permite explotar el desbordamiento del buffer . Aquí hay una solución para este ejemplo:

int programación_segura ( char * entrada ) { char str [ 1000 + 1 ]; // Uno más para el carácter nulo.       //... // Copiar la entrada sin exceder la longitud del destino. strncpy ( cadena , entrada , tamaño de ( cadena ));    // Si strlen(input) >= sizeof(str) entonces strncpy no terminará en nulo. // Contrarrestamos esto estableciendo siempre el último carácter del búfer en NUL, // recortando efectivamente la cadena a la longitud máxima que podamos manejar. // También se puede decidir abortar explícitamente el programa si strlen(input) // es demasiado largo. cadena [ tamaño de ( cadena ) - 1 ] = '\0' ;          //... }

Programación ofensiva

La programación ofensiva es una categoría de programación defensiva, con el énfasis adicional de que ciertos errores no deben manejarse de manera defensiva . En esta práctica, sólo se deben manejar errores fuera del control del programa (como la entrada del usuario); En esta metodología se debe confiar en el software en sí, así como en los datos de la línea de defensa del programa .

Confiar en la validez de los datos internos

Programación demasiado defensiva
const char * Trafficlight_colorname ( enum Traffic_light_color c ) { switch ( c ) { case TRAFFICLIGHT_RED : devuelve "rojo" ; caso TRAFFICLIGHT_YELLOW : devuelve "amarillo" ; caso TRAFFICLIGHT_GREEN : devuelve "verde" ; } devolver "negro" ; // Debe manejarse como un semáforo muerto. // Advertencia: Esta última declaración de 'retorno' será descartada por un // compilador de optimización si todos los valores posibles de 'traffic_light_color' están listados en // la declaración de 'cambio' anterior... }                           
Programación ofensiva
const char * nombre_color_trafico ( enum color_luz_trafico c ) { switch ( c ) { case TRAFFICLIGHT_RED : return "rojo" ; caso TRAFFICLIGHT_YELLOW : devuelve "amarillo" ; caso TRAFFICLIGHT_GREEN : devuelve "verde" ; } afirmar ( 0 ); // Afirma que esta sección es inaccesible. // Advertencia: esta llamada a la función 'asertar' será descartada por un // compilador de optimización si todos los valores posibles de 'traffic_light_color' se enumeran en // la declaración anterior 'switch'... }                          

Confiar en los componentes del software

Programación demasiado defensiva
if ( is_legacy_compatible ( user_config )) { // Estrategia: No confíes en que el nuevo código se comporte como el antiguo código ( user_config ); } else { // Alternativa: no confíe en que el nuevo código maneje los mismos casos if ( new_code ( user_config ) ! = OK ) { old_code ( user_config ); } }              
Programación ofensiva
// Espere que el nuevo código no tenga nuevos errores if ( new_code ( user_config ) != OK ) { // Informe en voz alta y finalice abruptamente el programa para obtener la atención adecuada report_error ( "Algo salió muy mal" ); salir ( -1 ); }       

Técnicas

A continuación se muestran algunas técnicas de programación defensiva:

Reutilización inteligente del código fuente

Si se prueba el código existente y se sabe que funciona, reutilizarlo puede reducir la posibilidad de que se introduzcan errores.

Sin embargo, reutilizar código no siempre es una buena práctica. La reutilización del código existente, especialmente cuando se distribuye ampliamente, puede permitir la creación de exploits dirigidos a un público más amplio de lo que sería posible de otro modo y trae consigo toda la seguridad y vulnerabilidades del código reutilizado.

Al considerar el uso del código fuente existente, una revisión rápida de los módulos (subsecciones como clases o funciones) ayudará a eliminar o informar al desarrollador sobre cualquier vulnerabilidad potencial y garantizará que sea adecuado para su uso en el proyecto. [ cita necesaria ]

Problemas heredados

Antes de reutilizar código fuente, bibliotecas, API, configuraciones, etc. antiguos, se debe considerar si el trabajo anterior es válido para su reutilización o si es probable que sea propenso a problemas heredados .

Los problemas heredados son problemas inherentes cuando se espera que los diseños antiguos funcionen con los requisitos actuales, especialmente cuando los diseños antiguos no se desarrollaron ni probaron teniendo en cuenta esos requisitos.

Muchos productos de software han experimentado problemas con el código fuente antiguo; Por ejemplo:

Ejemplos notables del problema heredado:

Canonicalización

Es probable que los usuarios malintencionados inventen nuevos tipos de representaciones de datos incorrectos. Por ejemplo, si un programa intenta rechazar el acceso al archivo "/etc/ passwd ", un cracker podría pasar otra variante de este nombre de archivo, como "/etc/./passwd". Se pueden emplear bibliotecas de canonicalización para evitar errores debidos a entradas no canónicas .

Baja tolerancia contra errores "potenciales"

Supongamos que las construcciones de código que parecen propensas a problemas (similares a vulnerabilidades conocidas, etc.) son errores y posibles fallas de seguridad. La regla básica es: "No conozco todos los tipos de vulnerabilidades de seguridad . Debo protegerme contra aquellos que conozco y luego debo ser proactivo".

Otros consejos para proteger tu código

Las tres reglas de la seguridad de los datos

Estas tres reglas sobre seguridad de los datos describen cómo manejar cualquier dato, de origen interno o externo:

Todos los datos son importantes hasta que se demuestre lo contrario : significa que todos los datos deben verificarse como basura antes de ser destruidos.

Todos los datos están contaminados hasta que se demuestre lo contrario : significa que todos los datos deben manejarse de una manera que no exponga el resto del entorno de ejecución sin verificar la integridad.

Todo el código es inseguro hasta que se demuestre lo contrario ; aunque es un nombre ligeramente inapropiado, hace un buen trabajo al recordarnos que nunca debemos asumir que nuestro código es seguro, ya que los errores o el comportamiento indefinido pueden exponer el proyecto o sistema a ataques como los ataques de inyección SQL comunes .

Más información

Ver también

Referencias

  1. ^ Boulanger, Jean-Louis (1 de enero de 2016), Boulanger, Jean-Louis (ed.), "6 - Técnica para gestionar la seguridad del software", Aplicaciones de software certificables 1 , Elsevier, págs. 125-156, ISBN 978-1-78548-117-8, consultado el 2 de septiembre de 2022
  2. ^ "archivo fogo: Paul Vixie y David Conrad sobre BINDv9 y Internet Security por Gerald Oskoboiny <[email protected]>". impresionante.net . Consultado el 27 de octubre de 2018 .
  3. ^ "En cuanto a la cuestión de WMF, ¿cómo llegó allí?". MSRC . Archivado desde el original el 24 de marzo de 2006 . Consultado el 27 de octubre de 2018 .
  4. ^ Litchfield, David. "Bugtraq: Oracle, ¿dónde están los parches???". seclistas.org . Consultado el 27 de octubre de 2018 .
  5. ^ Alejandro, Kornbrust. "Bugtraq: RE: Oracle, ¿dónde están los parches???". seclistas.org . Consultado el 27 de octubre de 2018 .
  6. ^ Cerrudo, César. "Bugtraq: Re: [Divulgación completa] RE: Oracle, ¿dónde están los parches???". seclistas.org . Consultado el 27 de octubre de 2018 .

enlaces externos