Capabilities - Compartimentar al todopoderoso root

El esquema tradicional de los entornos Linux/UNIX dividen los procesos en dos categorías, los que tienen el usuario ID 0 (root, o superusuario) y tiene todos los privilegios de acceso y ejecución, y el resto, sujetos al chequeo de privilegios y con la incapacidad de realizar ciertas tareas. La función de capabilities en GNU/Linux es dividir los privilegios disponibles para los procesos que se ejecutan como usuario root en grupos más pequeños de privilegios.

De esta manera un proceso puede ejecutarse con privilegios de usuario pero con ciertas capabilities que le permiten realizar unas tareas especificas. Anterior a esto la única opción para que un usuario pudiera realizar tareas de superusuario era set-UserID o set-GroupID que vimos cuando hablamos de permisos en GNU/Linux, pero esto siempre es un riesgo de seguridad. Si un atacante logra comprometer el proceso a través de un desbordamiento de buffer o una explotación similar, tendría acceso a todo el sistema (también si la aplicación falla por cualquier motivo).

La lista de capabilities disponibles en nuestro sistema la tenemos en el fichero linux/capability.h del que pongo un extracto correspondiente a un sistema con kernel 5.7:

 /** 
 ** POSIX-draft defined capabilities. 
 **/ 
 
/* In a system with the [_POSIX_CHOWN_RESTRICTED] option defined, this 
    overrides the restriction of changing file ownership and group 
    ownership. */ 
 
#define CAP_CHOWN 0 
 
/* Override all DAC access, including ACL execute access if 
    [_POSIX_ACL] is defined. Excluding DAC access covered by 
    CAP_LINUX_IMMUTABLE. */ 
 
#define CAP_DAC_OVERRIDE 1 
 
/* Overrides all DAC restrictions regarding read and search on files 
    and directories, including ACL restrictions if [_POSIX_ACL] is 
    defined. Excluding DAC access covered by CAP_LINUX_IMMUTABLE. */ 
 
#define CAP_DAC_READ_SEARCH 2 
 
/* Overrides all restrictions about allowed operations on files, where 
    file owner ID must be equal to the user ID, except where CAP_FSETID 
    is applicable. It doesn't override MAC and DAC restrictions. */ 
 
#define CAP_FOWNER 3 
 
/* Overrides the following restrictions that the effective user ID 
    shall match the file owner ID when setting the S_ISUID and S_ISGID 
    bits on that file; that the effective group ID (or one of the 
    supplementary group IDs) shall match the file owner ID when setting 
    the S_ISGID bit on that file; that the S_ISUID and S_ISGID bits are 
    cleared on successful return from chown(2) (not implemented). */ 
 
#define CAP_FSETID 4 
 
/* Overrides the restriction that the real or effective user ID of a 
    process sending a signal must match the real or effective user ID 
    of the process receiving the signal. */ 
 
#define CAP_KILL 5 
 
/* Allows setgid(2) manipulation */ 
/* Allows setgroups(2) */ 
/* Allows forged gids on socket credentials passing. */ 
 
#define CAP_SETGID 6 
 
/* Allows set*uid(2) manipulation (including fsuid). */ 
/* Allows forged pids on socket credentials passing. */ 
 
#define CAP_SETUID 7 
 
 
/** 
 ** Linux-specific capabilities 
 **/ 
 
/* Without VFS support for capabilities: 
 * Transfer any capability in your permitted set to any pid, 
 * remove any capability in your permitted set from any pid 
 * With VFS support for capabilities (neither of above, but) 
 * Add any capability from current's capability bounding set 
 * to the current process' inheritable set 
 * Allow taking bits out of capability bounding set 
 * Allow modification of the securebits for a process 
 */ 
 
#define CAP_SETPCAP 8 
 
/* Allow modification of S_IMMUTABLE and S_APPEND file attributes */ 
 
#define CAP_LINUX_IMMUTABLE 9 
 
/* Allows binding to TCP/UDP sockets below 1024 */ 
/* Allows binding to ATM VCIs below 32 */ 
 
#define CAP_NET_BIND_SERVICE 10 
 
/* Allow broadcasting, listen to multicast */ 
 
#define CAP_NET_BROADCAST 11 
 
/* Allow interface configuration */ 
/* Allow administration of IP firewall, masquerading and accounting */ 
/* Allow setting debug option on sockets */ 
/* Allow modification of routing tables */ 
/* Allow setting arbitrary process / process group ownership on 
    sockets */ 
