El día de hoy un colega se acercó a preguntarme si era posible utilizar los bits SETUID y SETGID para restringir el acceso a archivos de log creados por una aplicación lanzada por los usuarios. El escenario era el siguiente: una aplicación de escritorio, lanzada por el usuario, crea un registro de actividad en un archivo de log. La necesidad entonces era que el usuario no sea capaz de borrar o modificar luego estos archivos. Pero claro, al lanzar la aplicación el propio usuario, los archivos de log se crean a su nombre, por ende es el propietario de los mismos (lo que significa que puede hacer lo que le plazca con ellos). Entonces, el problema se reduce a lograr que la aplicación lanzada por el usuario cree los archivos de log a nombre de otro usuario. Esto se traduce a que la aplicación corra bajo un usuario diferente al usuario actual. Es aquí cuando puede ser útil recurrir a los bits SUID y SGID en la configuración de permisos del ejecutable.

Para explicar este problema voy a utilizar directamente un ejemplo.



Primero voy a crear un directorio de prueba dentro de /tmp:

root@debian:~# cd /tmp/
root@debian:/tmp# mkdir prueba
root@debian:/tmp# cd prueba/

Luego un pequeño programa escrito en C que simplemente escriba en un archivo "archivo.log", ubicado en el directorio actual:

root@debian:/tmp/prueba# nano programa.c

Código fuente del programa:

#include <stdio.h>
#define archivo "archivo.log"

int main() {

  FILE * f = fopen(archivo,"w");

  fprintf(f,"Hola mundo!\n");

  close(f);

}

Compilar el programa con gcc para generar el binario ejecutable:

root@debian:/tmp/prueba# gcc -o programa programa.c

Se observa que el dueño y grupo del binario creado es "root":

root@debian:/tmp/prueba# ll
total 12
-rwxr-xr-x 1 root root 6892 Oct 23 09:35 programa
-rw-r--r-- 1 root root  144 Oct 23 09:32 programa.c

El usuario actual es "root":

root@debian:/tmp/prueba# id
uid=0(root) gid=0(root) groups=0(root)

Si se ejecuta el programa con el usuario actual, "root", se observa que se crea el archivo a nombre de "root" (usuario y grupo):

root@debian:/tmp/prueba# ./programa
root@debian:/tmp/prueba# ll
total 16
-rw-r--r-- 1 root root   12 Oct 23 09:36 archivo.log
-rwxr-xr-x 1 root root 6892 Oct 23 09:35 programa
-rw-r--r-- 1 root root  144 Oct 23 09:32 programa.c

El contenido del archivo es correcto:

root@debian:/tmp/prueba# cat archivo.log
Hola mundo!

Ahora veamos qué pasa cuando se ejecuta en la sesión de otro usuario, por ejemplo "sysadmin". La idea es que "sysadmin" lance el programa, éste cree el archivo, pero luego "sysadmin" no lo pueda borrar ni modificar.

Primero es necesario borrar el archivo creado previamente y otorgar permisos de escritura para others en el directorio actual:

root@debian:/tmp/prueba# rm -f archivo.log
root@debian:/tmp/prueba# chmod 777 .

Abrir una sesión con el usuario "sysadmin":

root@debian:/tmp/prueba# su - sysadmin
sysadmin@debian:~$ cd /tmp/prueba/

Luego, ejecutar el programa nuevamente, esta vez como "sysadmin" en vez de "root":

sysadmin@debian:/tmp/prueba$ ./programa
sysadmin@debian:/tmp/prueba$ ls -l
total 16
-rw-r--r-- 1 sysadmin sysadmin   12 Oct 23 09:39 archivo.log
-rwxr-xr-x 1 root     root     6892 Oct 23 09:35 programa
-rw-r--r-- 1 root     root      144 Oct 23 09:32 programa.c

Se observa que ahora el archivo fue creado a nombre de "sysadmin", el usuario actual:

sysadmin@debian:/tmp/prueba$ id
uid=1003(sysadmin) gid=1003(sysadmin) groups=1003(sysadmin)

