La computación distribuida trata sobre el tiempo y el estado. Es decir, para que más de un componente se comunique, debe compartir el estado, y todo esto requiere tiempo.
La mayoría de programadores antropomorfizan su trabajo. Piensan en un único puesto de control que lleva a cabo todo el programa de igual forma que harían ellos si tuviesen que realizar la tarea ellos mismos. Sin embargo, los equipos modernos cambian entre tareas con gran rapidez y, en una CPU múltiple con varios núcleos, o en los sistemas distribuidos, dos eventos pueden llevarse a cabo a la vez exactamente. Estos defectos hacen que sea urgente que se unan posturas entre el modelo de los programadores sobre cómo un programa se ejecuta y lo que sucede en la realidad. Dichos defectos están relacionados con interacciones inesperadas entre los puestos, los procesos, el tiempo y la información. Estas interacciones se producen a través del estado compartido: semáforos, variables, el sistema de archivos y, básicamente, cualquier cosa que pueda guardar información.
Insecure Temporary File
Ejemplo: el siguiente código utiliza un archivo temporal para almacenar datos intermedios recopilados de la red antes de procesarlos.
...
if (tmpnam_r(filename)){
FILE* tmp = fopen(filename,"wb+");
while((recv(sock,recvbuf,DATA_SIZE, 0) > 0)&&(amt!=0))
amt = fwrite(recvbuf,1,DATA_SIZE,tmp);
}
...
Este código que, por lo general, es bastante corriente, es vulnerable a una serie de distintos ataques debido a que utiliza un método poco seguro para crear archivos temporales. Las vulnerabilidades que presenta esta función y otras se describen en las siguientes secciones. Los problemas de seguridad más destacables relacionados con la creación de archivos temporales se han producido en sistemas operativos basados en Unix. No obstante, las aplicaciones de Windows presentan riesgos paralelos. Esta sección incluye un debate sobre la creación de archivos temporales tanto en los sistemas Unix como Windows.
Los métodos y los comportamientos pueden variar de un sistema a otro, pero los riesgos fundamentales planteados por cada uno de ellos son razonablemente constantes. Consulte la sección Recomendaciones para obtener información sobre las funciones seguras de lenguaje del núcleo y consejos en cuanto a un método seguro para crear archivos temporales.
Las funciones diseñadas para ayudar en la creación de archivos temporales se pueden dividir en dos grupos en función de si simplemente proporcionan un nombre de archivo o de si realmente abren un archivo.
Grupo 1 - Nombres de archivo "exclusivos":
El primer grupo de funciones de WinAPI y la biblioteca de C diseñado para ayudar en el proceso de creación de archivos temporales generando un nombre de archivo exclusivo para un nuevo archivo temporal, que se supone que el programa debe abrir a continuación. Este grupo incluye funciones de la biblioteca de C, como
tmpnam()
, tempnam()
, mktemp()
y sus equivalentes de C++ precedidos de un _
(carácter de subrayado), así como de la función GetTempFileName()
de la API de Windows. Este grupo de funciones presenta una condición de carrera subyacente en relación con el nombre de archivo seleccionado. Aunque las funciones garantizan que el nombre de archivo es exclusivo en el momento de seleccionarlo, no hay ningún mecanismo que impida que otro proceso o un usuario malintencionado cree un archivo con el mismo nombre tras seleccionarlo, pero antes de que la aplicación intente abrirlo. Más allá del riesgo de conflicto legítimo provocado por otra llamada a la misma función, hay una alta probabilidad de que un usuario malintencionado pueda crear un conflicto malicioso debido a que los nombres de archivo generados por estas funciones no son lo suficientemente aleatorios para que sean difíciles de adivinar. Si se crea un archivo con el nombre seleccionado, en función de cómo este se abra, el contenido o los permisos de acceso existentes del mismo permanecerán intactos. Si el contenido existente del archivo presenta una naturaleza maliciosa, es posible que un usuario malintencionado pueda introducir datos peligrosos en la aplicación cuando esta lea el archivo temporal. Si un usuario malintencionado crea previamente el archivo con permisos de acceso moderados, es posible que este pueda acceder a los datos que ha almacenado la aplicación en el archivo temporal, así como modificarlos o dañarlos. En los sistemas basados en Unix, se puede dar un ataque más insidioso si el usuario malintencionado crea previamente el archivo como vínculo a otro archivo importante. A continuación, si la aplicación se trunca o escribe datos en el archivo, puede realizar de forma inconsciente operaciones dañinas en nombre del usuario malintencionado. Resulta una amenaza especialmente grave si el programa funciona con permisos elevados.
Por último, en el mejor de los casos, el archivo se abrirá con una llamada a
open()
mediante los indicadores O_CREAT
y O_EXCL
, o a CreateFile()
mediante el atributo CREATE_NEW
, que presentará errores si el archivo ya existe y, por lo tanto, impide los tipos de ataques descritos anteriormente. Sin embargo, si un usuario malintencionado puede predecir con precisión una secuencia de nombres de archivo temporales, es posible que se impida que la aplicación abra el almacenamiento temporal necesario, lo que provocaría un ataque de denegación de servicio (DoS). Este tipo de ataque no sería difícil de implementar dado el reducido nivel de aleatoriedad empleado en la selección de los nombres de archivo generados por estas funciones.Grupo 2 - Archivos "exclusivos":
El segundo grupo de funciones de la biblioteca de C intenta solucionar algunos de los problemas de seguridad relacionados con los archivos temporales, no solo generando un archivo exclusivo, sino abriéndolo. Este grupo incluye funciones de la biblioteca de C como, por ejemplo,
tmpfile()
y sus equivalentes de C++ precedidos de _
(carácter de subrayado), así como de una función de biblioteca C mkstemp()
con un comportamiento más eficaz.Las funciones del estilo
tmpfile()
crean un nombre de archivo exclusivo y abren el archivo del mismo modo que lo haría fopen()
si transfiriese los indicadores "wb+"
, es decir, como un archivo binario en modo de lectura/escritura. Si el archivo ya existe, tmpfile()
se truncará para establecer el tamaño cero, posiblemente en un intento por apaciguar las inquietudes de seguridad mencionadas anteriormente en cuanto a la condición de carrera que se produce entre la selección del nombre de archivo supuestamente exclusivo y la posterior apertura del archivo seleccionado. Sin embargo, este comportamiento no soluciona claramente los problemas de seguridad de la función. En primer lugar, un atacante puede crear previamente el archivo con permisos de acceso moderados que probablemente conservará el archivo abierto por tmpfile()
. Además, en los sistemas basados en Unix, si el usuario malintencionado crea previamente el archivo como vínculo a otro archivo importante, la aplicación puede utilizar los permisos posiblemente elevados para truncar ese archivo, dañándolo en nombre del usuario malintencionado. Por último, si tmpfile()
crea un nuevo archivo, los permisos de acceso aplicados al mismo variarán de un sistema operativo a otro, lo que puede dejar vulnerable la aplicación, incluso aunque el usuario malintencionado pueda predecir por adelantado el nombre de archivo que se va a usar.Por último,
mkstemp()
supone un método razonablemente seguro para crear archivos temporales. Este intentará crear y abrir un archivo exclusivo basado en una plantilla de nombre de archivo proporcionada por el usuario, junto con una serie de caracteres generados aleatoriamente. Si no puede crear este archivo, presentará errores y devolverá -1
. En los sistemas modernos, el archivo se abre mediante el modo 0600
, lo que implica que el archivo se protegerá frente a su manipulación a menos que el usuario cambie de forma explícita los permisos de acceso. No obstante, mkstemp()
aún presenta problemas en relación con el uso de nombres de archivo predecibles y puede dejar vulnerable una aplicación frente a ataques de denegación de servicio si un usuario malintencionado provoca que mkstemp()
presente fallos mediante la predicción y la creación previa de los nombres de archivo que se van a utilizar.Ejemplo: el siguiente código utiliza un archivo temporal para almacenar datos intermedios recopilados de la red antes de procesarlos.
...
try:
tmp_filename = os.tempnam()
tmp_file = open(tmp_filename, 'w')
data = s.recv(4096)
while True:
more = s.recv(4096)
tmp_file.write(more)
if not more:
break
except socket.timeout:
errMsg = "Connection timed-out while connecting"
self.logger.exception(errMsg)
raise Exception
...
Este código que, por lo general, es bastante corriente, es vulnerable a una serie de distintos ataques debido a que utiliza un método poco seguro para crear archivos temporales. Las vulnerabilidades que presenta esta función y otras se describen en las siguientes secciones. Los problemas de seguridad más destacables relacionados con la creación de archivos temporales se han producido en sistemas operativos basados en Unix. No obstante, las aplicaciones de Windows presentan riesgos paralelos.
Los métodos y los comportamientos pueden variar de un sistema a otro, pero los riesgos fundamentales planteados por cada uno de ellos son razonablemente constantes. Consulte la sección Recomendaciones para obtener información sobre las funciones seguras de lenguaje del núcleo y consejos en cuanto a un método seguro para crear archivos temporales.
Las funciones diseñadas para ayudar en la creación de archivos temporales se pueden dividir en dos grupos en función de si simplemente proporcionan un nombre de archivo o de si realmente abren un archivo.
Grupo 1 - Nombres de archivo "exclusivos":
El primer grupo de funciones diseñado para ayudar en el proceso de creación de archivos temporales genera un nombre de archivo exclusivo para un nuevo archivo temporal, que se supone que el programa debe abrir a continuación. Este grupo de funciones presenta una condición de carrera subyacente en relación con el nombre de archivo seleccionado. Aunque las funciones garantizan que el nombre de archivo es exclusivo en el momento de seleccionarlo, no hay ningún mecanismo que impida que otro proceso o un usuario malintencionado cree un archivo con el mismo nombre tras seleccionarlo, pero antes de que la aplicación intente abrirlo. Más allá del riesgo de conflicto legítimo provocado por otra llamada a la misma función, hay una alta probabilidad de que un usuario malintencionado pueda crear un conflicto malicioso debido a que los nombres de archivo generados por estas funciones no son lo suficientemente aleatorios para que sean difíciles de adivinar.
Si se crea un archivo con el nombre seleccionado, en función de cómo este se abra, el contenido o los permisos de acceso existentes del mismo permanecerán intactos. Si el contenido existente del archivo presenta una naturaleza maliciosa, es posible que un usuario malintencionado pueda introducir datos peligrosos en la aplicación cuando esta lea el archivo temporal. Si un usuario malintencionado crea previamente el archivo con permisos de acceso moderados, es posible que este pueda acceder a los datos que ha almacenado la aplicación en el archivo temporal, así como modificarlos o dañarlos. En los sistemas basados en Unix, se puede dar un ataque más insidioso si el usuario malintencionado crea previamente el archivo como vínculo a otro archivo importante. A continuación, si la aplicación se trunca o escribe datos en el archivo, puede realizar de forma inconsciente operaciones dañinas en nombre del usuario malintencionado. Resulta una amenaza especialmente grave si el programa funciona con permisos elevados.
Por último, en el mejor de los casos, el archivo se abrirá con una llamada a
open()
mediante los indicadores os.O_CREAT
y os.O_EXCL
, que presentará errores si el archivo ya existe y, por lo tanto, impedirá los tipos de ataques descritos anteriormente. Sin embargo, si un usuario malintencionado puede predecir con precisión una secuencia de nombres de archivo temporales, es posible que se impida que la aplicación abra el almacenamiento temporal necesario, lo que provocaría un ataque de denegación de servicio (DoS). Este tipo de ataque no sería difícil de implementar dado el reducido nivel de aleatoriedad empleado en la selección de los nombres de archivo generados por estas funciones.Grupo 2 - Archivos "exclusivos":
El segundo grupo de funciones intenta solucionar algunos de los problemas de seguridad relacionados con los archivos temporales no solo generando un nombre de archivo exclusivo, sino también abriendo el archivo. Este grupo incluye funciones como
tmpfile()
.Las funciones del estilo
tmpfile()
crean un nombre de archivo exclusivo y abren el archivo del mismo modo que lo haría open()
si transfiriese los indicadores "wb+"
, es decir, como un archivo binario en modo de lectura/escritura. Si el archivo ya existe, tmpfile()
se truncará para establecer el tamaño cero, posiblemente en un intento por apaciguar las inquietudes de seguridad mencionadas anteriormente en cuanto a la condición de carrera que se produce entre la selección del nombre de archivo supuestamente exclusivo y la posterior apertura del archivo seleccionado. Sin embargo, este comportamiento no soluciona claramente los problemas de seguridad de la función. En primer lugar, un atacante puede crear previamente el archivo con permisos de acceso moderados que probablemente conservará el archivo abierto por tmpfile()
. Además, en los sistemas basados en Unix, si el usuario malintencionado crea previamente el archivo como vínculo a otro archivo importante, la aplicación puede utilizar los permisos posiblemente elevados para truncar ese archivo, dañándolo en nombre del usuario malintencionado. Por último, si tmpfile()
crea un nuevo archivo, los permisos de acceso aplicados al mismo variarán de un sistema operativo a otro, lo que puede dejar vulnerable la aplicación, incluso aunque el usuario malintencionado pueda predecir por adelantado el nombre de archivo que se va a usar.