Executive summary
Most “Docker hacks” are not kernel 0-days—they’re misconfiguration, exposed daemons, over-privileged containers, and poisoned images. When attackers win, they usually:
- hit an exposed Docker API,
- abuse a mounted Docker socket,
- exploit
--privileged/ capabilities / unsafe mounts, or - leverage a malicious image/supply-chain.
This article maps each exploit class, shows how compromises unfold, and gives copy-paste hardening, detections, and IR playbooks you can apply today.
1) Threat model & attack surface
Docker stack: image → containerd → runc → kernel; host exposes dockerd (optionally on TCP); containers talk via docker0 bridge / user-defined networks; volumes bind host paths.
High-risk boundaries
- Docker API (
/var/run/docker.sock, TCP 2375 no-TLS, 2376 TLS). - Host filesystem mounts (
-v /:/host,hostPathin K8s). - Privileges/Capabilities (
--privileged,CAP_SYS_ADMIN,SYS_MODULE, etc.). - Supply chain (public registries, mutable tags, CI/CD tokens).
- Runtime bugs (runc/containerd), less common but high impact.
2) Exploit classes & how they work
A) Exposed Docker daemon (TCP 2375/2376)
Exploit: unauthenticated 2375 or weak-TLS 2376 → attacker runs:
bashCopyEditdocker -H tcp://victim:2375 run --privileged -v /:/host alpine chroot /host /bin/sh
Impact: host root, persistence (SSH keys, systemd units), crypto-mining, lateral movement.
B) Docker socket mount inside a container
Mounting /var/run/docker.sock makes the container root-equivalent on the host:
bashCopyEdit# from inside the compromised container:
docker run --privileged -v /:/host alpine chroot /host /bin/sh
Typical path: “helper sidecar” gets popped → attacker spawns sibling container with host mount → full host.
C) Over-privileged containers & capabilities
--privilegeddisables most isolation.- Dangerous caps:
CAP_SYS_ADMIN,SYS_MODULE,SYS_PTRACE,NET_ADMIN. - Devices: mapping
/dev/kmsg,/dev/mem, or raw disk → host abuse. - With no seccomp/AppArmor/SELinux, syscalls like
ptrace,bpf,mountare open.
D) Host filesystem & mount tricks
- Bind mounting sensitive dirs (
-v /etc:/etc) or-v /:/host→ direct host writes. - Symlink/TOCTOU on volume paths can redirect writes to protected files.
- OverlayFS quirks + setuid bits may enable privilege escalation on the host.
E) runc/containerd vulnerabilities
- runc FD/exec races (e.g., families like CVE-2019-5736 & later variants): malicious container overwrites the host
runcbinary duringdocker exec, gaining host code execution. - containerd/CRI parser/manifest issues leading to unexpected host access.
F) Image & registry supply-chain
- Typosquatted images on public registries, mutable tags (
:latest) silently changing. - Dockerfiles with
curl | bash, backdoored ENTRYPOINTs, secrets baked into layers. - Compromised CI pushing trojaned images to your registry.
G) Networking & credentials
- Over-exposed ports (
-p 0.0.0.0:…), weak firewall → direct internet reachability. - Stolen
~/.docker/config.jsontokens; environment-variable secrets; cloud metadata access from containers.
3) What a real compromise looks like (chain example)
- Shodan finds 2375 open → attacker runs privileged container.
- Mounts host
/and writes an SSH key to/root/.ssh/authorized_keys. - Installs miner & persistence unit under
/etc/systemd/system/…. - Harvests cloud creds from
/root/.awsor metadata URL (if reachable). - Lateral movement using harvested keys and registry tokens.
4) Hardening that actually works (copy-paste)
4.1 Daemon settings (/etc/docker/daemon.json)
jsonCopyEdit{
"icc": false,
"userns-remap": "default",
"no-new-privileges": true,
"live-restore": true,
"log-driver": "json-file",
"log-opts": { "max-size": "50m", "max-file": "3" }
}
- Never expose 2375 on the internet. If you must use TCP, 2376 with mutual TLS behind a firewall/VPN.
- Prefer rootless Docker where feasible.
4.2 Safer container run/compose
docker run
bashCopyEditdocker run --read-only --user 10000:10000 \
--cap-drop ALL --cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
--security-opt seccomp=default \
--pids-limit 256 --memory 512m --cpu-quota 50000 \
--tmpfs /tmp:rw,noexec,nosuid,nodev \
--network app-net \
--health-cmd='curl -f http://127.0.0.1:8080/health || exit 1' \
registry.example.com/app@sha256:<immutable-digest>
Compose snippet
yamlCopyEditservices:
api:
image: registry.example.com/api@sha256:3c1f...
read_only: true
user: "10000:10000"
cap_drop: ["ALL"]
security_opt:
- no-new-privileges:true
- seccomp:default
pids_limit: 256
tmpfs: ["/tmp:rw,noexec,nosuid,nodev"]
networks: [app]
healthcheck: { test: ["CMD","/healthcheck.sh"], interval: 30s, retries: 3 }
Do not: use --privileged, mount /var/run/docker.sock, or bind host / paths.
4.3 MAC & syscall policy
- Enforce AppArmor/SELinux (e.g.,
docker-defaultAppArmor profile). - Keep seccomp default (or stricter local profile) to block dangerous syscalls.
4.4 Supply-chain controls
- Sign images (Sigstore cosign) and enforce signatures in admission/webhooks.
- Pin digests, not tags.
- Generate SBOMs (Syft) and scan with Trivy/Grype on build & deploy.
- Lock down registries; restrict to allow-listed registries/namespaces.
4.5 Network & secrets
- Publish only necessary ports; firewall the host; use user-defined networks for micro-segmentation.
- Use secret managers or Docker secrets/Swarm/K8s Secrets; avoid env vars where possible.
- Block cloud metadata endpoints from app containers (IMDSv2 hop-limit / iptables).
5) Detections you should deploy (ready-to-use)
5.1 Falco (container breakout attempts)
yamlCopyEdit- rule: Docker Socket Mount
desc: Container accessed /var/run/docker.sock
condition: fd.name startswith /var/run/docker.sock
output: "Docker socket access (proc=%proc.name user=%user.name file=%fd.name)"
priority: CRITICAL
- rule: Write Below Root
desc: Container writing to sensitive host paths
condition: write_to_known_sensitive_file
output: "Write to sensitive file (proc=%proc.name file=%fd.name)"
priority: CRITICAL
5.2 Watch Docker events (quick hunt)
bashCopyEditdocker events --filter 'event=create' --filter 'event=exec_create' \
| grep -E 'privileged|/var/run/docker.sock|--cap-add|host|volume=/'
5.3 SIEM hunts
- Process tree:
dockerd→ launches container → unexpected host process (crypto miner, bash). - Network: sudden outbound to mining pools; container contacting
169.254.169.254(cloud metadata). - Filesystem: writes under
/etc/systemd/system,/root/.ssh/authorized_keys.
6) Incident response playbook (short & practical)
- Contain
iptables -A INPUT -p tcp --dport 2375 -j DROP(if exposed).docker pause <id>orsystemctl stop docker(consider business impact).- Snapshot host (disk + memory) before cleanup for forensics.
- Triage
docker ps -a,docker inspect <id>,docker logs <id>.docker image inspect <img>for digest and provenance; retrieve SBOM, scan with Trivy.journalctl -u docker,docker events --since ....- Check
/var/lib/docker/overlay2/*/difffor dropped binaries/scripts.
- Scope
- Search for privileged containers, docker-sock mounts, unknown images, suspicious systemd units, new users/SSH keys.
- Eradicate & Recover
- Remove malicious containers/images; rotate registry and CI tokens; rebuild from signed, scanned images.
- Patch Docker/runc/containerd; disable 2375; enforce TLS/VPN.
- Lessons learned
- Block docker-sock mounts, enforce image signatures, set daemon baseline, add Falco/SIEM rules.
7) 30-60-90 day security program
Days 1–30 (Baseline):
- Disable 2375; move to TLS or socket-only; firewall host.
- Enforce seccomp default, AppArmor/SELinux; enable
userns-remap. - Ban
--privilegedand docker-sock mounts by policy.
Days 31–60 (Supply chain & policy):
- Sign all images (cosign); enforce digest pinning.
- SBOM + vulnerability scan gates in CI.
- Implement admission controls (if on Swarm/K8s) to deny unsafe options.
Days 61–90 (Detection & drills):
- Deploy Falco/eBPF sensors → SIEM; build dashboards for privileged runs, socket access, host mounts.
- Red-team exercise: exposed 2375, docker-sock breakout, poisoned image → verify containment and recovery time.
Quick checklist
- No 2375 on the internet; 2376 TLS/VPN only
- No
--privilegedor dangerous caps; seccomp + AppArmor/SELinux on - Never mount
/var/run/docker.sockin app containers - Pin digests, sign images, scan SBOMs
- Default-deny egress/ingress where possible; block metadata access
- Falco/SIEM rules for privileged runs, socket access, host writes
- IR runbook tested; tokens & keys rotated post-incident
Key takeaways
- Misconfig is the exploit. Close 2375, kill
--privileged, and stop mounting the Docker socket. - Treat images as untrusted code—sign, pin, and scan them.
- Add runtime sensors and policies so breakouts are noisy and short-lived.
Leave a comment