Publi

Permitir que nuestros usuarios ejecuten tareas controladas en nombre de otro. #SUID #SGID – Con ejemplos y ataques

Uno de los puntos fuertes de un sistema Unix es su forma de gestionar los privilegios de usuario. Se suele decir que el malware en GNU/Linux sólo afecta al usuario por el que entra (y podrá hacer todo lo que éste pueda hacer), por lo tanto será muy difícil (nada es imposible, y siempre hay bugs) que un programa ejecutado con privilegios de usuario normal afecte al sistema completo.

Eso sí, otro de los puntos fuertes de un sistema tipo Unix, es que nos deja automatizar absolutamente todo. Podemos crear sistemas que trabajen solos, que atiendan a eventos y éstos disparen acciones dentro del sistema, casi sin límite. Como muchas tareas dependen de datos externos, se ejecutan multitud de programas (cada uno de su padre y de su madre), si queremos mantener un sistema seguro, debemos hacer que el usuario que haga esa automatización de tareas sea un usuario con los menores privilegios posibles. Aunque puede que alguna vez necesite ejecutar algo con mayores privilegios, incluso privilegios root.

Una breve introducción (permisos sobre archivos)

En este tipo de sistemas, cuando consultamos los permisos de acceso de un archivo (por ejemplo con ls), veremos algo como esto:

ls -latr test
-rwxr-xr-- 1 gaspy oficina 8608 mar 11 20:40 test

Veremos (de derecha a izquierda) el nombre, la fecha, el tamaño, el grupo, el usuario, número de enlaces y privilegios. Lo que nos interesa son los privilegios. Son 10 caracteres, primero encontramos un carácter que indicará si es un archivo, directorio, enlace, pipe, socket o cualquier otra cosa, y luego tres grupos de tres que pueden ser r (readable), w (writable) y x (executable); es decir, cada grupo puede tener activa la lectura, escritura y la ejecución de forma individual dependiendo de lo que puede hacer con el archivo.
El primer grupo, que en este caso es rwx, corresponde al dueño del fichero, el usuario (u) que aparece al lado, es decir, gaspy. En este caso, el usuario gaspy podrá leer, escribir y ejecutar el fichero.
El segundo grupo, que es r-x (como vemos, no tiene la w), corresponde a los permisos que tendrá cualquier usuario que pertenezca al grupo (g), es decir, cualquier usuario del grupo oficina sólo podrá leer y ejecutar el archivo.
El tercer grupo, r– (no tiene w, ni x), corresponde a los permisos que tendrá cualquier otro usuario del sistema (o), vamos un usuario que no sea el dueño, ni pertenezca al grupo oficina sólo podrá leer el archivo.

Bueno, no digo nada de root porque éste lo puede hacer todo, ya lo dice el dicho: “Cada user en su home y root en la de todos”.

Eso sí, como vemos, cada grupo puede tener cualquier combinación de rwx para un mismo archivo, cada elemento (rwx) corresponde con un bit, lo que hacen que los permisos se representen con 3bits. Además cada grupo puede tener ocho combinaciones diferentes de rwx (—, –x, -w-, -wx, r–, r-x, rw-, rwx) por lo que podemos representarlas por un número entre el 0 y el 7. Por lo tanto, muchas veces podremos representar los permisos con su forma numérica, incluso con 3 números, podríamos representar los permisos de los tres grupos. Por ejemplo, podría ser 754 (ningún número podrá ser mayor a 7).

Para cambiar los permisos de un archivo, podemos hacerlo con chmod:

chmod o+r mi_archivo

Con esta línea, daremos permiso de lectura a cualquier usuario sobre el archivo mi_archivo. También podremos hacer:

chmod 777 test

Ahora estaremos dando permiso de lectura/escritura/ejecución sobre el archivo test a todo el mundo.

SETUID y SETGID

Estos bits indican que vamos a ejecutar dicho archivo como si fuéramos el usuario y/o el grupo dueños del archivo. Por lo tanto podremos hacer cosas que tal vez el usuario normal no puede hacer. Y es común utilizar estos bits para hacer que un usuario ejecute tareas como root o como cualquier otro usuario que especifiquemos. Eso sí, debemos definir bien los permisos.
Vamos a empezar con un ejemplo sencillo, primero vamos a crear un programa en C que ponga en pantalla el usuario actual, en realidad debería indicar el usuario efectivo, este usuario efectivo será el que verifique el sistema operativo para casi todas las tareas. Normalmente coinciden, pero en este tipo de casos se separan los caminos de cada uno y aunque un programa realmente lo lanza un usuario sin privilegios, el usuario efectivo debería ser root.
El programa en C será el siguiente (muy sencillo, sin comprobación de errores y lo llamaremos quiensoy.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    struct passwd *pww;

    pww = getpwuid(geteuid());
    printf ("USER: %s\n",  pww->pw_name);

    return 0;
}

Ahora compilamos, y ejecutamos:

gcc -o quiensoy quiensoy.c
./quiensoy
USER: gaspy

Luego le damos privilegios (con el usuario www-data, por ejemplo. Podría ser root si queremos):