/* Allow binding to any address for transparent proxying (also via NET_RAW) */ 
/* Allow setting TOS (type of service) */ 
/* Allow setting promiscuous mode */ 
/* Allow clearing driver statistics */ 
/* Allow multicasting */ 
/* Allow read/write of device-specific registers */ 
/* Allow activation of ATM control sockets */ 
 
#define CAP_NET_ADMIN 12 
 
/* Allow use of RAW sockets */ 
/* Allow use of PACKET sockets */ 
/* Allow binding to any address for transparent proxying (also via NET_ADMIN) */ 
 
#define CAP_NET_RAW 13 
 
/* Allow locking of shared memory segments */ 
/* Allow mlock and mlockall (which doesn't really have anything to do 
    with IPC) */ 
 
#define CAP_IPC_LOCK 14 
 
/* Override IPC ownership checks */ 
 
#define CAP_IPC_OWNER 15 
 
/* Insert and remove kernel modules - modify kernel without limit */ 
#define CAP_SYS_MODULE 16 
 
/* Allow ioperm/iopl access */ 
/* Allow sending USB messages to any device via /dev/bus/usb */ 
 
#define CAP_SYS_RAWIO 17 
 
/* Allow use of chroot() */ 
 
#define CAP_SYS_CHROOT 18 
 
/* Allow ptrace() of any process */ 
 
#define CAP_SYS_PTRACE 19 
 
/* Allow configuration of process accounting */ 
 
#define CAP_SYS_PACCT 20 
 
/* Allow configuration of the secure attention key */ 
/* Allow administration of the random device */ 
/* Allow examination and configuration of disk quotas */ 
/* Allow setting the domainname */ 
/* Allow setting the hostname */ 
/* Allow calling bdflush() */ 
/* Allow mount() and umount(), setting up new smb connection */ 
/* Allow some autofs root ioctls */ 
/* Allow nfsservctl */ 
/* Allow VM86_REQUEST_IRQ */ 
/* Allow to read/write pci config on alpha */ 
/* Allow irix_prctl on mips (setstacksize) */ 
/* Allow flushing all cache on m68k (sys_cacheflush) */ 
/* Allow removing semaphores */ 
/* Used instead of CAP_CHOWN to "chown" IPC message queues, semaphores 
    and shared memory */ 
/* Allow locking/unlocking of shared memory segment */ 
/* Allow turning swap on/off */ 
/* Allow forged pids on socket credentials passing */ 
/* Allow setting readahead and flushing buffers on block devices */ 
/* Allow setting geometry in floppy driver */ 
/* Allow turning DMA on/off in xd driver */ 
/* Allow administration of md devices (mostly the above, but some 
    extra ioctls) */ 
/* Allow tuning the ide driver */ 
/* Allow access to the nvram device */ 
/* Allow administration of apm_bios, serial and bttv (TV) device */ 
/* Allow manufacturer commands in isdn CAPI support driver */ 
/* Allow reading non-standardized portions of pci configuration space */ 
/* Allow DDI debug ioctl on sbpcd driver */ 
/* Allow setting up serial ports */ 
/* Allow sending raw qic-117 commands */ 
/* Allow enabling/disabling tagged queuing on SCSI controllers and sending 
    arbitrary SCSI commands */ 
/* Allow setting encryption key on loopback filesystem */ 
/* Allow setting zone reclaim policy */ 
 
#define CAP_SYS_ADMIN 21 
 
/* Allow use of reboot() */ 
 
#define CAP_SYS_BOOT 22 
 
/* Allow raising priority and setting priority on other (different 
    UID) processes */ 
/* Allow use of FIFO and round-robin (realtime) scheduling on own 
    processes and setting the scheduling algorithm used by another 
    process. */ 
/* Allow setting cpu affinity on other processes */ 
 
#define CAP_SYS_NICE 23 
 
/* Override resource limits. Set resource limits. */ 
/* Override quota limits. */ 
/* Override reserved space on ext2 filesystem */ 
/* Modify data journaling mode on ext3 filesystem (uses journaling 
    resources) */ 
/* NOTE: ext2 honors fsuid when checking for resource overrides, so 
    you can override using fsuid too */ 
/* Override size restrictions on IPC message queues */ 
/* Allow more than 64hz interrupts from the real-time clock */ 
/* Override max number of consoles on console allocation */ 
/* Override max number of keymaps */ 
/* Control memory reclaim behavior */ 
 
#define CAP_SYS_RESOURCE 24 
 
/* Allow manipulation of system clock */ 
/* Allow irix_stime on mips */ 
/* Allow setting the real-time clock */ 
 
#define CAP_SYS_TIME 25 
 
/* Allow configuration of tty devices */ 
/* Allow vhangup() of tty */ 
 
