security - Programación defensiva
defensive-programming (14)
Al escribir el código, ¿programa conscientemente de forma defensiva para garantizar la alta calidad del programa y para evitar la posibilidad de que su código sea explotado maliciosamente, por ejemplo, mediante exploits de desbordamiento de búfer o inyección de código?
¿Cuál es el nivel de calidad "mínimo" que siempre aplicará a su código?
Bueno, hay un cierto conjunto de mejores prácticas para la seguridad. Como mínimo, para las aplicaciones de bases de datos, debe tener cuidado con la inyección SQL.
Otras cosas como contraseñas hash, encriptación de cadenas de conexión, etc. también son un estándar.
A partir de ahora, depende de la aplicación real.
Afortunadamente, si está trabajando con frameworks como .Net, mucha protección de seguridad viene incorporada.
Siempre tienes que programar de forma defensiva, diría incluso para aplicaciones internas, simplemente porque los usuarios podrían simplemente escribir algo que rompe tu aplicación. De acuerdo, probablemente no tengas que preocuparte por tratar de estafarte sin dinero, pero aún así. Siempre programe a la defensiva y suponga que la aplicación fallará.
Siempre trabajo para prevenir cosas como ataques de inyección. Sin embargo, cuando trabajas en un sitio interno de intranet, la mayoría de las características de seguridad parecen un esfuerzo desperdiciado. Todavía los hago, tal vez simplemente no tan bien.
Depende.
Si realmente estoy pirateando algo para mi propio uso, escribiré el mejor código en el que no tenga que pensar. Deje que el compilador sea mi amigo para las advertencias, etc., pero no crearé automáticamente tipos por el placer de hacerlo.
Cuanto más probable sea que se use el código, incluso ocasionalmente, elevo el nivel de los controles.
- números mágicos mínimos
- mejores nombres de variables
- longitud de matriz / serie completamente revisada y definida
- programación por aserciones de contrato
- verificaciones de valor nulo
- excepciones (dependiendo del contexto del código)
- comentarios explicativos básicos
- documentación de uso accesible (si es perl, etc.)
Recomiendo estar a la defensiva para los datos que entran en un "componente" o marco. Dentro de un "componente" o marco uno debería pensar que los datos son "correctos".
Pensando así Depende de quien llama que proporcione los parámetros correctos; de lo contrario, TODAS las funciones y métodos deberán verificar cada parámetro recibido. Pero si el cheque solo se realiza para la persona que llama, el cheque solo se necesita una vez. Por lo tanto, un parámetro debe ser "correcto" y, por lo tanto, puede pasarse a niveles inferiores.
- Siempre verifique los datos de fuentes externas, usuarios, etc.
- Un "componente" o marco siempre debe verificar las llamadas entrantes.
Si hay un error y se usa un valor incorrecto en una llamada. ¿Qué es realmente lo correcto? Uno solo tiene una indicación de que los "datos" en los que el programa está trabajando son incorrectos y algunos como ASSERTS, pero otros quieren usar informes de errores avanzados y posibles errores de recuperación. En cualquier caso, los datos se encuentran defectuosos y en algunos casos es bueno continuar trabajando en ellos. (tenga en cuenta que es bueno si los servidores no mueren al menos)
Una imagen enviada desde un satélite podría ser un caso para intentar la recuperación avanzada de errores en ... una imagen descargada de Internet para poner un icono de error para ...
Soy de la opinión de que la programación correcta protegerá contra estos riesgos. Cosas como evitar las funciones en desuso, que (al menos en las bibliotecas de Microsoft C ++) son obsoletas debido a las vulnerabilidades de seguridad y validar todo lo que cruza un límite externo.
Las funciones que solo se invocan desde su código no deben requerir una validación de parámetros excesiva porque usted controla a la persona que llama, es decir, no se cruza ningún límite externo. Las funciones llamadas por el código de otras personas deben asumir que los parámetros entrantes serán inválidos y / o maliciosos en algún momento.
Mi enfoque para lidiar con las funciones expuestas es simplemente colgarse, con un mensaje útil si es posible. Si la persona que llama no puede obtener los parámetros correctos, entonces el problema está en su código y deberían solucionarlo, no usted. (Obviamente, usted ha proporcionado documentación para su función, ya que está expuesta).
La inyección de código solo es un problema si su aplicación puede elevar al usuario actual. Si un proceso puede inyectar código en su aplicación, podría escribir fácilmente el código en la memoria y ejecutarlo de todos modos. Sin poder obtener acceso completo al código del sistema, los ataques de inyección no tienen sentido. (Esta es la razón por la cual las aplicaciones utilizadas por los administradores no deben ser escribibles por usuarios menores).
Tomaré una definición diferente de programación defensiva, como la que defiende Effective Java de Josh Bloch. En el libro, habla sobre cómo manejar los objetos mutables que las personas que llaman pasan a su código (por ejemplo, en setters) y los objetos mutables que le pasa a las personas que llaman (por ejemplo, en getters).
- Para los instaladores, asegúrese de clonar cualquier objeto mutable y almacene el clon. De esta forma, las personas que llaman no pueden cambiar el objeto transferido después del hecho para romper las invariantes de su programa.
- Para getters, devuelve una vista inmutable de tus datos internos, si la interfaz lo permite; o bien devuelve un clon de los datos internos.
- Cuando llame a devoluciones de llamada proporcionadas por el usuario con datos internos, envíe una vista inmutable o clonar, según corresponda, a menos que tenga la intención de que la devolución de llamada altere los datos, en cuyo caso debe validarlo después de los hechos.
El mensaje para llevar a casa es asegurarse de que ningún código externo pueda contener un alias de cualquier objeto mutable que use internamente, de modo que pueda mantener sus invariantes.
Usar Test Driven Development ciertamente ayuda. Escribe un componente individual a la vez y luego enumera todos los posibles casos de entradas (mediante pruebas) antes de escribir el código. Esto garantiza que haya cubierto todas las bases y no haya escrito ningún código interesante que nadie use pero que pueda romperse.
Aunque no hago nada formal, generalmente paso un tiempo mirando cada clase y asegurándome de que:
- si están en un estado válido que permanecen en un estado válido
- no hay forma de construirlos en un estado inválido
- En circunstancias excepcionales, fracasarán tan elegantemente como sea posible (a menudo esto es una limpieza y lanzamiento)
En mi experiencia, emplear positivamente la programación defensiva no significa necesariamente que termines mejorando la calidad de tu código. No me malinterpreten, necesitas un programa defensivo para detectar los tipos de problemas que los usuarios encontrarán, a los usuarios no les gusta cuando tu programa falla, pero es poco probable que el código sea más fácil de mantener. prueba, etc.
Hace varios años, tomamos la política de utilizar aserciones en todos los niveles de nuestro software y esto, junto con pruebas unitarias, revisiones de códigos, etc. además de nuestras suites de prueba de aplicaciones existentes, tuvo un efecto significativo y positivo en la calidad de nuestro código.
Recomiendo a las personas escribir código que sea fascista en el entorno de desarrollo y benévolo en la producción.
Durante el desarrollo, desea capturar datos / lógica / código incorrectos lo antes posible para evitar que los problemas pasen desapercibidos o que resulten en problemas posteriores donde la causa raíz es difícil de rastrear.
En producción, maneje los problemas tan elegantemente como sea posible. Si algo realmente es un error no recuperable, trátelo y presente esa información al usuario.
Como ejemplo, aquí está nuestro código para normalizar un vector. Si le das datos malos en desarrollo, gritará, en producción devuelve un valor de seguridad.
inline const Vector3 Normalize( Vector3arg vec )
{
const float len = Length(vec);
ASSERTMSG(len > 0.0f "Invalid Normalization");
return len == 0.0f ? vec : vec / len;
}
Java, JARs firmados y JAAS.
Java para evitar el desbordamiento de búfer y los exploits de puntero / stack whacking.
No use JNI. (Java Native Interface) lo expone a bibliotecas DLL / Shared.
Firmado JAR para detener la carga de clases es un problema de seguridad.
JAAS puede permitir que su aplicación no confíe en nadie, incluso en sí misma.
J2EE tiene (admitidamente limitado) soporte incorporado para la seguridad basada en roles.
Hay algo de sobrecarga para algo de esto, pero los agujeros de seguridad desaparecen.
Respuesta simple: depende . Demasiada codificación defensiva puede causar problemas importantes de rendimiento.
Al igual que abyx, en el equipo en el que participo, los desarrolladores siempre usan pruebas unitarias y revisiones de códigos. Además de eso, también pretendo asegurarme de no incorporar código que la gente pueda usar; tiendo a escribir código solo para el conjunto básico de métodos requeridos para que el objeto en cuestión funcione como se ha especificado. . Descubrí que la incorporación de métodos que quizás nunca se utilicen, pero que proporcionan funcionalidad, puede introducir involuntariamente un uso "clandestino" o involuntario / imprevisto en el sistema.
Es mucho más fácil regresar más tarde e introducir métodos, atributos y propiedades para los que se pregunta frente a anticipar algo que quizás nunca llegue.
En mi línea de trabajo, nuestro código tiene que ser de alta calidad.
Entonces, nos enfocamos en dos cosas principales:
- Pruebas
- Revisiones de código
Esos traen a casa el dinero.