sudo chown www-data:www-data quiensoy
sudo chmod u+s quiensoy
./quiensoy
USER: www-data

Dejo aquí un ejemplo con el que podemos ver también el grupo, usuario real y demás.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <sys/types.h>
#include <unistd.h>

int main()
{
    struct passwd *pww;
    struct group *grp;

    pww = getpwuid(geteuid());
    printf ("USER EFECTIVO: %s\n",  pww->pw_name);

    pww = getpwuid(getuid());
    printf ("USER REAL: %s\n",  pww->pw_name);

    grp = getgrgid(getgid());
    printf ("GRUPO REAL: %s\n",  grp->gr_name);

    grp = getgrgid(getegid());
    printf ("GRUPO EFECTIVO: %s\n",  grp->gr_name);

    return 0;
}

Ahora si compilamos, establecemos permisos y damos SGID y SUID veremos lo que pasa:

gcc -o quiensoy quiensoy.c
sudo chown www-data:openvpn quiensoy
sudo chmod u+s quiensoy
sudo chmod g+s quiensoy
./quiensoy
USER EFECTIVO: www-data
USER REAL: gaspy
GRUPO REAL: gaspy
GRUPO EFECTIVO: openvpn

De este modo podremos ejecutar acciones como si fuéramos otro usuario. Aunque tenemos que tener cuidado con lo que dejamos hacer a la gente.

SUID y SGID en scripts

