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.