¿Sería una buena práctica de programación segura sobrescribir una variable "sensible" antes de borrarla?

77

¿Es una buena práctica de programación segura sobrescribir los datos confidenciales almacenados en una variable antes de que se eliminen (o queden fuera del alcance)? Mi idea es que evitaría que un pirata informático pudiera leer los datos latentes en la RAM debido a la remanencia de los datos. ¿Habría alguna seguridad adicional en sobrescribirlo varias veces? Aquí hay un pequeño ejemplo de lo que estoy hablando, en C ++ (con comentarios incluidos).

void doSecret()
{
  // The secret you want to protect (probably best not to have it hardcoded like this)
  int mySecret = 12345;

  // Do whatever you do with the number
  ...

  // **Clear out the memory of mySecret by writing over it**
  mySecret = 111111;
  mySecret = 0;
  // Maybe repeat a few times in a loop
}

Una idea es que, si esto realmente agrega seguridad, sería bueno si el compilador agregara automáticamente las instrucciones para hacer esto (tal vez por defecto, o tal vez diciéndole al compilador que lo haga al eliminar variables).

  

Esta pregunta apareció como Pregunta de la semana sobre seguridad de la información .
  Lea el 12 de diciembre de 2014 entrada de blog para obtener más detalles o envíe su propia pregunta de la semana .

    
pregunta Jonathan 04.12.2014 - 17:18
fuente

10 respuestas

71

Sí, es una buena idea sobrescribir y luego eliminar / liberar el valor. No asuma que todo lo que tiene que hacer es "sobrescribir los datos" o dejar que quede fuera del alcance de la GC, porque cada idioma interactúa con el hardware de manera diferente.

Al proteger una variable, deberías pensar en:

  • cifrado (en caso de volcados de memoria o almacenamiento en caché de página)
  • fijando en la memoria
  • capacidad de marcar como de solo lectura (para evitar nuevas modificaciones)
  • construcción segura al NO permitir que se pase una cadena constante
  • optimizar compiladores (vea nota en el artículo vinculado re: macro ZeroMemory)

La implementación real de "borrar" depende del idioma y la plataforma. Investigue el idioma que está utilizando y vea si es posible codificar de forma segura.

¿Por qué es una buena idea? Los desplomes y cualquier cosa que contenga el montón podría contener sus datos confidenciales. Considere utilizar lo siguiente cuando proteja sus datos en memoria

Consulte StackOverflow para ver las guías de implementación por idioma.

Debe tener en cuenta que incluso cuando utilice la guía del proveedor (MSFT en este caso) todavía es posible vuelca el contenido de SecureString , y puede tener pautas de uso específicas para escenarios de alta seguridad.

    
respondido por el random65537 04.12.2014 - 17:26
fuente
34

¿Almacenar un valor que no se usa de nuevo? Parece algo que se optimizaría, independientemente de cualquier beneficio que pudiera proporcionar.

Además, es posible que no sobrescriba los datos en la memoria dependiendo de cómo funciona el idioma. Por ejemplo, en un idioma que utiliza un recolector de basura, no se eliminará de inmediato (y esto se supone que no dejó ninguna otra referencia).

Por ejemplo, en C #, creo que lo siguiente no funciona.

string secret = "my secret data";

...lots of work...

string secret = "blahblahblah";

"my secret data" se cuelga hasta que se recolecta la basura porque es inmutable. Esa última línea es en realidad crear una nueva cadena y tener un punto secreto hacia ella. No acelera la rapidez con la que se eliminan los datos secretos reales.

¿Hay algún beneficio? Suponiendo que lo escribamos en ensamblador o en un lenguaje de palanca baja para que podamos asegurarnos de que estamos sobrescribiendo los datos, y ponemos nuestra computadora en suspensión o la dejamos encendida con la aplicación en ejecución, y una RAM malvada la rasca nuestra memoria RAM. la criada malvada consiguió nuestros datos de RAM después de que se sobrescribiera el secreto pero antes de que simplemente se hubiera eliminado (probablemente un espacio muy pequeño), y nada más en la RAM o en el disco duro revelaría este secreto ... entonces veo un posible aumento en seguridad.

Pero el costo en comparación con el beneficio parece hacer que esta optimización de seguridad sea muy baja en nuestra lista de optimizaciones (y por debajo del punto de "valioso" en la mayoría de las aplicaciones en general).

