コンテナを介して攻撃者にボコボコにされないためのtips

# はじめに
こんにちは, 株式会社Acompanyインターン生の湯浅です。
名古屋大学の学部4年生です。もうすぐ卒業するので, ワクワクです。

本記事は[OPTIMIND x Acompany Advent Calendar 2021](https://qiita.com/advent-calendar/2021/optimind_acompany)の15日目の記事となります。

## 本記事の内容
本記事では, Dockerコンテナを介して攻撃者にボコボコにされないために満たすべき事項を紹介します。
この記事を読めば, Dockerコンテナ経由でホストマシンの管理者権限が獲得される事態は避けられます（きっと）。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">コンテナを介してホストマシンが侵害される図 <a href="https://t.co/14taCg6LJV">https://t.co/14taCg6LJV</a></p>&mdash; yuasa (@junkitombob) <a href="https://twitter.com/junkitombob/status/1470414077050892291?ref_src=twsrc%5Etfw">December 13, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 満たすべき事項
まず, コンテナを介してホストマシンに侵入されないために, 私が満たすべきだと考える事項を以下に挙げます。

1. `--privileged`オプションを使用しない。
2. docker socketをコンテナ内にマウントしない。
3. コンテナ内でrootユーザーを使用しない。
4. セキュリティオプション`no-new-privileges`を使用する。
5. Rootlessモードを使用する。

# 満たすべき事項の詳細
上に挙げた満たすべき事項の詳細を説明します。

## 1. `--privileged`オプションを使用しない
[公式ドキュメント](https://docs.docker.jp/engine/reference/run.html#runtime-privilege-linux-capabilities)にもあるように, `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の権限昇格の脆弱性](https://www.jpcert.or.jp/at/2019/at190007.html)などコンテナプロセスからホストへアクセス可能になる脆弱性が生まれた際, その脆弱性を悪用するためにコンテナがrootで実行されている必要性がある可能性もあります。そのようなケースも考慮して, コンテナ内では非rootユーザーで実行した方が良いでしょう。

Dockerfileで以下のようにして非rootユーザーを作成しておきます。

```Dockerfile
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ファイルを作成する処理を書きます。

```Dockerfile
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ユーザーの場合も特権コンテナを作成するなどして, 管理者権限と同等の権限を得ることができます。その他の悪用方法については[こちら](https://gtfobins.github.io/gtfobins/docker/)で紹介されています。

Rootlessモードを使用すると, Dockerコマンドを実行可能なユーザーの権限が獲得されたとしても管理者権限を獲得されることは免れます。Rootlessモードの詳細は[こちらの記事](https://medium.com/nttlabs/rootless-docker-12decb900fb9)で説明されています。

> 
Rootlessモードは，Dockerデーモン及びコンテナを，非rootユーザで実行する技術です．Rootlessモードを用いることにより，万一Dockerに脆弱性や設定ミスがあっても，攻撃者にホストのroot権限を奪取されることを防ぐことが出来ます．

Rootlessモードの導入方法, 使用方法については[こちら](https://docs.docker.jp/engine/security/rootless.html)で紹介されています。

# まとめ
本記事では, Dockerコンテナを介して攻撃者にボコボコにされないために満たすべき事項を紹介しました。便利なDockerですが, 使用方法によっては脆弱性が生まれることはあります。セキュリティを意識して, 快適なコンテナライフを送りましょう！

# 参考資料
- [Docker Basics & Breakout](https://book.hacktricks.xyz/linux-unix/privilege-escalation/docker-breakout)
- [Docker Breakout / Privilege Escalation](https://book.hacktricks.xyz/linux-unix/privilege-escalation/docker-breakout/docker-breakout-privilege-escalation)
- [特権コンテナの脅威から学ぶコンテナセキュリティ](https://jpn.nec.com/cybersecurity/blog/210730/index.html)
- [Best practices for writing Dockerfiles](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user)
- [Docker デーモンをルート以外のユーザで実行（Rootless モード）](https://docs.docker.jp/engine/security/rootless.html)