Esto supone una gran brecha de seguridad. Si, por ejemplo el usuario pudiera escribir el script o crear un enlace a un fichero que llame el script, o sustituir algún programa (por ejemplo, un programa que use which para buscar un ejecutable y creemos un equivalente en una ruta a la que sí tengamos acceso… o bueno, se nos pueden ocurrir mil cosas por las cuales esto no sería seguro. Así que muchos sistemas operativos tipo Unix no te van a dejar… bueno sí te van a dejar, pero harán caso omiso del SUID/SGID que le hayamos dado, incluyendo las últimas versiones del kernel Linux. Aunque existe un ataque típico que consiste en aprovecharse del tiempo que tarda el kernel en leer en #! de comienzo del script así como el intérprete y cargar todo en memoria para hacer el cambiazo del script.

De todas formas, si queremos comprometer un sistema es un buen punto para probar, porque podemos alcanzar privilegios de forma muy fácil.

Hasta hace relativamente poco, podíamos utilizar scripts en perl con SUID, pero las últimas versiones ya no lo permiten. Podemos pensar que App Packer para perl nos haría el trabajo sucio, ya que crea un ejecutable directamente con nuestro script. Pero, tras unas comprobaciones de seguridad que hacen los ejecutables no es posible hacerlo. Existe una versión parcheada, pero si no es para experimentar yo no la usaría.

Otra cosa que podemos hacer es utilizar scripts en Python y compilarlos con cython. Si no queremos hacer programas en C, que a veces es la solución más pesada y con un script podemos hacer las cosas mucho más rápido. Los ejecutables quedarán algo grandes, pero es lo que tiene utilizar otro lenguaje y luego traducirlo. Podemos hacer algo como esto:

1
2
3
4
5
6
#!/usr/bin/python3

import os
import pwd

print ("QUIÉN SOY? {}".format(pwd.getpwuid(os.geteuid()).pw_name))

Ahora compilamos, asignamos permisos y demás:

cython --embed -3 quiensoy.py
gcc -I/usr/include/python3.5m quiensoy.c -o quiensoy -lpython3.5m
sudo chown root:root quiensoy
sudo chmod u+s quiensoy
./quiensoy
QUIÉN SOY? root

Envolventes de scripts

No lo recomiendo, para nada. Pero alguna vez, y sobre todo para hacer experimentos, está muy bien. El objetivo es crear un programa en C que ejecute un script y lo haga con privilegios del usuario elegido. Podemos hacerlo de múltiples maneras, ya que es un programa muy pequeño que sólo se encarga de llamar a otro programa:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

int main( int argc, char ** argv )
{
              if( setgid(getegid()) ) perror( "setgid" );
              if( setuid(geteuid()) ) perror( "setuid" );

              execl("./miscript", "./miscript", (char *) 0);

              return errno;
}

Pero vamos, no es nada recomendable, un usuario podría llegar a cambiar el contenido de miscript. Por ejemplo, en este programa (vulnerable por definición) podría cambiar la ruta desde la que se ejecuta el programa y así ./miscript seguramente no sea quien creemos que es. O simplemente podemos atacar una llamada de comando falseando la variable $PATH o cualquier otra variable de entorno.

De todas formas utilizar una envoltura de este tipo es prácticamente igual a utilizar sudo, que debidamente configurado y actualizado podría ser incluso más seguro.

Seguridad

¡ Ataques ! ¡ Ataques !
Es cierto que ningún sistema es 100% libre de fallos, y que cuanto menos código o programas ejecutemos con privilegios, mejor para nosotros y nuestra seguridad (menos puntos de rotura del sistema). De todas formas hay varios ataques clásicos y típicos para los programas ejecutados con SETUID/SETGID. En casi todos los sistemas están arreglados pero, tal vez nos encontremos ante un Unix pequeño (sobre todo de sistemas empotrados) o incluso en nuestra facultad tengan un sistema Unix de hace unos cuantos años y tengamos la necesidad de romperlo.

El primero de los ataques es un ataque IFS. Si has curioseado shell scripts, habrás visto que IFS es el Internal Field Separator, es decir, el separador de campos, o palabras, o comandos, etc. Entonces podríamos crear un archivo llamado “bin”, hacerlo ejecutable y poner lo que queramos ejecutar con privilegios. Al mismo tiempo, asignaremos a IFS el valor ‘/‘ (la barra de separación de directorios), para que cuando una envoltura ejecute ./miscript y el script intente procesar el intérprete ejecute en realidad nuestro fichero bin. Afortunadamente no suele funcionar desde hace muchos años, pero claro, siempre hay implementaciones pequeñas para IOT donde puede que funcione.

Otro ataque importante es el de la precarga de bibliotecas. Normalmente como usuarios podemos sustituir las bibliotecas que se cargan cuando ejecutamos un programa. Esto es muy útil para solucionar problemas en ciertos programas (sobre todo si no disponemos del código fuente), mantener retrocompatibilidad con bibliotecas y algunas cosas más. Aunque si lo llevamos al terreno de las maldades, podríamos sustituir todas las llamadas a printf() por lo que nosotros queramos, como abrir un intérprete de comandos, por lo que nada más ejecutar el primer printf() el programa abrirá un bash/sh/dash/etc. Eso sí, si el programa tenía activado el SUID o SGID, la shell la abrirá con el usuario dueño del archivo, lo cual puede ser muy malo. Afortunadamente, en la mayoría de sistemas esto ya no es un problema.

Otro posible ataque es debido a bugs de los programas instalados. Por ejemplo, hace poco tiempo, la aplicación nmap, muy útil para auditoría de red tenía el bit SUID definido en algunas distribuciones Linux, con lo que podíamos acceder a un modo interactivo y ejecutar un intérprete de comandos, con lo que teníamos root en muy poco tiempo. ¿Necesita nmap ese SUID? Si estamos en un servidor, ¿necesitamos nmap?
De todas formas, es una buena práctica de seguridad tener un listado de todos los archivos con SUID o SGID. De la siguiente forma:

find / -user root -perm -u=s -type f
/usr/sbin/pppd
/usr/bin/pkexec
/usr/bin/nvidia-modprobe
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/newgrp

Y de la misma forma que ponemos -u=s para SUID, probaremos -g=s para SGID. Lo primero es preguntarnos, ¿necesitamos todo esto?. Si es nuestro trabajo estar al tanto de la seguridad del sistema, deberíamos revisar uno a uno todos los archivos que se pueden ejecutar, y no estaría de más crear un informe sobre cada uno de ellos, para revisarlo en el futuro.
Además, es importante hacer esto de forma periódica para ver que no hay cambios, o asegurarnos de que los cambios están controlados.

OverlayFS exploit. Podemos probarlo, afectaba desde Linux 3.13 hasta Linux 3.19. Podemos encontrarlo aquí. Sólo hay que compilarlo y veremos si el programa nos permite escalar privilegios

SUID/SGID en nuestro día a día

Seguramente hayas utilizado esta característica en el pasado casi sin darte cuenta. Ya que necesitaríamos privilegios especiales para escribir cualquier archivo sobre el que no tengamos permiso, por ejemplo como hace la orden passwd. Este comando, que sirva para cambiar nuestra contraseña de usuario deberá escribir sobre un archivo que ni tan solo podemos leer por cuestiones de seguridad, /etc/shadow y para hacerlo, necesitamos obligatoriamente permisos de superusuario. Si hacemos stat podremos ver:

stat /usr/bin/passwd
Fichero: ‘/usr/bin/passwd’
Tamaño: 54256      Bloques: 112        Bloque E/S: 4096   fichero regular
Dispositivo: 813h/2067d Nodo-i: 203318      Enlaces: 1
Acceso: (4755/-rwsr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Acceso: 2017-06-27 22:23:51.194148876 +0200
Modificación: 2017-05-17 01:37:35.000000000 +0200
Cambio: 2017-05-22 02:50:43.470092977 +0200
Creación: --

Por supuesto, sudo. Cómo si no se las apaña para cambiar el usuario antes de hacer la ejecución de un programa. Aunque tenemos que cumplir con la propia seguridad de sudo (archivo sudoers).

Ping… ¡ehh! ¿Por qué ping requiere privilegios? ping lo utilizamos muchas veces para probar la red, saber si tenemos conexión y saber el tiempo de respuesta de un servidor. Pero ping necesita acceso directo a los protocolos de red y eso sólo lo puede hacer root.

Otros comandos podrían ser mount / umount, mlocate y algunos más. Nosotros incluso podríamos crear algunos programas para la administración de nuestros servidores, toma de decisiones, y mucho más. Eso sí, con mucho cuidado, y estudiando las posibilidades de un usuario o malware a afectar nuestra máquina. A veces, ésta no siempre es la mejor solución.

Foto principal: Alex Knight

También podría interesarte...

Leave a Reply