Es posible que pueda ver un uso limitado de este en chips especiales destinados a guardar secretos durante un breve período de tiempo para garantizar que permanezcan durante el menor tiempo posible, pero aún así no estoy seguro de ningún beneficio por los costos.

    
respondido por el Lawtonfogle 04.12.2014 - 17:45
fuente
17

Necesitas un modelo de amenaza

Ni siquiera deberías empezar a pensar en sobrescribir las variables de seguridad hasta que tengas un modelo de amenaza que describa qué tipo de piruetas intentas prevenir. La seguridad siempre tiene un costo. En este caso, el costo es el costo de desarrollo de enseñar a los desarrolladores a mantener todo este código adicional para asegurar los datos. Este costo significa que puede ser más probable que sus desarrolladores cometan errores, y es más probable que esos errores sean la fuente de una fuga que un problema de memoria.

  • ¿Puede el atacante acceder a tu memoria? Si es así, ¿hay alguna razón por la que crees que no pudieron o no podrían simplemente rastrear el valor anterior por sobreescribirlo? ¿Qué tipo de marcos de tiempo tiene el atacante acceso a su memoria?
  • ¿Puede el atacante acceder a los volcados de memoria? ¿Te importa si pueden acceder a datos confidenciales a cambio de ser lo suficientemente ruidosos como para causar un volcado de memoria en primer lugar?
  • ¿Es este código abierto o código cerrado? Si es de código abierto, tiene que preocuparse por los compiladores múltiples, porque los compiladores optimizarán las cosas como datos sobrescritos todo el tiempo. Su trabajo no es proporcionar seguridad. ( Para un ejemplo de la vida real, el PasswordSafe de Schneier tiene clases especializadas para proteger los datos de contraseña no cifrados. Para hacerlo, usa las funciones de la API de Windows para bloquear la memoria y forzarla a sobrescribirse correctamente en lugar de usar el compilador hacerlo por él )
  • ¿Es este un idioma recolectado en la basura? ¿Sabe cómo forzar a SU versión particular de su recolector de basura en particular a deshacerse de sus datos?
  • ¿Cuántos intentos puede hacer el atacante para obtener datos confidenciales antes de que lo note y lo desconecte con otros medios (como cortafuegos)?
  • ¿Se está ejecutando esto en una máquina virtual? ¿Qué tan seguro está de la seguridad de su hipervisor?
  • ¿Un atacante tiene acceso físico? Por ejemplo, Windows está más que feliz de usar una unidad flash para almacenar en caché la memoria virtual. Todo lo que un atacante tendría que hacer es convencer a Windows para que lo empuje en la unidad flash. Cuando eso sucede, es MUY difícil conseguirlo. Tan difícil, de hecho, que no hay un método reconocido para eliminar datos de forma confiable desde una unidad flash.

Estas preguntas deben abordarse antes de considerar el intento de sobrescribir datos confidenciales. Intentar sobrescribir los datos sin abordar el modelo de subproceso es una falsa sensación de seguridad.

    
respondido por el Cort Ammon 06.12.2014 - 17:45
fuente
14

Sí, es una buena práctica para la seguridad sobrescribir datos que son particularmente sensibles cuando los datos ya no son necesarios, es decir, como parte de un destructor de objetos (ya sea un destructor explícito proporcionado por el lenguaje o una acción que realiza el programa antes de desasignar el objeto). Incluso es una buena práctica sobrescribir datos que no son sensibles a sí mismos, por ejemplo, para poner a cero los campos de punteros en una estructura de datos que queda fuera de uso, y también poner a cero los punteros cuando el objeto al que apuntan se libera, incluso si lo sabe. ya no vas a usar ese campo.

Una razón para hacer esto es en caso de que los datos se filtren a través de factores externos como un volcado de núcleo expuesto, una imagen de hibernación robada, un servidor comprometido que permita un volcado de memoria de procesos en ejecución, etc. Ataques físicos donde un atacante extrae la RAM. los bastones y el uso de la remanencia de datos rara vez son una preocupación, excepto en las computadoras portátiles y quizás en dispositivos móviles como los teléfonos (donde la barra es más alta porque la RAM está soldada), e incluso en su mayoría solo en escenarios específicos. La remanencia de los valores sobrescritos no es una preocupación: se necesitaría un hardware muy costoso para probar dentro de un chip RAM para detectar cualquier diferencia de voltaje microscópico persistente que podría estar influenciada por un valor sobrescrito. Si está preocupado por los ataques físicos en la RAM, una mayor preocupación sería asegurarse de que los datos estén sobrescritos en la RAM y no solo en la memoria caché de la CPU. Pero, de nuevo, eso suele ser una preocupación menor.

