Malicious Docker Hub Images Are Hiding in Plain Sight
Every other post in this series about container risk has been about what
happens after a workload is already running — a kernel escape, an
overprivileged default service account, an exposed API server. This one is
earlier in the chain: the image itself. Docker Hub is the default,
unauthenticated-by-default registry for millions of docker pull and
FROM lines, and researchers have repeatedly found it hosting images that
are malicious by design, not compromised after the fact. Sysdig’s 2024
scan of public Hub content found roughly a fifth of the images it sampled
carrying some form of malicious payload — cryptominers dominant, but also
embedded secrets, trojanized binaries, and DNS-based data exfiltration
(Sysdig),
and JFrog’s earlier registry sweep independently found thousands of
images built purely to mine coins or open reverse shells on docker run
(JFrog).
None of this needs a CVE. It needs someone to trust a tag.
Root cause
Docker Hub’s trust model was built for a smaller, friendlier ecosystem.
Anyone can push a public image under their own namespace, and nothing
about the platform verifies that postgres-backup-tool or redis-utils
does what the name implies. Two patterns show up over and over in the
research. The first is straightforward typosquatting: a malicious image
published under a name one character off from a popular official one —
nginx- prefixed variants, misspelled base-image names — betting that a
rushed docker pull or a copy-pasted docker-compose.yml won’t notice.
The second, more effective pattern doesn’t rely on a typo at all: attackers
publish images with plausible, professional-sounding names — backup
utilities, monitoring agents, “official-looking” database tools — with
real functionality bundled around a hidden payload, so the image does what
it claims and runs a miner in the background. JFrog’s sweep found
payloads triggered via ENTRYPOINT and embedded shell scripts that
curl | bash a second-stage payload at container start, which also means
static image scanning that only inspects filesystem layers for known
malware signatures can miss logic that only executes at runtime. Docker
Hub’s own “Official Images” and “Verified Publisher” programs exist
precisely to counter this, but they cover a small slice of the registry —
most pulls in the wild target unverified, community-namespace images that
carry none of that scrutiny.
Blast radius
A malicious image doesn’t need a privilege-escalation bug to do damage; it
already runs as whatever the container spec grants it, which in CI runners
and quick docker run experiments is often more than it should be. The
common outcome documented across both research sets is cryptomining —
CPU-cycle theft that’s easy to dismiss as a nuisance but that also confirms
the attacker had arbitrary code execution inside your environment, with
network egress, the means to read any mounted volume or environment
variable, and — if the image reached a CI pipeline — the same access every
build step gets: source code, deployment credentials, and whatever secrets
the pipeline injects. Images pulled into Kubernetes clusters inherit
whatever service account and network policy the pod spec grants, so a
miner today is a pivot point tomorrow if the attacker decides to escalate
instead of just farming coins. And because the image is the artifact
everyone in a team pulls identically, one bad tag reaching a shared base
image or a widely referenced docker-compose.yml multiplies the blast
radius across every environment that built from it.
Remediation
- Pin images by digest, not tag, in anything that matters —
image@sha256:...instead ofimage:latest. Tags are mutable; a publisher (or an attacker who compromises a publisher’s account) can repoint a tag to different content without changing the reference your manifest holds. - Prefer Docker Official Images and Verified Publisher images for base layers, and treat any other public-namespace image as unverified third-party code until proven otherwise — because that’s what it is.
- Run images through a scanner that executes, not just inspects, or at minimum a static scanner (Trivy, Grype, Docker Scout) as a required CI gate before an image is allowed to build on top of or deploy from — static scanning alone won’t catch every runtime-triggered payload, but it catches the cheap, common cases for free.
- Mirror the base images you actually depend on into a private registry you control, and build from that mirror instead of pulling upstream on every build — it turns “did the public tag change” into a question you control the answer to.
- Restrict egress from build and runtime environments so a payload that does land can’t freely reach out to a mining pool or a C2 endpoint; default-deny network policies catch a class of attack that image scanning never will.
- Audit who can push to any internal or shared namespace with the same rigor as a source-code repository — a registry credential is a code-execution credential for everyone who trusts that namespace.
The bigger lesson
Container images get treated like configuration — something you reference by name and move on from — when they’re really compiled, executable artifacts pulled from a registry with roughly the same trust guarantees as a random npm package before secret-scanning and lockfiles became standard practice. The fix isn’t exotic: pin by digest, source from verified publishers, scan before you build on top of anything, and mirror what you actually rely on. None of that requires new tooling most teams don’t already have — it requires applying the scrutiny already given to dependency files to the one artifact still pulled on trust.
Join the discussion
Comments for this post live on social — reply to the thread.
Related posts
Storm-0558: When a Stolen Signing Key Forges Any Token
A single stolen token-signing key let attackers mint valid cloud identities and read email in about 25 organizations. Root cause, blast radius, and what to do about your own keys.
CircleCI Breach: Stolen Session Cookies and Your CI Secrets
One infected engineer laptop, one stolen SSO session, and every customer secret in CircleCI was in play. What failed, the blast radius, and how to make CI secrets cheap to rotate.
The chalk/debug npm Compromise: Anatomy of a Package Hijack
One phished maintainer turned 18 ubiquitous npm packages into a crypto-stealing payload in about two hours. Here is how it worked and the build-pipeline controls that blunt it.