はじめに
こんにちは, 株式会社Acompanyインターン生の湯浅です。
名古屋大学の学部4年生です。もうすぐ卒業するので, ワクワクです。
本記事はOPTIMIND x Acompany Advent Calendar 2021の15日目の記事となります。
本記事の内容
本記事では, Dockerコンテナを介して攻撃者にボコボコにされないために満たすべき事項を紹介します。
この記事を読めば, Dockerコンテナ経由でホストマシンの管理者権限が獲得される事態は避けられます(きっと)。
コンテナを介してホストマシンが侵害される図 https://t.co/14taCg6LJV
— yuasa (@junkitombob) December 13, 2021
満たすべき事項
まず, コンテナを介してホストマシンに侵入されないために, 私が満たすべきだと考える事項を以下に挙げます。
-
--privileged
オプションを使用しない。 - docker socketをコンテナ内にマウントしない。
- コンテナ内でrootユーザーを使用しない。
- セキュリティオプション
no-new-privileges
を使用する。 - Rootlessモードを使用する。
満たすべき事項の詳細
上に挙げた満たすべき事項の詳細を説明します。
1. --privileged
オプションを使用しない
公式ドキュメントにもあるように, docker run --privileged
を実行すると, Dockerはホスト上の全てのデバイスに対して接続可能になります。
--privileged
オプションを使用して全ての特権を所有するコンテナを作成し, デバイスドライバを表示します。
# 全ての特権を所有するコンテナ
$ docker run --rm -it --privileged ubuntu bash
root@6beaf87cdae2:/# fdisk -l # 全てのデバイスドライバを表示可能
Disk /dev/loop0: 24.10 MiB, 26189824 bytes, 51152 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
Disk /dev/loop1: 55.5 MiB, 58183680 bytes, 113640 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
...
Device Boot Start End Sectors Size Id Type
/dev/xvda1 * 2048 16777182 16775135 8G 83 Linux
ホストのファイルシステム/dev/xvda1
をコンテナにマウントします。その前にホストマシンでroot権限でファイルを作成しておきます。
$ sudo touch /tmp/hoge
$ ls -la /tmp/hoge
-rw-r--r-- 1 root root 0 Dec 15 07:43 /tmp/hoge
$ cat /tmp/hoge
hogehoge
次にホストのファイルシステム/dev/xvda1
をコンテナにマウントします。--privileged
オプションで作成した特権付きコンテナを使用すると, 簡単にホストマシンの(ほぼ)管理者権限を得ることができます。
root@0f939b1206ed:/# mkdir -p /mnt/hoge
root@0f939b1206ed:/# mount /dev/xvda1 /mnt/hoge # マウント
root@0f939b1206ed:/# ls /mnt/hoge # ホストマシンのファイルシステム
bin dev home lib32 libx32 media opt root sbin srv tmp var
boot etc lib lib64 lost+found mnt proc run snap sys usr
root@0f939b1206ed:/# ls -la /mnt/hoge/tmp/hoge # ホストマシンのファイルを閲覧可能
-rw-r--r-- 1 root root 9 Dec 15 07:44 /mnt/hoge/tmp/hoge
root@0f939b1206ed:/# cat /mnt/hoge/tmp/hoge
hogehoge
ホストで作成したファイルが閲覧できました。このように--privileged
オプションを使用して実行したコンテナを利用することで, ホストの管理者と同等の権限を得ることができます。
2. docker socketをコンテナ内にマウントしない
docker socket(docker.sock
)をコンテナ内にマウントしている場合, そのコンテナ内で更にコンテナを作成することでホストマシンの(ほぼ)管理者権限を得ることができます。
# Docker socketをコンテナ内にマウント
$ docker run --rm -it -v /run/docker.sock:/run/docker.sock ubuntu bash
root@1daead7c1ca7:/# apt update && apt install wget -y && wget -qO- https://get.docker.com | sh # dockerをインストール
# コンテナA内でコンテナBを実行し, ホストディスクをマウント
# ここでの / はコンテナAの / ではなくホストマシンでの / になる
root@1daead7c1ca7:/# docker run -it -v /:/host/ ubuntu:18.04 chroot /host/ bash
root@1daead7c1ca7:/# ls -la /tmp/hoge # ホストのファイルシステムにアクセス可能
-rw-r--r-- 1 root root 9 Dec 15 07:44 /tmp/hoge
root@1daead7c1ca7:/# cat /tmp/hoge
hogehuga
docker socketへの書き込み権限を持つプロセスは基本的に管理者権限を持つ必要があるため, docker socketをマウントしたコンテナを介して, ホストの管理者と同等の権限を得ることができます。
3. コンテナ内でrootユーザーを使用しない
Dockerコンテナ内ではデフォルトでroot権限でプロセスが実行されます。コンテナ内のrootはホストマシンのrootとほぼ同等の権限を持っています。しかし, コンテナプロセスはLinuxのcapabilityとnamespace機能を使用してサンドボックス化されているため, 例えばコンテナからホストで実行されているプロセスを閲覧することはできません。
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.9 101916 9848 ? Ss 07:39 0:03 /sbin/init
root 2 0.0 0.0 0 0 ? S 07:39 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< 07:39 0:00 [rcu_gp]
root 4 0.0 0.0 0 0 ? I< 07:39 0:00 [rcu_par_gp]
...
ubuntu 11343 4.0 0.5 10056 5116 pts/1 Ss 09:00 0:00 -bash
ubuntu 11352 0.0 0.3 10624 3296 pts/1 R+ 09:00 0:00 ps aux
$ docker run --rm -it ubuntu bash
root@dd88bc329300:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.7 0.3 4116 3312 pts/0 Ss 09:01 0:00 bash
root 9 0.0 0.2 5904 2856 pts/0 R+ 09:01 0:00 ps aux
しかし, runcの権限昇格の脆弱性などコンテナプロセスからホストへアクセス可能になる脆弱性が生まれた際, その脆弱性を悪用するためにコンテナがrootで実行されている必要性がある可能性もあります。そのようなケースも考慮して, コンテナ内では非rootユーザーで実行した方が良いでしょう。
Dockerfileで以下のようにして非rootユーザーを作成しておきます。
FROM ubuntu:18.04
RUN groupadd -r hoge && useradd --no-log-init -r -g hoge hoge
USER hoge
$ docker build . -t hoge
$ docker run --rm -it hoge bash
hoge@527a715a5a83:/$ id
uid=999(hoge) gid=999(hoge) groups=999(hoge)
4. セキュリティオプションno-new-privileges
を使用する
セキュリティオプションno-new-privileges
を使用してコンテナを実行すると, コンテナ内で誤設定されたSUIDファイルを介した新たな特権の獲得を防ぐことができます。ちなみに, SUIDファイルは所有者の権限で実行されるファイルです。
Dockerfileでroot権限で実行されるSUIDファイルを作成する処理を書きます。
FROM ubuntu:18.04
RUN cp /bin/bash /bin/yabash && chmod u+s /bin/yabash # /bin/yabashはroot権限で実行される
RUN useradd -s /bin/bash hoge
USER hoge
CMD ["/bin/bash"]
no-new-privileges
を使用しない場合は, 誤設定suidファイルを用いた権限昇格が可能になります。
$ docker run --rm -it hoge bash
hoge@2f83f3b6de29:/$ id
uid=1000(hoge) gid=1000(hoge) groups=1000(hoge)
hoge@2f83f3b6de29:/$ /bin/yabash -p
yabash-4.4# id
uid=1000(hoge) gid=1000(hoge) euid=0(root) groups=1000(hoge)
no-new-privileges
を使用する場合は, 誤設定suidファイルを用いた権限昇格が不可能になります。
ubuntu@ip-172-31-40-126:~$ docker run --rm -it --security-opt=no-new-privileges:true hoge bash
hoge@5a9d2e7ac35d:/$ id
uid=1000(hoge) gid=1000(hoge) groups=1000(hoge)
hoge@5a9d2e7ac35d:/$ /bin/yabash -p
hoge@5a9d2e7ac35d:/$ id
uid=1000(hoge) gid=1000(hoge) groups=1000(hoge)
5. Rootlessモードを使用する
通常, Dockerコマンドを実行するためにはrootユーザーである, もしくは非rootユーザーでdockerグループに属している必要があります。外部の攻撃者によってDockerコマンドを使用可能なユーザーの権限が獲得された場合, 管理者権限を奪われることになります。非rootユーザーの場合も特権コンテナを作成するなどして, 管理者権限と同等の権限を得ることができます。その他の悪用方法についてはこちらで紹介されています。
Rootlessモードを使用すると, Dockerコマンドを実行可能なユーザーの権限が獲得されたとしても管理者権限を獲得されることは免れます。Rootlessモードの詳細はこちらの記事で説明されています。
Rootlessモードは,Dockerデーモン及びコンテナを,非rootユーザで実行する技術です.Rootlessモードを用いることにより,万一Dockerに脆弱性や設定ミスがあっても,攻撃者にホストのroot権限を奪取されることを防ぐことが出来ます.
Rootlessモードの導入方法, 使用方法についてはこちらで紹介されています。
まとめ
本記事では, Dockerコンテナを介して攻撃者にボコボコにされないために満たすべき事項を紹介しました。便利なDockerですが, 使用方法によっては脆弱性が生まれることはあります。セキュリティを意識して, 快適なコンテナライフを送りましょう!