La razón más importante para sobrescribir los datos obsoletos es como una defensa contra los errores del programa que causan el uso de memoria no inicializada, como el infame Heartbleed . Esto va más allá de los datos confidenciales porque el riesgo no se limita a una fuga de datos: si hay un error de software que hace que se elimine la referencia de un campo de puntero sin haber sido inicializado, el error es menos propenso a la explotación y más fácil de rastrear si el campo contiene todos los bits-cero que si potencialmente apunta a una ubicación de memoria válida pero sin sentido.

Tenga en cuenta que los buenos compiladores optimizarán la puesta a cero si detectan que el valor ya no se usa. Es posible que deba usar algún truco específico del compilador para hacer que el compilador crea que el valor permanece en uso y, por lo tanto, generar el código de reducción a cero.

En muchos idiomas con administración automática, los objetos se pueden mover a la memoria sin previo aviso. Esto significa que es difícil controlar las fugas de datos obsoletos, a menos que el administrador de memoria borre la memoria no utilizada (a menudo no lo hace, por su rendimiento). En el lado positivo, generalmente no debe preocuparse por las fugas externas, ya que los lenguajes de alto nivel tienden a impedir el uso de memoria no inicializada (tenga cuidado con las funciones de creación de cadenas en idiomas con cadenas mutables).

Por cierto, escribí "cero" arriba. Puede utilizar un patrón de bits distinto de todos los ceros; all-ceros tiene la ventaja de que es un puntero no válido en la mayoría de los entornos. Un patrón de bits que sabe es un puntero no válido pero que es más distintivo puede ser útil para la depuración.

Muchos estándares de seguridad exigen el borrado de datos confidenciales, como las claves. Por ejemplo, el estándar FIPS 140-2 para módulos criptográficos lo requiere incluso en el nivel de seguridad más bajo, que además de eso solo requiere el cumplimiento funcional y no la resistencia contra ataques.

    
respondido por el Gilles 04.12.2014 - 19:02
fuente
8

Para una adición (esperemos que sea interesante) al resto de las respuestas, muchas personas subestiman la dificultad de sobrescribir correctamente la memoria con C. Voy a citar en gran medida la publicación del blog de Colin Percival titulada " Cómo poner a cero un búfer ".

El principal problema al que se enfrentan los intentos ingenuos de sobrescribir la memoria en C son las optimizaciones del compilador. La mayoría de los compiladores modernos son lo suficientemente "inteligentes" para reconocer que los ídems comunes utilizados para sobrescribir la memoria no cambian de hecho el comportamiento observable del programa y pueden optimizarse. Desafortunadamente esto rompe completamente lo que queremos lograr. Algunos de los trucos comunes se describen en la publicación de blog vinculada anteriormente. Peor aún, un truco que funciona para una versión de un compilador no necesariamente funciona con otro compilador o incluso con una versión diferente del mismo compilador. A menos que esté distribuyendo binarios solamente , esta es una situación problemática.

La única forma de sobrescribir de manera confiable la memoria en C que conozco es la Función memset_s . Lamentablemente, esto solo está disponible en C11, por lo que los programas escritos para versiones anteriores de C están fuera de suerte.

  

La función memset_s copia el valor de c (convertido en un carácter sin signo) en cada uno de los   Primeros n caracteres del objeto apuntado por s. A diferencia de memset, cualquier llamada a memset_s será   evaluado estrictamente de acuerdo con las reglas de la máquina abstracta, como se describe en 5.1.2.3. Es decir, cualquier   la llamada a memset_s asumirá que la memoria indicada por s y n puede ser accesible en el   futuro y por lo tanto debe contener los valores indicados por c.

Sin lugar a dudas, Colin Percival es de la opinión de que no es suficiente sobrescribir la memoria. De una publicación de blog de seguimiento titulada " Los búferes de reducción a cero son insuficientes " , declara

  

Con un poco de cuidado y un compilador cooperativo, podemos poner a cero un búfer, pero eso no es lo que necesitamos. Lo que debemos hacer es poner a cero todas las ubicaciones donde se puedan almacenar datos confidenciales. Recuerde, la razón principal por la que teníamos información sensible en la memoria en primer lugar era para poder utilizarla; y ese uso es casi seguro que los datos confidenciales se copian en la pila y en los registros.