Lógicamente, al ser "sysadmin" el dueño del archivo, puede hacer cualquier cosa con él, por ejemplo borrarlo:

sysadmin@debian:/tmp/prueba$ rm archivo.log

Veamos que sucede al utilizar SUID y SGID.

Primero, cerrar la sesión de "sysadmin":

sysadmin@debian:/tmp/prueba$ exit
logout

Es posible alterar los bits SUID, SGID y STICKY utilizando tanto el modo simbólico como el octal. Para el modo simbólico, la letra s establece tanto el bit SETUID como SETGID. Por ejemplo el modo simbólico u+s establece el bit SETUID, mientras que g-s borra el SETGID. Para el modo octal es sencillo, en caso de utilizar 4 dígitos octales, el más significativo representa los bits de SETUID, SETGID y STICKY (protección de borrado) respectivamente. Por ejemplo, si el permiso de un archivo (expresado en modo octal) es 755, es posible setear SETUID, SETGID y borrar el STICKY mediante el modo 6755.

Setear entonces tanto el bit SETUID como el bit SETGID (sin STICKY) para el binario ejecutable, mediante el comando:

root@debian:/tmp/prueba# chmod 6755 programa
root@debian:/tmp/prueba# ll
total 12
-rwsr-sr-x 1 root root 6892 Oct 23 09:35 programa
-rw-r--r-- 1 root root  144 Oct 23 09:32 programa.c

Se observa que, donde antes había una letra x, ahora aparece la s (que significa ejecución más set ID de grupo/usuario).

Antes de iniciar nuevamente la sesión como "sysadmin", restaurar los permisos del directorio actual (quitar permiso de escritura para los demás):

root@debian:/tmp/prueba# chmod 755 .

Iniciar nuevamente una sesión con "sysadmin", y cambiar al directorio de prueba:

root@debian:/tmp/prueba# su - sysadmin
sysadmin@debian:~$ cd /tmp/prueba/

Ejecutar el programa que ahora tiene SETUID y SETGID habilitados:

sysadmin@debian:/tmp/prueba$ ./programa

Notar que "sysadmin" no tiene permisos de escritura en el directorio, pues fueron quitados previamente. Por ende el programa no podría crear el archivo, sin embargo:

sysadmin@debian:/tmp/prueba$ ls -l
total 16
-rw-r--r-- 1 root root   12 Oct 23 09:43 archivo.log
-rwsr-sr-x 1 root root 6892 Oct 23 09:35 programa
-rw-r--r-- 1 root root  144 Oct 23 09:32 programa.c

Se observa que el archivo ha sido creado, pero a nombre de "root". Esto se debe a que, cuando un ejecutable tiene SETUID, en lugar de lanzarse el proceso a nombre del usuario actual ("sysadmin"), se lanza a nombre del usuario dueño del ejecutable ("root"). Este se denomina usuario efectivo del proceso. Lo mismo sucede con el grupo. Por esta razón, el archivo se crea a nombre de "root", el usuario efectivo del proceso.

De esta forma, si ahora "sysadmin" intenta borrar o eliminar el archivo, no puede hacerlo pues no es el dueño (y no tiene permiso de escritura sobre el archivo):

sysadmin@debian:/tmp/prueba$ rm archivo.log
rm: remove write-protected regular file `archivo.log'? y
rm: cannot remove `archivo.log': Permission denied

Lógicamente, la máscara por defecto (en la mayoría de las distribuciones GNU/Linux) es 0022, lo cual significa que los archivos se crean siempre sin permisos de escritura para el grupo y los demás (sólo para el usuario):

sysadmin@debian:/tmp/prueba$ umask
0022

Una prueba adicional para reforzar el concepto

Para comprobar esta configuración, una prueba más.

Cerrar la sesión de "sysadmin":

sysadmin@debian:/tmp/prueba$ exit
logout

Editar el programa para que, luego de escribir el archivo, entre en un bucle infinito:

root@debian:/tmp/prueba# nano programa.c
#include <stdio.h>
#define archivo "archivo.log"

int main() {

  int i;

  FILE * f = fopen(archivo,"w");

  fprintf(f,"Hola mundo!\n");

  fflush(f);
  close(f);

  while (1) {
    i+1;
  }
}

