Docker and solutions like it are useful because:
- Application deployments occur in identical environments, eliminating per-host quirks
- Process isolation strategies incur lower overheads than emulating hardware devices
- Images can be built, shared, and re-used in a repeatable way
Lately I’ve been looking at vulnerable webapp sandboxes such as DVWA to test vulnerability scanners like Arachni against. Therefore, why not try using docker as an easy way to compartmentalize this and make deployment easier?
Not only will this tutorial be helpful in understanding how Docker works, but security enthusiasts will get a ridiculously easy way to deploy DVWA on (most) OSes. Let’s get started!
In this howto I’m going to explain:
- Docker itself
- Creating custom Docker images
- A step-by-step custom DVWA Dockerfile example
The end result of this exercise will be a Dockerfile and Docker image that can run DVWA for you with the ease of a
docker run dvwa.
Docker is still under heavy development but works perfectly well for experimental and development purposes like ours. To learn all about the technology and philosophy behind docker, take a look at their excellent getting started page.
Look to that same page to get Docker installed. Note that Docker uses very Linux-specific technologies like cgroups and Aufs to work, so if you’re not on a relatively up-to-date Linux distro, you’ll likely have to run on top of a thin VirtualBox VM for this exercise.
Why am I using Docker in a virtual machine? Doesn’t that defeat the purpose?
For this exercise? A little. But by following the getting started guide, you’re preparing to run other docker images essentially instantaneously, and have the capability to deploy them on any Docker instance, whether on your desktop, EC2, a VPS, etc.
Use Docker’s excellent getting started page to set up docker (most unices and Windows are supported, with the aforementioned caveats.) Once you can invoke
docker version and see both client and server versions, you’re good to get going.
Note: OS X users: be sure to read the additional comments regarding opening ports for your VirtualBox image obtained from boot2docker.
We’re going to be setting up DVWA within a Docker container to be a webapp security testing sandbox. DVWA requires a basic LAMP stack, so let’s begin there.
With your working
docker command, just issue the following command to find Docker images that contain the string ‘lamp’:
What’s happening here is that the Docker index is being searched for matching images. Many of these images are based off of even more primitive images and customized with Dockerfiles, which is what we’ll be doing to an image that provides the LAMP stack we need.
We’re going to use
dockerfiles/centos-lamp for our base image, as CentOS is pretty solid and we can explore its existing dockerfile pretty easily.
Issue the command:
While that’s running, take a look at the image’s source. That Dockerfile you’re looking at, along with the accompanying files from the corresponding github repo, are what created the image you’re pulling from the index right now – pretty simple.
I’d encourage you to read the short Dockerfile and look at some Dockerfile resources to understand what’s going on there. We’ll be doing the same ourselves with it as a base image.
With the image pulled from the index, check that it’s available with:
(if the image isn’t listed, try to
Now we can try running the container. There are a myriad of switches that you can pass to the
docker run command, but we’re going to use just a couple here to achieve what we need:
Wait for the command to successfully return (Docker will return the numeric ID of the new container it’s running.) then:
If you see a running image, you’re good to go! Try browsing to http://localhost:49001. You should see the boilerplate CentOS landing page. Try browsing to http://localhost:49001/phpinfo.php as well to confirm php is working.
So what happened here? In short:
docker runinstructs the Docker daemon to begin execution of a container
-dflag sets the execution to begin in daemon mode, e.g. run in the background
- Apache’s port 80 listener is exposed via the -p flag to the local port 49001
- The CMD directive in the image’s Dockerfile runs apache and mysql under supervisord (a small python application that keeps programs daemonized)
A successful phpinfo page indicates our container is up and accessible. Time to customize it.
Dockerfiles are easy-to-understand instructions that tell Docker how to build an image. Each RUN directive in a Dockerfile executes commands, then “commits” those changes to the filesystem to the image you’re building like git or mercurial would do.
The best way to visualize this is to write one. Following where we left off, we’ve got a Docker image that can run a lamp stack for us - the same architecture that DVWA needs to run. DVWA is easy to set up – the steps to install are:
- Download and extract the source
- Set relevant database connection credentials
- Run apache and mysql
First, if your old container is still running, stop it:
Now create a working directory for your Docker image source:
Now we’re in our Dockerfile. Here are the directives we’ll use to start off with:
- First we define the base image we’re building off of – the same one we ran phpinfo on. It gives us an environment with apache, mysql, and php.
- Identify who built this Dockerfile.
MAINTAINER You <firstname.lastname@example.org>
- All directives that follow WORKDIR will take place in the indicated path. By default CentOS places Apache’s files here, so we’ll do the same with DVWA.
- Now we’re going to make some actual filesystem changes with
RUN. All this directive does is fetch the DVWA source and pipe the tarball to standard output, which tar extracts into the current working directory (the strip-components just gets rid of the containing directory so that all the source files are directly extracted to the working directory.)
RUN wget https://github.com/RandomStorm/DVWA/archive/v1.0.8.tar.gz -O- | tar xvz --strip-components=1
- We’ve got the source code, now we need to ensure DVWA can talk to our database. The following
RUNdirective will start MySQL, set root’s password to the DVWA default and stop MySQL again (default passwords usually aren’t a good thing, but DVWA is vulnerable by definition, right?)
RUN service mysqld start && mysqladmin -uroot password p@ssw0rd && service mysqld stop
- Expose port 80 in the container so that when we run Apache, we can access it:
- On a normal host, you’d run MySQL and Apache with init scripts. However, Docker and similar solutions operate under the assumption that you’re using the container to run a single process. If we were to break out this project into a MySQL container and an Apache container that would work fine, but we want to wrap everything into one container. Supervisord is a python program that can run and keep track of multiple processes, so by using supervisord in the same way as the base image we’re basing this off of, MySQL and Apache run side by side.
CMD ["supervisord", "-n"]
Which, put together, gives us our master Dockerfile:
Now, from within the directory that contains the Dockerfile, issue the command:
You’ll see lots of stuff fly past the screen – mostly fetching the source code and extracting it. We also passed the
-t flag to the build command to tag our image with the name ‘dvwa’ so that we can run it easily without using the new image’s hash. Once it’s finished, look for your newly minted image.
The ‘dvwa’ image is your newly build docker image. Issue the following command to run the container in daemonized mode and tell it to auto-assign a port for you:
ps command will show you the external listener that Docker has mapped to port 80 on the container. Browse to http://localhost:[port] and DVWA will ask you to initialize the database. Just follow the instructions for DVWA from here on out.
While running, there are some useful commands you can use to control the running container. The
ps command will offer you some information about all containers, most usefully exposed ports.
docker logs <container id> will show you what the container has been logging (try it out to see supervisord’s output.) When you’re finished, use
docker stop <contiainer id> to stop the container.
Note: Docker currently keeps around stopped images until removed. Although this preserves some state about your completed processes, if you want to start fresh images without cluttering up your disk, you can use
docker ps -a to find all containers (running or not) and
docker rm to forcibly remove running or terminated containers.
Where From Here?
At this point you have a ready-made DVWA container for pentesting or similar exercises. You can take your Dockerfile and build images anywhere, wipe DVWA afresh if you bork your testing environment and start a new container, and run the image wherever Docker can run.
As you can see, building images is fairly easy and lightweight. If anybody cares to create additional useful Docker images, feel free to email me or collaborate on github and I’ll add additional notes about other Docker images.
The slightly-enhanced version of this Dockerfile can be found on my github repo.