Continúa y da el ejemplo de las implementaciones de AES usando el conjunto de instrucciones AESNI en plataformas x86 que pierden datos en los registros.

Afirma que,

  

Es imposible implementar de forma segura cualquier sistema de cifrado que brinde secreto hacia adelante en C.

Una afirmación preocupante de hecho.

    
respondido por el Ayrx 16.12.2014 - 13:48
fuente
4

Es importante sobrescribir los datos confidenciales inmediatamente después de que sean necesarios porque, de lo contrario:

  • Los datos permanecen en la pila hasta que se sobrescriben, y se pueden ver con un desbordamiento de marcos desde un procedimiento diferente.
  • Los datos están sujetos a raspado de memoria.

De hecho, si busca en el código fuente aplicaciones sensibles a la seguridad (por ejemplo, openssh ), encontrará que cuidadosamente elimina los datos confidenciales después de su uso.

También es cierto que los compiladores podrían intentar optimizar la sobrescritura, y, si no, es importante saber cómo se almacenan los datos físicamente (por ejemplo, si el secreto está almacenado en SSD, sobrescribirlo podría no borrar el contenido antiguo debido a nivelación de desgaste ).

    
respondido por el Ari Trachtenberg 05.12.2014 - 04:37
fuente
3

El ejemplo original muestra una variable de pila porque es un tipo int nativo.

Sobrescribirlo es una buena idea, de lo contrario, permanece en la pila hasta que otra cosa lo sobrescribe.

Sospecho que si estás en C ++ con objetos de pila o tipos C nativos asignados mediante punteros y malloc, sería una buena idea

  1. Utilice volatile
  2. Use pragmas para rodear el código usando esa variable y desactive las optimizaciones.
  3. Si es posible, solo reúna el secreto en valores intermedios en lugar de variables con nombre, por lo que solo existe durante los cálculos.

Bajo la JVM o C # creo que todas las apuestas están desactivadas.

    
respondido por el Andy Dent 07.12.2014 - 06:49
fuente
2

Es posible que la porción de memoria se haya desplazado antes de ir y eliminarla, dependiendo de cómo se reproduce la distribución de uso en el archivo de la página, los datos pueden estar allí para siempre .

Entonces, para eliminar con éxito el secreto de la computadora, primero debes asegurarte de que los datos nunca lleguen a la memoria persistente mediante fijación Las paginas. Luego, asegúrese de que el compilador no optimice las escrituras en la memoria que se eliminará.

    
respondido por el ratchet freak 05.12.2014 - 13:00
fuente
1

En realidad, la respuesta es sí, probablemente deberías sobrescribirlo antes de eliminarlo. Si eres un desarrollador de aplicaciones web, probablemente deberías sobrescribir todos los datos antes de utilizar las funciones delete o free .

Ejemplo de cómo se puede aprovechar este error:

El usuario puede insertar algunos datos maliciosos en el campo de entrada. Proceda con los datos y luego llame a la función free de la memoria asignada sin sobrescribirla, por lo que los datos permanecerán en la memoria. Luego, el usuario hace que su aplicación web caiga (por ejemplo, cargando una imagen muy grande o algo así) y el sistema UNIX asegurará el volcado de la memoria central en el archivo core o core.<pid> . Luego, si el usuario puede atacar el <pid> , que no tomará mucho tiempo, y el archivo de volcado central se interpretará como un shell web, porque contiene datos maliciosos del usuario.

    
respondido por el PaulOverflow 17.12.2014 - 08:20
fuente
-2

¿No es int secret = 13123 una constante en el ámbito local?

No debes estar demasiado ocupado con lo que estás escribiendo, ya que está cerca de valer la pena leerlo. De hecho, sugeriría, en un poco de consejo contrario, que deliberadamente lo deje legible. Y además, rellénelo al azar con cadenas que se correlacionan en el tiempo, que no se llaman de la manera estándar.

De esa manera, si revisa los sitios web oscuros para averiguar si ha sido comprometido, puede decir exactamente cómo se hizo. Dado que un volcado de datos está separado de un volcado raspador.

    
respondido por el mincewind 17.12.2014 - 08:52
fuente

Lea otras preguntas en las etiquetas