Compilar y setear nuevamente SUID/GID:

root@debian:/tmp/prueba# gcc -o programa programa.c
root@debian:/tmp/prueba# chmod 6755 programa
root@debian:/tmp/prueba# ls -l
total 12
-rwsr-sr-x 1 root root 6892 Oct 23 09:48 programa
-rw-r--r-- 1 root root  181 Oct 23 09:48 programa.c

Como usuario "root", lanzar el programa en segundo plano (pues se quedará ciclando indefinidamente):

root@debian:/tmp/prueba# ./programa &
[1] 11700

El proceso "programa" corre como usuario y grupo "root" efectivos:

root@debian:/tmp/prueba# ps -o euser,ruser,suser,fuser,comm
EUSER    RUSER    SUSER    FUSER    COMMAND
root     root     root     root     bash
root     root     root     root     programa
root     root     root     root     ps
root@debian:/tmp/prueba# ps u
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      1926  0.0  0.0   5932   640 tty2     Ss+  Oct14   0:00 /sbin/getty 38400 tty2
root      1927  0.0  0.0   5932   636 tty3     Ss+  Oct14   0:00 /sbin/getty 38400 tty3
root      1928  0.0  0.0   5932   632 tty4     Ss+  Oct14   0:00 /sbin/getty 38400 tty4
root      1929  0.0  0.0   5932   640 tty5     Ss+  Oct14   0:00 /sbin/getty 38400 tty5
root      1930  0.0  0.0   5932   636 tty6     Ss+  Oct14   0:00 /sbin/getty 38400 tty6
root      7080  0.0  0.0   5932   640 tty1     Ss+  Oct15   0:00 /sbin/getty 38400 tty1
root     10636  0.0  0.3  20864  3788 pts/0    Ss   09:02   0:00 -bash
root     11700 99.7  0.0   3852   444 pts/0    R    09:49   1:54 ./programa
root     11730  0.0  0.1  16340  1172 pts/0    R+   09:51   0:00 ps u

Terminar el programa y eliminar el archivo creado:

root@debian:/tmp/prueba# kill 11700
root@debian:/tmp/prueba# rm -f archivo.log

A continuación, volver a abrir una sesión como "sysadmin":

root@debian:/tmp/prueba# su - sysadmin
sysadmin@debian:~$ cd /tmp/prueba/

Lanzar nuevamente el programa, esta vez como usuario "sysadmin":

sysadmin@debian:/tmp/prueba$ ./programa &
[1] 11905

Notar cómo, a pesar de haber iniciado el programa como usuario "sysadmin", el usuario efectivo con el que corre el proceso es "root", gracias al bit SETUID:

sysadmin@debian:/tmp/prueba$ ps aux | grep programa
root     11905  102  0.0   3852   444 pts/0    R    09:58   0:14 ./programa
sysadmin 11907  0.0  0.0   7548   856 pts/0    S+   09:58   0:00 grep programa

Lo cual hace que los archivos creados por el proceso en cuestión sean propiedad de "root":

sysadmin@debian:/tmp/prueba$ kill 11905
sysadmin@debian:/tmp/prueba$ ls -l
total 12
-rw-r--r-- 1 root root   12 Oct 23 09:58 archivo.log
-rwsr-sr-x 1 root root 6892 Oct 23 09:48 programa
-rw-r--r-- 1 root root  181 Oct 23 09:48 programa.c
[1]+  Terminated              ./programa

Y gracias a que la máscara de creación de archivos es 0022, los demás usuarios no tienen acceso de escritura sobre los mismos.

SETGID

El bit SGID (set group id) tal vez no sea de gran utilidad, cumple la misma función que SUID, pero para controlar el grupo.

La diferencia es simple. Primero borremos el último archivo creado:

