Blind Spot: how I get from Docker Registry To RCE

Chux
7 min readAug 26, 2023

--

Introduction

In this article we’ll dive in the secrets stored in docker images and might be exploited in case that an attacker gets access to a private docker registry. From attacker’s perspective, getting a docker image of a target may be the same as getting access to the target’s git or source code. Stored credentials, vulnerability research and image poisoning — so many opportunities for a determined threat actors.

For more content and hacking tips — follow me on X.

Let’s get started.

Basic Explanation: Docker and Docker Registry

We are surrounded by docker containers almost everywhere: web applications, web servers, databases and almost every piece of software that we can think of. A docker image is really just a collection of code and dependencies that developers put together in order to simplify the activation and the deployment of their software. An example for the contracture (Dockerfile) of a docker image:

The image above demonstrates a web application that based on NodeJS 14 and includes all the code of the web app (line 5), and after that installs all the dependencies of the app (line 7) and runs on port 3000 (line 9). Now, for running this application, all you need to do is pulling the images from the repository it’s stored in and running it, literally with one simple command.

You can find and download public docker images from DockerHub (like Nginx, Python, MySQL and more), and if you want a private DockerHub for your organization or yourself, you can create your Docker Registry to store all the docker images. If you choose to work with docker images, whatever docker registry you use (DockerHub or private registry) is now containing your code, similar to your Git repository.

To understand how easy it is, here’s ChatGPT guidance for creating a private docker registry:

The default port for docker registry is 5000 and it runs over HTTP without any authentication. Of course, you are expected to secure your registry with limited access or at least password. Otherwise, you expose your code to anyone on the internet who can find your registry.

If you want to set authentication for accessing your registry, here’s again ChatGPT answer:

Just a little bit more complex from the previous command.

Also, for using a private docker registry, we need to allow our docker daemon to trust the specified address by editing /etc/docker/daemon.json:

After that, just restart the docker service:

sudo systemctl stop docker

sudo systemctl start docker

And now we are ready to go with our new private docker registry.

Theory:

Docker registries would be a blind spot for many companies, even tech companies who consider themselves secure and strict to best practices. By finding an open private docker registry, I’ll be able to gain access by one of the following methods:

· Code secrets — stored credentials, security tokens, JWT secrets and more.

· Vulnerability research — by viewing the code and performing white box PT.

· Image poisoning — pushing a malicious image that contains malicious code.

By simply running “Docker Registry HTTP API” in Shodan, we get the following result:

After a combination of Censys, fofa, scanners and other tools, I was able to map approximately one million docker registries, most of them were accessible without any needed authentication.

From Code To RCE

In order to find valuable images, I was narrowing down the results by focusing on registries that contain images with name like: “backend”, “api”, “database”, “crm” and some more based on the same idea.

I picked a registry of a software company that provides SaaS platform for enterprises, that will be referred here as REDACTED (for not exposing the real name).

REDACTED has many available images on its registry, so I pulled the one that had interesting name — “crm”.

Now that I have the image, I need to mount its file system to my machine in order to read the application code. Here comes a simple trick.

Docker has the option to export the file system of a running container (not a static image).

At this point I still don’t know what is the correct way to run the container, so if I’ll just simply run the container, it probably collapse after few seconds. But here comes the trick. I will run the container knowing it will collapse, I just need to be quick enough to execute the export command before the container dies. So on one terminal I’ll run the container and immediately move to the second terminal to export the file system:

Run the container:

Export the file system:

Works like a charm :)

Now we need to extract the files from the tar file:

Diving into the app directory and viewing its content, I noticed a file named config.json. Reading the content of it revealed the following:

As expected — code secrets!

Now we hold the database credentials of a PostgreSQL. A quick look for an IP address at the range of the company that has port 5432 exposed pointed on one IP. I tried the credentials I got earlier:

After a humble look, it was clearly that this is the real an live CRM of the company!

Not only that, the user that I had also holds superuser permissions. So by granted myself extra permissions:

Now we can get a dir list from the remote server:

We have permissions to use Postgres to read files:

According to our permissions, we also have the ability to write files on the remote server. But now let’s try to run commands and officially get RCE here:

Similar to this example, I tried couple more images from different registries and found a few more secrets:

· AWS tokens

· Stripe secret keys

· JWT secrets

· Email accounts credentials

· API keys

Also worth mentioning that in some cases, by viewing the code of some applications it was pointed out that the some apps were vulnerable to different kind of attacks, including:

· IDORs in API’s

· SQL Injection

· SSRF

To conclude this part of the project, it can be said that in most of the cases when a threat actor would be able to put his hands on an unintended exposed docker registry, the threat actor will find valuable information that would be useful for compromising the target infrastructure.

When the code is “secure”

Now that we made a few PoC’s that by gaining access to a private docker image we can find many vulnerabilities, let’s assume that we encountered a target that wrote its code in a secure way and was strict to best practices (not storing credentials hard-coded for example).

How can we still win this game? Image Poisoning!

Most of the cases, when I had pull permissions to download the image, I also had push permissions. Which means that now it is possible to switch the original image with a malicious one:

· Pull the target image

· Mount its file system locally

· Add a piece of malicious code (open a reverse shell, download a webshell, implant a JS keylogger…)

· Re-build the image locally

· Push the image back to the target registry and override the original image

This way, no matter if the developers team was doing a great job, it’s enough that the DevOps managing the registry permissions in a sloppy way for a threat actor to poison the entire docker images of an organization.

Mitigations

· Protect the docker registry with a secure password!

· Don’t expose your registry to the internet if it’s not needed

· Avoid hard-coded credentials and tokens in the code, use environment variables when running the container instead

*** The vulnerabilities that found during this article were reported to the companies who owned these images and the necessary actions were done before publishing it.

--

--

Chux

Red Teamer and Security Researcher | CVE-2024-46990