Table of Contents

Over the last few years I have done a complete 180 on Docker (well, containerization in general). One of the very first posts I wrote on this blog was about plundering Docker images, and at the time I was not a fan. I didn't see the benefit of Docker, I thought it was confusing, I thought it was a fad and I couldn't understand why anyone thought it was better than just a VM.

Fast forward 3 years and Docker has become an indespensible part of my day-to-day workflow, both professionally and personally.

My hope in this post is to demonstrate some of my usecases and workflows, and illustrate how I think pentesters and security professionals in general can greatly benefit from Docker.

Background

Everybody has probably seen a diagram like this when Googling "Container vs VM" (in fact, this one is the top result)

Container vs VM

But that doesn't really explain much. Conceptually I always understood how containers technically differed from VMs, but I didn't understand the benefit to me. Now I won't go in to the benefit of containers for an organization (although I could easily!), but I'll focus on how Docker improves my workflow as a pentester (and developer).

I use Docker containers as fast, purpose built, disposable environments for testing things and running applications. I think of containers less as a "Virtual Machine", and more as a self-contained executable or a "virtualenv" for an OS. And no matter what machine I am on (work, personal, etc), I will always have the exact same experience.

In the following examples, hopefully you'll see the benefits of Docker containers. If you're of the camp that "VM's are still better", I won't try to convince you. If your workflow is amazing with VMs, maybe even automated using Vagrant and Packer, that's awesome. Keep doing you. This works for me and I just wanted to share :)

Useful Docker Aliases

I make heavy use of aliases when using Docker. These are an absolute life saver to me, and IMO should be added for anyone using Docker:

alias dockershell="docker run --rm -i -t --entrypoint=/bin/bash"  
alias dockershellsh="docker run --rm -i -t --entrypoint=/bin/sh"

function dockershellhere() {  
    dirname=${PWD##*/}
    docker run --rm -it --entrypoint=/bin/bash -v `pwd`:/${dirname} -w /${dirname} "$@"
}
function dockershellshhere() {  
    dirname=${PWD##*/}
    docker run --rm -it --entrypoint=/bin/sh -v `pwd`:/${dirname} -w /${dirname} "$@"
}

What do these do? They simply let me specify an image and drop into an interactive shell in a fresh, disposable environment. I can look around, make changes, test things, and then when I'm finished and type "exit", everything is destroyed and cleaned up.

dockershell executes /bin/bash, while dockershellsh executes /bin/sh. This is useful since not every Docker image has bash installed.

The ones that end in here take it one step further and mount my current working directory inside the disposable container. This lets me interact with my files inside the disposable environment, and anything I write to that directory while inside the container persists after exit.

For example:
dockershell ubuntu

No spinning up a VM, no SSH, just instant shell access to test something out. And when I'm done it's destroyed and the next time I run it I have a fresh instance again.

I'll use these aliases a lot in the examples below.

Example 1 - Exploring other OSs

Early on in my pentest career, I would find myself with either a blind command injection or LFI on a server but didn't really know what to look for. I spent hours spinning up VMs to match the OS version or application stack just to do some local reconnaissance to know what to look for. With Docker this is painless now.

Let's say I have LFI on a Tomcat 6 box - what files exist that I should go after? I'll just drop into a shell on a Tomcat 6 docker image and go exploring:

dockershell tomcat6

This pulls the official Tomcat 6 Docker image which is based on Debian 8 and has Tomcat pre-installed. I can start exploring the configuration files and figure out which ones exist and might contain sensitive information (e.g. conf/server.xml or conf/tomcat-users.xml)

Or what if I found an old CentOS box and need to know what version of glibc is on it? Instead of downloading an ISO and booting a VM, I can use Docker:

dockershell centos

Note: I highlighted my kernel version to demonstrate a point. I'm running on a very new Kernel because it's my Docker host's kernel - this is one of the key differences between containers and VMs

CentOS 4 appears to have glibc 2.3.4

Example 2 - Compiling Code for old targets

Taking the last example one step further, what if I need to compile something to target an older version of glibc? Back when I was doing boot2roots and CTFs this came up all the time. I needed to compile a specific exploit for a specific version - and often I would download an entire ISO and create a VM just to compile a single file. But now I can do it in Docker easily.

First, I need to install the necessary build tools in a CentOS 5 docker image:

yum install -y perl curl wget gcc c++ make glibc-devel glibc-devel.i386  

But! The official CentOS 5 image doesn't have up-to-date sources to actually install anything. A little bit of Googling and somebody fixed that for me: https://hub.docker.com/r/astj/centos5-vault/

Now I can just dockershell into astj/centos5-vaule and run the yum commands. However, it's annoying to reinstall all the build tools every time I need them. So let's create another Docker image! This is another reason Docker is awesome - I'll just create a purpose-built image that instantly gets me a CentOS 5 box with all the build tools I need. And in terms of storage, this doesn't create a copy or a clone of the CentOS 5 image - I'm just creating another layer on top it.