Docker Engine: open API on 2375 and docker.sock in web-facing containers = root on host
Risk / Filed 28 Sep 2026
dockercomposeportainertraefikexposed-apicryptominerbotnet
What: The Docker API is root on the host. Anyone who can talk to it (TCP 2375, or
/var/run/docker.sock) can start a privileged container with / mounted and own the box.
Bots do exactly that, nonstop. Carbonato (ThreatDown, disclosed Sept 2026, activity Oct 2024 to
Aug 2026) hits unauthenticated 2375, launches a privileged container, drops SSH keys and
cron/systemd persistence, steals AI API keys and SSH creds, and scans for the next daemon every
five minutes. Earlier strains (Trend Micro, June 2025; Akamai, Sept 2025) mount /:/hostroot,
drop XMRig, add keys to /root/.ssh/authorized_keys, and firewall 2375 so other gangs can't get
in after them.
Who it hits:
-
dockerdstarted with-H tcp://0.0.0.0:2375(a daemon.jsonhostsentry, a systemd override, or a leftover "enable remote API" tutorial). Port-forwarded, on a VPS, or anywhere the LAN is reachable. -
2376without--tlsverify. Using the TLS port number doesn't give you TLS. -
Containers that are reachable from the web and mount
docker.sock: Portainer, Traefik, Watchtower, Homepage/dashboards, "container manager" UIs. One bug or weak login in that app gives the attacker root on the host. -
Anyone who trusts
ufwto protect published container ports. It doesn't. Docker's own iptables rules divert published-port traffic before ufw sees it.
Check if you're affected:
# 1. Is the daemon listening on TCP? Any output here = look closer.
sudo ss -ltnp | grep -E ':2375|:2376'
systemctl cat docker | grep -n 'tcp://'
grep -n 'tcp://' /etc/docker/daemon.json 2>/dev/null
# 2. From OUTSIDE your network (phone hotspot, a VPS). JSON back = you're open to the world.
curl -s --max-time 5 http://<public-ip>:2375/version
# 3. Which running containers mount the socket?
docker ps -q | xargs -r docker inspect --format '{{.Name}} {{range .Mounts}}{{.Source}} {{end}}' | grep docker.sock
grep -rn --include='*compose*.y*ml' 'docker.sock' ~ /opt /srv 2>/dev/null
# 4. Which published ports listen on every interface (and skip ufw)?
docker ps --format '{{.Names}}\t{{.Ports}}' | grep -E '0\.0\.0\.0|\[::\]|:::'
# 5. Signs you're already hit (alpine matches plenty of legit images; look for ones you didn't start)
docker ps -a --format '{{.ID}}\t{{.Image}}\t{{.CreatedAt}}\t{{.Command}}' | grep -Ei 'alpine|base64|hostroot'
sudo cat /root/.ssh/authorized_keys; sudo crontab -l; systemctl list-timers --all
Do this:
-
Tonight: close 2375. Remove
tcp://...from/etc/docker/daemon.json(hosts) and from anyExecStartin/etc/systemd/system/docker.service.d/*.conf, thensudo systemctl daemon-reload && sudo systemctl restart docker. Delete the router port-forward too. -
Need remote control? Use SSH, not TCP.
docker context create lab --docker host=ssh://you@labboxthendocker --context lab ps. If you really need TCP, use TLS on 2376 with--tlsverifyand client certs. Never plain 2375. -
Take the socket out of web-facing apps. If an app only needs to read container state (Traefik's Docker provider, Homepage, most dashboards), put
tecnativa/docker-socket-proxyin front withCONTAINERS=1andPOST=0(the default). Put it on an internal network, never published. Mounting the socket:rodoes not make the API read-only: it still accepts every call. Apps that must write (Portainer, Watchtower) stay off the public internet. Put them behind a VPN (Tailscale/WireGuard) or Cloudflare Access. -
Stop trusting ufw for container ports. Anything behind a reverse proxy or tunnel should be published as
127.0.0.1:8080:80(compose:"127.0.0.1:8080:80"), or not published at all and reached over a shared Docker network. On Docker older than 28.0.0, machines on the same L2 segment can still reach localhost-published ports, so upgrade. -
Verify. Re-run checks 1–4.
curl http://<public-ip>:2375/versionfrom outside must time out.
If you were already hit: Treat the host as owned. They had root. Rebuild it. Before you do,
rotate everything that lived on it (SSH keys, cloud creds, AI provider API keys, tokens in .env
files) because Carbonato goes after exactly those.
Why this level: An open 2375 or an internet-facing app holding docker.sock gives root with no auth, and live worms are scanning 2375 right now (the mass-scanning override applies). That's 5/5 on the rubric. act-tonight.
Sources
- BleepingComputer: Carbonato malware hijacks exposed Docker hosts (2026-09-24)
- The Hacker News: Carbonato botnet compromises Docker hosts
- Akamai Hunt: new malware targeting exposed Docker APIs (2025-09-08)
- Trend Micro: Tor-enabled Docker exploit (2025-06-18)
- Docker docs: daemon attack surface
- Docker docs: protect the daemon socket (SSH / TLS)
- Docker docs: packet filtering and firewalls (ufw bypass)
- Docker docs: port publishing (insecure by default, bind to 127.0.0.1)
- Tecnativa docker-socket-proxy
How severity is decided. Source file: risks/2026-09-28-docker-api-2375-and-docker-sock.md.