Beginners Guide to Container Security

Learn How to Hack Into Containers and understand how they actually work.

Mayank Pandey
InfoSec Write-ups

--

Containerization is a popular way to deploy applications, many companies use this way to run their services, As a Security Enthusiast or Red Teamer it becomes necessary to know how containers work and how to break into them. Learn the basics of containerization in the context of Docker and how to hack into them.

A Group of Hackers Trying to Break inside a Container
Banner Generated by DALL.E 2 AI

Containers aren’t really a thing they are created using Linux kernel features such as namespaces and cgroups, which allow for the creation of isolated environments for running applications. These isolated environments, or containers, can include their own system libraries, files, and networking configurations, enabling them to function as if they are separate from the host system.

Basic docker architecture

  • containerd is a container runtime that can manage a complete container lifecycle — from image transfer/storage to container execution, supervision, and networking.
  • container-shim handles headless containers, meaning once runc initializes the containers, it exits handing the containers over to the container-shim which acts as some middleman.
  • runc is a lightweight universal run time container, which abides by the OCI specification. runc is used by containerd for spawning and running containers according to OCI spec. It is also the repackaging of libcontainer.
  • grpc used for communication between containerd and docker-engine.
  • OCI maintains the OCI specification for runtime and images. The current docker versions support OCI image and runtime specs.
Docker Arctichture | Source

Behind the Scenes

Docker has abstracted the process of creating an Isolated environment to run applications, it has made it pretty easy to spawn a container that is separated from the host, but if you look closely there is a lot that's going in the background. The overall process looks something like this.

  • The Docker daemon starts the container process.
  • If necessary, the specified image for the container is downloaded from Docker Hub.
  • The unshare system call creates a new namespace for the container.
  • The fork system call creates a new process within the new namespace.
  • The container runs, using system calls such as exec, kill, and waitpid to manage processes within the container.

In order to better understand containerization, it is important to familiarize ourselves with some basic terminology

The Namespaces

Linux namespaces (ns) are kernel-level constructs that allow for the isolation of global system resources such as network interfaces, process IDs, and mount points.

Each namespace has its own set of system resources, such as network interfaces, process IDs, and mount points. This allows processes to be isolated from one another so that they cannot interfere with or manipulate the resources of other processes.

There are several different types of namespaces in Linux, each of which provides a private view of a different type of system resource:

  • PID namespaces: Processes within a PID namespace have their own private set of process IDs, which are independent of the process IDs of processes outside the namespace. This allows multiple processes to have the same process ID without conflicting with one another.
  • UTS namespaces: UTS namespaces provide a private view of the hostname and domain name for a process.
  • IPC namespaces: IPC namespaces provide a private view of inter-process communication (IPC) resources, such as message queues and semaphores.
  • Network namespaces: Network namespaces provide a private view of network interfaces, routes, and firewall rules. This allows processes within a namespace to have their own virtual network stack, which is independent of the network stack of the host system.
  • Mount namespaces: Mount namespaces provide a private view of the file system hierarchy. Processes within a mount namespace have their own private set of mount points.

You can check the namespaces of a process using ls -l /proc/<PID>/ns .

The namespace of the Host System

1150 is the PID of a Process run from inside the Container

The namespace of the Docker Container

Some namespaces of the Host and the container remains the same as they don't require to be isolated. We can create separate namespaces for them too.

We can also use the unshare command, it allows a process or command to be run in a new namespace.

Spawning a Shell in a new PID Namespace

Here we run the unshare command to spawn /bin/bash in a new PID namespace (-p). -f is used to fork the specified program as a child process of unshare rather than running it directly.

ps aux shows that only 2 processes are running, but in reality, these processes are running inside a new namespace and they can not see other processes of the host system.

cgroups

Control groups (cgroups) is a Linux feature that allows you to set limits on resources that processes can use and allocate these resources to containers, processes, or groups of processes. cgroups provides the core functionality that permits docker to work.

It divides system resources into control groups with specified resource limits and assigns processes to these groups to prioritize certain processes and fine-tune resource allocation.

We can launch a Docker container with limits on the number of processes or amount of memory that the container is allowed to use.

Using cgroups to limit processes inside a container

Here we set the number of processes to be 2, if we launch more than 2 processes then we get an error. This becomes important if we want to prevent attacks that consume systems resources.

Linux Capabilities

In Linux, capabilities are a way of dividing up the privileges associated with superuser (root) access into a set of distinct units that can be independently enabled or disabled. This allows you to give certain programs access to certain privileges without giving them full root access.