#define CAP_SYS_TTY_CONFIG 26 
 
/* Allow the privileged aspects of mknod() */ 
 
#define CAP_MKNOD 27 
 
/* Allow taking of leases on files */ 
 
#define CAP_LEASE 28 
 
/* Allow writing the audit log via unicast netlink socket */ 
 
#define CAP_AUDIT_WRITE 29 
 
/* Allow configuration of audit via unicast netlink socket */ 
 
#define CAP_AUDIT_CONTROL 30 
 
#define CAP_SETFCAP 31 
 
/* Override MAC access. 
    The base kernel enforces no MAC policy. 
    An LSM may enforce a MAC policy, and if it does and it chooses 
    to implement capability based overrides of that policy, this is 
    the capability it should use to do so. */ 
 
#define CAP_MAC_OVERRIDE 32 
 
/* Allow MAC configuration or state changes. 
    The base kernel requires no MAC configuration. 
    An LSM may enforce a MAC policy, and if it does and it chooses 
    to implement capability based checks on modifications to that 
    policy or the data required to maintain it, this is the 
    capability it should use to do so. */ 
 
#define CAP_MAC_ADMIN 33 
 
/* Allow configuring the kernel's syslog (printk behaviour) */ 
 
#define CAP_SYSLOG 34 
 
/* Allow triggering something that will wake the system */ 
 
#define CAP_WAKE_ALARM 35 
 
/* Allow preventing system suspends */ 
 
#define CAP_BLOCK_SUSPEND 36 
 
/* Allow reading the audit log via multicast netlink socket */ 
 
#define CAP_AUDIT_READ 37 
 
#define CAP_LAST_CAP CAP_AUDIT_READ 

Podemos encontrar información detallada en man capabilities.

Capabilities en procesos

Por cada proceso (en realidad por cada hilo) el sistema GNU/Linux tiene cinco conjuntos (implementado como una máscara de 64 bits) con las capabilities asignadas en cada uno de ellos, que podemos inspeccionar leyendo /proc/<pid>/status:

CapInh:    0000000000000000
CapPrm: 0000000000002000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000

Que corresponden a:

  • Heredables (CapInh): Son las capabilities que un proceso hijo puede heredar
  • Permitidas (CapPrm): Las capabilities que un proceso puede emplear
  • Efectivas (CapEff): Las capabilities actualmente efectivas en un proceso. Un proceso con una capability permitida puede deshabilitarla temporalmente no siendo efectiva entonces en el proceso
  • Delimitación (bounding) (CapBnd): Se usa para limitar las capabilities que se obtienen durante un execve()
  • Ambiente (CapAmb): Este es un conjunto de capabilities que se conservan en un execve() de un programa que no tiene privilegios. Las capabilities ambientales se agregan al conjunto permitido y se asignan al conjunto efectivo cuando se llama a execve()

Capabilities en ficheros

Un fichero puede tener asociadas capabilities y estas serán usadas para determinar las que tendrá el proceso si se ejecuta el fichero. Existen tres tipos:

  • Permitidas: Es el conjunto de capabilities que se añadiran al proceso durante la ejecución de execve()
  • Efectivas: No es un conjunto sino un solo bit. Si se establece este bit, durante un execve(), todas las nuevas capabilities permitidas para el hilo también se incluirán en el conjunto efectivo. Si no se establece este bit, después de un execve(), el conjunto de capacidades efectivas inicial estará vacio
  • Heredables: Con este conjunto se hace un AND con el conjunto heredable del subproceso para determinar qué capabilities están habilitadas en el conjunto permitidas del subproceso después del execve()

Las capabilities asociadas a un fichero se almacenan en los atributos extendidos de seguridad namespace security como vimos al tratar los atributos extendidos.

Podemos ver estos atributos con getfattr:

$ getfattr -m - -d /usr/bin/ping
getfattr: Eliminando «/» inicial en nombres de ruta absolutos
# file: usr/bin/ping
security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=

O listar las capabilities en un formato más legible con getcap:

$ getcap /usr/bin/ping
/usr/bin/ping = cap_net_raw+ep

Támbien con la aplicación infofile, que hicimos para acceder por código a los permisos y atributos de los ficheros:

$ infofile /usr/bin/ping
[+] Tipo de fichero: fichero regular
Número de hard links: 1
ID del dispositivo que contiene el i-node: fc:05
Número de I-node: 1704858
Tamaño total: 72776 bytes
Tamaño óptimo de bloque I/O: 4096 bytes
Bloques de 512B asignados: 144
[+] Permisos absoluto/simbólico: 100755 (rwx|r-x|r-x)
Propietario: UID=0 (root) | GID=0 (root)
[+] Atributos extra soportados: (0x00000874)
(c):Contenido comprimido
(i):Inmutable, no están permitidos los cambios
(a):Solo permite añadir
(d):No hacer backup
(E):Encriptado
[+] Atributos extra activos: (0x00000000)
[+] i-node flags: (0x00080000)
(e):Extensiones
[+] Fechas
Ultimo acceso: vie 31 ene 2020 00:11:23.000000000
Ultima modificación: vie 31 ene 2020 00:11:23.000000000
Ultimo cambio de status: dom 26 abr 2020 18:12:54.967403876
Fecha de creación: dom 26 abr 2020 18:12:54.963402247
[+] Atributos extendidos:
security.capability
= cap_net_raw+ep

Herencia de capabilities durante la ejecución de un programa

Teniendo en cuenta las diferentes capabilities que puede tener un proceso y un fichero, vamos a ver como se seleccionan las capabilities de los nuevos procesos. Cuando execve() lanza un nuevo proceso, el kernel selecciona un nuevo conjunto de capabilities para él teniendo en cuenta las propias del proceso que ejecuta execve() y las del fichero que ejecutamos, siguiendo las siguientes reglas (man capabilities):

P'(ambiente) = (fichero con privilegios) ? 0 : P(ambiente)

P'(permitida) = (P(heredable) & F(heredable)) |
(F(permitida) & P(delimitación)) | P'(ambiente)

P'(efectiva) = F(efectiva) ? P'(permitida) : P'(ambiente)

P'(heredable) = P(heredable) [es decir, sin cambios]

P'(delimitación) = P(delimitación) [es decir, sin cambios]