root@debian:/tmp/prueba# rm archivo.log 
rm: remove regular file `archivo.log'? y
root@debian:/tmp/prueba# ll
total 12
-rwsr-sr-x 1 root root 7024 Oct 23 10:00 programa
-rw-r--r-- 1 root root  194 Oct 23 10:00 programa.c

Para setear sólo el bit SUID, utilizar el dígito octal 4 (100 en binario, el cual corresponde a SUI=1, SGID=0 y STICKY=0, se codifica como 4 en base octal):

root@debian:/tmp/prueba# chmod 4755 programa
root@debian:/tmp/prueba# ll
total 12
-rwsr-xr-x 1 root root 7024 Oct 23 10:00 programa
-rw-r--r-- 1 root root  194 Oct 23 10:00 programa.c

Notar que aparece la letra s sólo para el usuario (ya no para el grupo).

Cambiar a "sysadmin":

root@debian:/tmp/prueba# su - sysadmin
sysadmin@debian:~$ cd /tmp/prueba/

Ejecutar y terminar el programa con Ctrl+C:

sysadmin@debian:/tmp/prueba$ ./programa 
^C
sysadmin@debian:/tmp/prueba$ ls -l
total 16
-rw-r--r-- 1 root sysadmin   12 Oct 23 10:05 archivo.log
-rwsr-xr-x 1 root root     7024 Oct 23 10:00 programa
-rw-r--r-- 1 root root      194 Oct 23 10:00 programa.c

Se observa que esta vez el propietario del archivo "archivo.log" coincide con el usuario efectivo del proceso ("root"), mientras que el grupo corresponde al grupo al que pertenece el usuario actual ("sysadmin").

Ahora bien, esta implementación es un tanto precaria, pues existen diferentes vulnerabilidades y factores a tener en cuenta:

  • Primero lo más importante, poner una aplicación a nombre de "root" es entregarle el control total sobre nuestro sistema, lo cual puede tener consecuencias devastadoras. Es como correr una aplicación en Windows como "Administrator". Lo correcto es que esté a nombre de otro usuario, que no tenga privilegios. Más aún, es recomendable crear un usuario específico para el programa. Lo importante es que el nombre de usuario con el que corra el programa sea distinto al nombre de usuario del usuario que lanza la aplicación.
  • Además, si no se establecen correctamente los permisos sobre el programa (o cualquier otro programa que tenga el mismo SETUID), el usuario podría reemplazar el programa con otra aplicación que le permita borrar o modificar los logs. Específicamente se debe quitar todo permiso de escritura sobre el binario ejecutable.
  • Por otro lado está el problema de los scripts (Bash, Perl, etc.) y lenguajes interpretados (Java, Python, etc.). Éstos no son ejecutados directamente, sino ejecutados por un intérprete o máquina virtual (Java). Lo cual implica que el SETUID y modificación de owner debe realizarse sobre el binario del intérprete o máquina virtual. Existen dos o tres implicancias graves al respecto. Primero, todas las aplicaciones o scripts lanzados correrán bajo el mismo usuario, lo que implica (además de ser una desprolijidad) que la seguridad se pierde totalmente. Esto se debe a que nada impide al usuario crear y lanzar un script/aplicación (utilizando el intérprete/máquina virtual en cuestión) que le permita acceder a los archivos que se intenta proteger. Obviamente el usuario tiene que tener conocimientos de programación para llevar a cabo esta tarea. Segundo, ante una actualización del intérprete o máquina virtual, el gestor de paquetes puede sobrescribir los permisos sobre los binarios, teniendo que setear nuevamente los bit SETUID cada vez que se actualiza. Una opción es utilizar una copia del intérprete/máquina virtual específica para lanzar la aplicación, que tenga los permisos seteados correctamente. Sin embargo se dificulta el mantenimiento, pues o se deja sin actualizar la copia del intérprete, o cada vez que se actualiza se debe copiar y reconfigurar.

Lo mejor es utilizar sudo con un permiso específico, de forma similar a como expliqué en el artículo Cómo permitir que un usuario pueda ejecutar como root sólo un comando específico utilizando sudo (obviamente utilizando otro usuario en lugar de "root"). Aunque de todas formas, ésto sirve para entender los riesgos e implicancias de utilizar los bits SETUID y SETGID en sistemas Unix.


Tal vez pueda interesarte



Compartí este artículo y dejá tu comentario