In the context of Docker, Linux capabilities can be used to allow a container to perform certain privileged actions that would normally be restricted to the host operating system’s root user.

Getting Information about the Capabilities of System

Limiting syscalls with seccomp

Seccomp (short for “Secure Computing Mode”) is a kernel feature that allows an application to specify a filter for system calls that it is allowed to make. This can be used to restrict the application’s access to the system, in order to increase the overall security of the system.

seccomp can be used to further restrict the access of a containerized application to the host system. By default, Docker containers have access to all of the same system calls as the host system, but this can be changed by specifying a seccomp profile when starting the container.

Basic seccomp profile that blocks the unlink system call:

{
"defaultAction": "SCMP_ACT_KILL",
"syscalls": [
{
"name": "unlink",
"action": "SCMP_ACT_KILL"
}
]
}

AppArmor

AppArmor is a Linux security module that allows administrators to specify rules for how applications can access resources on the host system.

In the context of Docker, AppArmor can be used to secure containers by specifying rules that restrict the actions that a containerized application is allowed to perform.

For example, the following rule will allow limited access to the mentioned files

  /etc/shadow r,
/home/credentails.txt rw,

Let's now discuss the potential attack vectors that can be used to hack a docker container.

Abusing privileged mode

The --privileged flag in Docker is used to give a container full access to the host system's resources. When a container is run with this flag, it has the same privileges as the host system and can perform any action that the host can.

Running a Container in Privileged Mode

Since this container is running using the privileged flag, it disables isolation and security mechanisms. We can now use the mount command to mount the host’s filesystem inside the container and read/write/update files.

Mounting the Host File System Inside the Container

Docker socket misconfigurations

The Docker socket is used as a communication channel between the Docker client and the Docker daemon (server).

Docker socket misconfigurations can pose a security risk because they can allow unauthorized access to the Docker daemon. The Docker daemon is responsible for managing and executing containers and has access to all the resources of the host system.

Docker socket loaded inside a container

You can search for docker.sock the file inside a container, these sockets files are usually loaded inside the container while launching them. Using this you can create new containers or delete the existing ones.

Exploiting Kernel vulnerabilities

One of the important security issues that docker has is its sharing the same kernel with the host which makes it exploitable with the same vulnerabilities in which the host system’s kernel is affected.

You can scan the Container for kernel Vulnerability using tools like LinPEAS

Unauthenticated Docker HTTP REST API

If a Docker daemon’s HTTP REST API is unauthenticated, it means that anyone can access and potentially control the Docker daemon remotely over the network without the need for a username or password.

By default, the docker REST API is an unauthenticated API, meaning anyone on the network can start and stop a container.

Mostly the service runs on port 2376 , but you can also scan the localhost to check for open ports.

Accessing Docker REST API

No network segregation

By default, every container in the network can talk to each other and there is no segregation between them.

However, docker provides three types of network communication mechanisms.

Different Network Profiles in Docker
  • None

When we run the container in the none mode the docker container runs in an isolated environment. All incoming and outgoing traffic will be blocked.

No Outgoing connection is allowed
  • Bridge

In the Bridge network, containers under the same bridge network can talk to each other. The bridge network is the default driver in Docker.

  • Host

In the host network driver setting, the container uses the same network stack as the host. The container binds its virtual network interface card to the host’s network interface card.

Reviewing Dockerfile

Dockerfiles are the foundation for building a container, and it is important to consider security when creating them, few of the things that should be looked out for are:

  1. Exposing unnecessary ports: Make sure that only the ports that are required for the application to function are exposed.
  2. Running containers as root: By default, containers run as root, which can be a security risk. Instead, create a non-root user in the Dockerfile and use that user to run the application.
  3. Not specifying resource limits: Without resource limits, a container could potentially consume all of the host’s resources and cause a denial of service.
  4. Using outdated base images: Base images that are out of date may contain vulnerabilities that can be exploited.
  5. Not properly handling secrets: Sometimes you can find API Keys and secret tokens embedded inside the Dockerfile
  6. Not enabling security features: Make sure that security features such as AppArmor and seccomp are enabled to help protect the container.

These are some of the techniques you can use to hack inside a docker container. There are many other things you can try, I will highly recommend you research on your own and practice.

You can check out These Docker Breakout techniques for a better understanding of the topic.

Thanks a lot for reading. Share if you like it 😇😇

Follow me here on Medium and Subscribe to the Mailing list 💌 if you would like to get my articles when I publish them.✨✨

My GitHub: MayankPandey01 👨‍💻

You can find me on Twitter: mayank_pandey01 👻

--

--