donde:

  • P() indica el valor de un conjunto de capabilities de un subproceso antes de execve()
  • P'() inica el valor del conjunto de capabilities de un subproceso después de execve()
  • F() indica el conjunto de capabilities de un fichero
  • Un fichero con privilegios es uno que tiene capabilities o tiene activado el bit set-user-ID o set-group-ID
  • & operador binario AND
  • | operador binario OR
  • ?: operador condicional (condición ? si_TRUE : si_FALSE

Uso de capabilities

Vamos a ver el uso de capabilities con unas sencillas aplicaciones que abren un puerto y esperan conexiones (server). Cuando el cliente (client) se conecta le devuelve una cadena. Se incluyen 2 versiones de server: server y server_cap, el primero no tiene en cuenta las capabilities y el segundo usa la API lipcap para levantar la capability que necesita y luego la baja.

Descargar código fuente: prac_capabilities-0.5.tar.gz
Firma sha256sum: 728459af760f190562f510533a9da100b775d668b11beda96ed4fae961d11545

Bajamos y creamos:

$ wget https://clibre.io/files/prac_capabilities-0.5.tar.gz 
$ sha256sum prac_prac_capabilities-0.5.tar.gz
728459af760f190562f510533a9da100b775d668b11beda96ed4fae961d11545 prac_capabilities-0.5.tar.gz
$ tar xvfz prac_capabilities-0.5.tar.gz
$ cd prac_capabilities/
$ make

Para construir e instalar estas aplicaciones necesitamos la libreria libcap.
En derivados de Debian/Ubuntu podemos instalarla con:

sudo apt install libcap-dev

Si ejecutamos el programa para que escuche en un puerto por encima de 1024 no tendremos problema:

$ ./server 1973
Socket creado!

Conectamos desde otra consola:

$ ./client 127.0.0.1 1973
Socket creado!
Connectado a 127.0.0.1:1973!
38: CLIBRE - ComunidadLibre SoftwareLibre

Pero si intentamos utilizar un puerto por debajo de 1024:

$ ./server 21
Socket creado!
[server.c:76]:
[+] Error en Bind
[+] errno: [Permiso denegado]
saliendo...

Como vemos [Permiso denegado] nos indica que no tenemos privilegios para usar este puerto.

Si ejecutamos como root, no tendremos problemas:

$ sudo ./server 21
[sudo] contraseña para david:
Socket creado!

y también podemos poner la propiedad del fichero como root y activar set-user-ID (hago primero una copia de server como serverSUID):

$ cp server serverSUID
$ sudo chown root serverSUID
$ sudo chmod +s serverSUID
$ ls serverSUID -ll
-rwsrwsr-x 1 root david 19728 jun 5 18:20 serverSUID

Ahora si podemos usar un puerto privilegiado (<1024):

$ ./serverSUID 21
Socket creado!

Pero el servicio esta corriendo como root:

$ ps aux|grep serverSUID
root 30423 0.0 0.0 15576 772 pts/5 S+ 18:22 0:00 ./serverSUID 21

Para evitarlo vamos a darle al fichero la capability que permite usar puertos privilegiados, que como podemos ver en la lista de arriba es CAP_NET_BIND_SERVICE.

Comprobamos con getcap las capabilities que tiene actualmente server:

$ getcap server

No obtenemos ninguna salida. Añadimos ahora CAP_NET_BIND_SERVICE con setcap:

$ sudo setcap 'CAP_NET_BIND_SERVICE+p' server
$ getcap server
server = cap_net_bind_service+p

Como vemos al comprobar con getcap, ahora tenemos la capability añadida al fichero server.

Si ejecutamos ahora server en un puerto privilegiado:

./server 21
Socket creado!
[server.c:76]:
[+] Error en Bind
[+] errno: [Permiso denegado]
saliendo...

Obtenemos un error de permisos igualmente. El problema esta en que aunque el proceso tiene la capability necesaria permitida, nuestro programa server no tiene idea de capabilities y por tanto no la hace efectiva. Para estos casos, como hemos visto en las capabilities de ficheros tenemos el bit efectivas. Al habilitarlo todas las nuevas capabilities permitidas para el hilo también se incluirán en el conjunto efectivo.

Vamos a habilitarlo entonces:

$ sudo setcap 'CAP_NET_BIND_SERVICE+ep' server
$ getcap server
server = cap_net_bind_service+ep

Ahora vemos que tenemos la capability permitida y también el bit efectivo. Probamos:

$ ./server 21
Socket creado!

Comprobamos que funciona perfectamente, y si miramos el usuario con el que corre:

ps aux|grep /server 
david 30753 0.0 0.0 15576 804 pts/5 S+ 18:30 0:00 ./server 21

Vemos que no es root, sino el usuario que lanza la aplicación.

La capability esta efectiva durante toda la ejecución del programa. Pero el objetivo de una aplicación que sepa trabajar con capabilities es que ésta este solo efectiva cuando sea necesario. Para este caso vamos a ver la otra versión de sever: server_cap que le añadimos esta funcionalidad.

Solo levantamos la capability CAP_NET_BIND_SERVICE antes de llamar a bind que es cuando es necesario el privilegio, y justo despues la bajamos, de tal manera que no queda efectiva:

    ifError (levantarCap(CAP_NET_BIND_SERVICE) == -1, "Error en raiseCap()\n"); 
 
    /* hacemos bind en la dirección y puerto */ 
    returnStatus = bind(conSocket,(struct sockaddr *)&conServer,sizeof(conServer)); 
    ifError (returnStatus != 0, "Error en Bind\n"); 
 
    ifError (dropAllCaps() == -1, "Error en dropAllCaps()\n"); 

Le vamos a asignar a nuestra aplicación la capability que permite usar puertos privilegiados:

$ sudo setcap 'CAP_NET_BIND_SERVICE+p' server_cap
$ getcap server_cap
server = cap_net_bind_service+p

Como vemos al comprobar con getcap, ahora tenemos la capability añadida al fichero server_cap. Pero no asignamos el bit que la hace efectiva, esto recae en la aplicación.

Probamos entonces server_cap en un puerto privilegiado:

$ ./server_cap 21
Socket creado!

Y efectivamente funciona, y la capability solo se usa cuando es necesaria dentro de la aplicación.

Tenemos que tener en cuenta que si actualizamos o sobrescribimos un fichero que tenga asignada una capability esta se pierde

Conclusión

Creo que podemos hacernos una idea de la funcionalidad y necesidad de las capabilities. Es necesario, de cualquier forma, probar y revisar bien las opciones de herencia para saber evaluar correctamente el uso efectivo de estas. Podemos encontrarnos en casos donde no es fácil averiguar las capabilities necesarias para que una aplicación pueda realzar una tarea concreta. En este caso podemos utilizar por ejemplo strace() para ver las llamadas que producen los errores, normalmente del tipo EPERM.

De cualquier forma, para poder explotar esta funcionalidad completamente es necesario que las aplicaciones hagan uso de ella, levantando y quitando las capabilities necesarias en cada momento. Las nuevas versiones de aplicaciones van haciendo uso de estas mejoras de seguridad que se van incorporando en las nuevas versiones de las distros, obteniendo de esta forma un sistema notablemente más robusto.

Modificado por última vez enSábado, 26 Septiembre 2020 15:36
(3 votos)
Etiquetado como :

Deja un comentario

Asegúrese de introducir toda la información requerida, indicada por un asterisco (*). No se permite código HTML.