Post

Container runtime fun time!

Container runtime fun time!

Working our way up the container stack, a runtime is what takes an image and runs it. It also manages its resources, like virtual networking or shared host file system access. A runtime also provides a “wrapper” around what would otherwise be a ton of shell commands to launch and manage the process inside the container. It’s a critical part of our threat model, as many of the inputs and outputs are directly controlled by the runtime we choose.

You get a choice of at least the runtimes above, plus more options that aren’t CNCF projects. Each of these have different defaults, settings available, and security model to work with.

So … let’s start on what to start considering to have some fun times with our runtime. 🧐

This is part of a series put together from client-facing conversations, conference talks, workshops, and more over the past 10 years of asking folks to stop doing silly things in containers. Here’s the full series, assembled over Autumn 2025. 🍂

Runtime choices and configurations

There are a lot of options for container runtimes, each with opinions that impact the security of the system.

  • Does the runtime process run as root? Must it? Many runtimes offer the ability to run without superuser privileges, limiting the amount of damage an escape can do, as the process has access to less on the host.
  • Does it run all the time as a service on the host (daemon ), or not (daemonless)?
  • What’s able to be configured? While the number of configuration options available isn’t a proxy for security, it is directly tied to the number of options you’ll need to understand for how it impacts the system.
  • How is it maintained? Does it have an active community and/or commercial support available? If something needs fixing, knowing the options and likely timeline helps.
  • How does it work? Not so secretly, some of the runtimes listed above are little virtual machines with “shims” to make them look like a container. Others are a virtual machine and all containers run inside of it. Others are running containers side-by-side with the host’s processes. All of these are acceptable, but they do all work differently.

😈 Exploit example - This demo using persistent shared storage to escape uses a common misconfiguration at runtime. Another good example uses the default user group for the runtime to escalate privileges once you’re on the host.

There’s as many ways to mitigate this as there are options in all the runtimes in the world, including never touching a computer again to go farm goats. 👩🏻‍🌾 🐐

That said, don’t disable security features or add extra permissions without deeply understanding what you’re doing.

Here are a few places it’s common to mess up, so check these first.

  • Don’t disable SELinux or AppArmor (even if AI tells you to). Here’s how to check the status of each:
    • sudo getenforce for SELinux
    • sudo apparmor_status for App Armor
  • The default seccomp profiles are your best friend and the default is probably good enough. (more about that here)
  • Avoid the --privileged flag, as it should be called --plz-pwn-me or --i-am-root
  • Check your container’s CAPabilities (more about that here)
  • The microVM runtimes get better every day (eg, Firecracker, Kata containers, and the like) and now have some new companies looking into it like the new Apple container runtime.

Runtimes have their own vulnerabilities

A container runtime is software. It manages processes and their resources. It’s frequently a highly-privileged process. And it too will accrue known vulnerabilities over time.

😈 Exploit example - A good example that’s still actively exploited is “leaky vessels”, a name for several CVEs including one that’s a simple container breakout path. CVE-2024-21626 allowed for privileged escape to the host using file descriptors, usually to overwrite a binary on the host or cause other mischief.

Fortunately, mitigating this one is pretty simple. Choose a well-supported and mature runtime for production, then keep it up to date. 🤷🏻‍♀️

Networking is hard

There are two separate, but related, paths to worry about.

Inter-container communication

inter-container-comms inter-container-comms

The first is communication between containers. In general, the container runtime is in control of this part. It handles networking and addresses and routing for the containers it runs. It looks like this. ➡️

This gets complex quickly, as it:

  • depends on the runtime
  • is usually configurable
  • interacts with other container network concepts
  • is not always encrypted
  • usually not restricted by default

This is why FIPS-validated cryptography is being required w/i containerized applications for many US public sector organizations. If two services, say a database and an application, would communicate before containers it would have had to be encrypted. Changing the technology may or may not change where the boundary can be drawn according to your app, customer, auditor, etc.

Network egress

container-egress container-egress

The other path out is egress onto resources elsewhere - to other machines or services, either on the internet or local network.

There are even more places to configure this traffic than the path above, yet it can be simpler to observe with external tools. To most layers here, network packets don’t have the full context of what it is going on. What is this application trying to send? To where? What for? This gets tricky when trying to set up deep packet inspection or a context-aware egress policy. Keep in mind what tools you have available and what info is needed about network communication.

Here’s roughly the extra steps that could have a say in the goings-on of network egress. Most of these have some filtering you can configure, but the combination may be unique to that system.

flowchart LR
    A(container<br>application) --> B(container<br>runtime)
    B --> C(host's<br>network stack)
    C --> D(switch)
    D --> E(router)
    E --> F{internet}
    F --> E
    E --> D
    D --> C
    C --> B
    B --> A

At a high level, mitigation means first understanding what needs to be controlled. Then figure out where you can and can’t set network controls in a container. More specifically, this is exactly what the tools inside the cloud native network part of the landscape diagram is designed to solve for containerized applications.

Applications have vulnerabilities, too

container-escape container-escape

None of this absolves you from responsibility for the code in the container! This is still just as important to run your regular application security tooling on that, such as …

  • Static analysis
  • Dynamic analysis
  • Fuzzing
  • Composition analysis
  • Embedded secrets scanning
  • … etc …

😈 Exploit example - A good exploitation example is the handful of demos I run with an app that is vulnerable to shell injection to show off container escapes in the real world. There are a few examples to play with that have different restrictions, but you can still escape out of each of them.

Containers package, but they don’t contain. Keep the stuff inside secure too!

Rogue containers

inventory

A rogue container happens when an unauthorized container is running on a host (or in a cluster of hosts). Inventory isn’t shiny, it needs to be done continuously, and it’s the foundation of your security program. 🫠

Common offenders for rogue containers:

  • Developer resources / environments
  • CI/CD systems and other remote-code-execution-as-a-service type systems
  • anything “Docker-in-Docker”
  • Don’t forget about “shadow IT” either

All of these systems are typically more lenient in granting extra permissions to do things, but also need a shell or rootful access or extra capabilities in order to accomplish the task at hand. They’re high risk, but that doesn’t mean it can’t be secured some.

This category can also include unapproved images, but we’ll talk about this when we get to image-specific threats.

😈 Exploit example - It’s uncomfortably common to find the container runtime’s socket shared into the container. I made an interactive lab using this path to escape, launch a privileged container, which allowed me to persist and escalate my own privileges on the host.

github-socket-sharing-dark github-socket-sharing-light a quick search for “/var/run/docker.sock” on public GitHub repos

Mitigation here usually involves separating these high-risk workloads from others, usually by having different environments for development / staging / production. The same simple operations hygiene provides a ton of benefit here. If possible, try to de-risk these workloads as much as possible too, which could mean finding new ways to allow development or build software.

tl;dr

Container runtimes are the layer that turns images into running processes. They come with a delightful buffet of security risks including misconfigurations, networking complexities, and their own vulnerabilities that can lead to container escapes.

Up next: We run containers to scale horizontally, so what threats exist in our container orchestrator (back to the summary)

This post is licensed under CC BY-NC-SA 4.0 by the author.