Much has been said recently about the abuse of Docker APIs exposed to the internet. These references all talk about the main Docker API that communicates with the Daemon to carry out cluster actions. As many articles explain, if an API of this type is exposed to the internet, an attacker may take remote control of the entire cluster.
However, there is another API that could also be dangerously exposed: The Docker Registry API.
What is a Docker Registry?
In a nutshell, Docker containers are live instances of Docker images.
For example, if an Nginx container is run, first the Docker image of specified software is pulled and then the container is executed.
Where is this image downloaded from?
From a Docker Registry, it is used to store the Docker images.
Docker Hub is the Registry queried by default, however, it is possible to have a private images repository for the local infrastructure. When running a Registry, it exposes an API that can be queried by anyone wanting to interact with the Registry (for example to pull or push images). This is where the potential danger may appear. If the API is exposed to the internet or does not have sufficient protection mechanisms, then an attacker could download the images and even poison them, to execute code in the containers that make use of these images inside local infrastructure.
Download the Private Registry's Docker Images
As mentioned, the Docker Registry exposes an API REST that can be queried via HTTP. If a Registry exposed to the internet is found, then it is possible to see all the Docker images uploaded there by acceding the following URL:
It is also possible to see the tags for each image by making this request:
In the example above is observed that the Docker image "webapp" has only one tag named "latest".
In the building process of every image, Docker creates a layer for each instruction (or step) that makes up the image. It is possible to get the information about every layer by making the following request:
It will download a file that can then be opened with any text editor.
Each sha256 hash seen in the file, represents a layer of the Docker image.
Once this information is known, then it is possible to download the contents of each layer with another request to the API. To do this, it is necessary to specify the sha256 of the image layer, as follows:
It will download a file whose extension has to be renamed to .tar.gz.
Once downloaded, it can be decompressed and its contents inspected. This will show the status of the Docker image's filesystem in that specific layer.
Sometimes developers may enter passwords or other sensitive information during the build process, that is then removed in later layers. As a result it is important to inspect each of the image layers, as they may contain sensitive information that is not present in the final image.
So far only direct interaction with the Docker Registry API has been considered. However, it is possible to add the remote registry to a local Docker installation in order to interact with it using the Docker client. For that the following information must be added inside the daemon.json file located at the /etc/docker path.
Once completed, it is then possible to interact with the remote registry using the local Docker client.
docker image pull command it is possible to download the remote Docker images to a local computer.
After that, a container of that image can then be executed on the local machine by using the
docker container run command, as the picture above.
Poison the Private Registry's Docker Images
After running a container using the images downloaded, a shell inside such container can then be opened in order to alter the filesystem as desired. In the following example, since the image is an Nginx, the content of
index.html was replaced with "Docker Image Poisoned!".
As this modification was made in the local machine, then in the following steps a new image will be created from this container and uploaded to the remote Docker Registry.
By using the
docker container commit command, it is possible to generate a new image from a given container ID. The ID of the modified container must be specified.
The next step after making the commit, it is to tag the new image. To achieve that the
docker tag command is used, with the image ID as parameter, followed by the remote Registry IP address, port and repository name.
The picture below shows how the poisoned image then becomes the latest version available.
Finally, the poisoned image can be pushed to the remote Registry by using the
docker push command.
The next time the Docker daemon runs a container of this image and downloads it, instead of downloading the original it will download the poisoned version. Therefore, the service executed in the cluster will be affected by the attack.
A disadvantage of this attack is that the poisoned image will only be executed with a new container. There are many reasons why a container may be destroyed and recreated, sooner or later the image will be pulled and used if the poisoning is not detected.
From a defensive point of view, it is important that the docker registry API is not exposed to the internet or insecure environments. Another effective measure is to make use of DCT (Docker Content Trust) to ensure that only images signed by the original developers are used in the cluster, this will avoid malicious image execution.
Do you want to learn more?
We will give a two-day training about Attacking and Securing Docker & Kubernetes Environments at HITBSecTrain (Online - Europe timezone).
Sheila A. Berta
Head of Research at Dreamlab Technologies