TL;DR

I’ve created a dockerfile that starts a kalilinux/kali-rolling image with KDE, ssh, x11vnc and noVNC preinstalled. Which can further be seasoned to your individual taste. For a fast and quick start see the pullkali.sh script and modify the mods variable to change the image. For the customizable dockerfile and a simplified docker-build script see the github repository or if you want to get started immediately without changes see the setup steps at the dockerhub repository.

But why?

Kali is a great way to separate your daily driver from your pentesting tools. With a full desktop being a necessity for many applications, virtual machines or dual booting have been the leading choices for many testers in the field. However, these approaches always felt clunky to me.

Dual booting locks you from the tools of your daily driver. Having to restart your PC in order to start writing your report is a time consuming process that should not be necessary anymore in 2020.

Virtual machines insert a thick layer of virtualization between your host and Kali. While getting a lot closer and more flexible than dual booting, this still offers cumbersome issues that are annoying to deal with. From NAT port forwarding, gigantic VM storage files and cumbersome shared folders in order to share files between host and vm, the virtualization solution has left me with a desire for a better alternative.

Thus, I have set out my sights for a solution that allows for both separation and seamless integration at once. This is where Docker came in.

Sure, Docker was never meant to run persistent operating systems. But the more I thought about it, Kali isn’t supposed to be that. To me, Kali is just a chain of tools that are great for pentesting. Ready to be thrown away and rebuilt as needed. All you need to store is your projects and files that you are working on.

Plus, I could use it as an excuse to learn docker how Dockerfiles work!

Breaking it down

In this section we’ll break down how various sections work, we’ll also describe how you can modify the pullkali.sh script and dockerfile to match your own flavor of security testing.

Dockerfile

It is always good to know what you’re running on your machines and why! As such, we’ll take a quick look at the lines inside the Dockerfile at the current time of writing and explain the reasoning behind them. We’ll skip over the basic parts to keep this short.

RUN export DEBIAN_FRONTEND=noninteractive && \
    apt-get update && \
    echo 'Installing desktop files, this may take a few minutes...' && \
    apt-get install -y \
        kali-defaults \
        desktop-base \
        kali-desktop-kde \
        kde-plasma-desktop \
        --show-progress

Here we install all the requirements to run a desktop environment in Kali. These have been separated into a new layer to ensure that later layers can be changed and rebuilt without having to rebuild this layer.

RUN export DEBIAN_FRONTEND=noninteractive && \
    apt-get update && \
    echo 'Installing base files, this may take a few minutes...' && \
    apt-get install -y \
        curl \
        sudo \
        net-tools \
		[...]
        --show-progress && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    mkdir ~/.vnc && \
    x11vnc -storepasswd admin ~/.vnc/passwd

Here we install a number of standard packages common on linux systems and required to run the VNC and SSH servers. Finally, we also tell x11vnc to store a password in ~/.vnc/passwd. Despite the fact that this password is not used, it still prevents annoying messages from x11vnc that would otherwise clog up the logs.

RUN cd /root && git clone https://github.com/kanaka/noVNC.git && \
    cd noVNC/utils && git clone https://github.com/kanaka/websockify websockify && \
    ln -s /root/noVNC/vnc_auto.html /root/noVNC/index.html

Here we clone the repositories required to run noVNC.

RUN echo "Port 22000\nPermitRootLogin yes" >> /etc/ssh/sshd_config && \
        echo "root:toor" | chpasswd && \
        service ssh restart

Here we enable ssh on the server and set the following appropriate settings:

  • Port 22000: As the name states, we want to run ssh on port 22000 to prevent any potential clashes with an ssh server running on the host.
  • PermitRootLogin yes: We want to connect as root, so this needs to be set to yes

We also set the password for root using the chpaswd application.

RUN echo 'Installing additional packages...' && \
        export DEBIAN_FRONTEND=noninteractive && \
        apt-get update && \
        apt-get install \
        kali-tools-top10 \
        -y --show-progress && \
        mkdir /usr/share/wordlists && \
        ln -s /usr/share/wordlists /root/wordlists

As we’ve reached the last step of installations, here we can insert any personal modifications we want to apply.

RUN echo "#!/bin/bash \n\
	[ -e /tmp/.X1-lock ] && rm -rf /tmp/.X1-lock \n\
	export DISPLAY=:1 \n\
	Xvfb :1 -screen 0 1920x1080x16 & \n\
	sleep 5 \n\
	plasmashell  &>/dev/null & \n\
	kwin --replace &>/dev/null &  \n\
	sleep 5 \n\
	service ssh start \n\
	chmod +x /root/post.sh \n\
	/root/post.sh &>/dev/null & \n\
	x11vnc -display :1 -nopw -listen localhost -xkb -forever & # -ncache 10 -ncache_cr can be added for performance. Might give issues with some vnc viewers \n\
	cd /root/noVNC && ./utils/launch.sh --vnc localhost:5900 \n" >/root/startup.sh

Now we’ll drop the startup.sh script into place. This script will run (and keep running) every time the docker container is started. In rough order, it does the following:

  • Remove the Xvfb lockfile if it exists. (Workaround required to make the pullkali.sh workflow functional)
  • Start Xvfb to start a virtual X display server
  • Startup plasma on the newly created interface
  • Start ssh
  • Execute the post.sh script silently
  • Start x11vnc
  • Start noVNC
RUN echo "#!/bin/bash \n\
\#java -jar \`which burpsuite\` & &>/root/log & \n" >/root/post.sh

Write out a template file for the post.sh script.

buildkali.sh

All of the steps required to build your local kalidesktop dockerfile are prepared in the buildkali.sh script. Here we’re stepping through a few of the steps in there and explain the reason why we do them.

echo 'Creating volume...'
sudo docker volume create kalivol

First off, we create a volume for the docker container. This is required as certain applications (cough cough Burpsuite professional cough) don’t like being installed headless and/or being activated repeatedly. As such, we create this volume for weird one off applications that cannot be reinstalled easily. Furthermore, this volume can also be used to store configuration files of IE nikto.

echo 'Building image, this might take a while...'
sudo docker build -t kalidesktop kalidesktopdocker/

As the echo states, we’re building the image here. Depending on your internet speeds, this step may take a while but should only be required once per device. Assuming caching is enabled, you can make more personalization changes without having to rebuild the whole image from scratch (more on personalizations later).

echo 'Finished building! Running kali and waiting...'
sudo docker run -d --network host --privileged -v $HOME:/home/$USER --mount source=kalivol,target=/opt/vol/ kalidesktop
sleep 30

Here we’re running the container with the following flags:

  • -d: We’re starting detached. The container should run in the background.
  • --network host: We’re giving the container full access to the machines network interface. This makes interacting with the machine much easier.
  • --privileged: Here we’re giving the docker container additional rights on the host machine.
  • -v $HOME:/home/$USER: Here we mount the users' home folder. This way, any file in your host home folder becomes available in the /home/$USER folder in the container. Be sure to read the warning.
  • --mount source=kalivol,target=/opt/vol/: Here we mount the kalivol volume we’ve created earlier to /opt/vol. As can be read above, this is for storing and installing special cases.

Finally we’re adding a 30 seconds sleep to allow the container to start itself and its services before we attempt to connect to it.

echo 'How do you want to connect?'
select CHOICE in vnc novnc ssh none ; do

  case $CHOICE in
    vnc)
	echo 'Opening your default vnc viewer'
	xdg-open vnc://localhost:5900 || echo 'Opening vnc viewer failed. Please install a vnc viewer and connect to localhost:5900' &
        ;;
    novnc)
	echo 'Opening your default browser...'
	xdg-open http://127.0.0.1:6080/vnc.html &
        ;;
    ssh)
	echo 'Open a root shell with the following command, password "toor"'
	echo 'ssh root@localhost -p 22000 -X'
        ;;
    none)
        ;;
    *)
  esac
  break # break avoids endless loop
done
echo 'All done! Happy hacking!'

Finally, with a little bash-fu, we offer the user a few choices to automatically connect to the container.

On average, it takes roughly 20 minutes to build the dockerfile with wired internet connectivity. time results:

real	19m58,532s
user	0m0,752s
sys	0m0,468s

pullkali.sh

A faster way to build is by using dockerhub for prebuilt images. The pullkali.sh script leverages this while still offering the user the ability to make personal modifications to the downloaded images. Doing this effectively quarters the time neccesary to start a customized kali docker experience.

Let’s step through the file and describe what we’re doing with each step:

mods='export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install nmap'

This variable is set to allow the user to modify the downloaded image with. Any personal changes can be applied here.

echo 'Pulling the latest image from dockerhub, this might take a few minutes...'
sudo docker pull newlinedotblog/kalidesktop

Here the latest docker image is being downloaded from dockerhub. Since this download (at the current time of writing) comes down to a total of 2.36 GB of data, this might take a few minutes. For the latest information of the size of the download and an indepth breakdown see the dockerhub tags page.

echo 'Creating volume'
sudo docker volume create kalivol

As discussed in the buildkali section, this is required for certain oddball applications and configuration files.

echo 'Tagging the base image'
sudo docker image tag newlinedotblog/kalidesktop kalidesktop-base

Here we give the downloaded image a unique tag. This is done to ensure that we can rename the final image back to simply kalidesktop and ensure it doesn’t clash with the initially downloaded image from dockerhub.

echo 'Starting the base container...'
contname=`sudo docker run -d --network host --privileged -v $HOME:/home/$USER --mount source=kalivol,target=/opt/vol/ kalidesktop-base`
sleep 3

echo 'Running personal modifications'
sudo docker exec -it $contname /bin/bash -c "$mods"

echo 'Committing changes'
sudo docker commit $contname kalidesktop

echo 'Stopping base container'
sudo docker stop $contname
sleep 5

unset mods
unset contname

These four steps together allow us to make changes to the prebuilt image from dockerhub and apply our own modifications to them and then store them as a new image. This way, we only have to make the changes once and we can keep using that image until we want to update the entire image agian.

As stated in the comment we have to start by running the base image as we would normally. The $contname variable contains the hash of the running container. Then, while it’s running, we use the docker exec command to run commands inside of the running container. These are the modifications from the $mods variable declared in the first lines. We run this sequentially (instead of detached) to ensure that all the modifications are completed before we move on to the next step.

Then, we leverage the functionality of docker commit. This allows us to save the current container, with applied changes, to a new image. We name the new and final image kalidesktop for future use.

Finally, since we’re done with the base image, we tell it to stop. We also unset our variables here since we no longer need them.

The sleeps injected in between are for safety reasons. The sleep between stopping the base image and the next line below is to ensure the ports have been freed before reusing them when starting the modified personal container.

echo 'Starting personalized container'
sudo docker run -d --network host --privileged -v $HOME:/home/$USER --mount source=kalivol,target=/opt/vol/ kalidesktop
sleep 5

Finally, we’re running the personalized image as a new container in detached mode. For a full breakdown of the flags used here see the same part in the buildkali section. The next section simply offers the user ways to connect to the container, this is also already covered in the buildkali section.

Average build times for this set up come down to:

real	4m46,247s
user	0m1,268s
sys	0m0,902s

Personalizations

A bland docker image of Kali that keeps resetting each time you update is of no use to anyone. So built in to the two different ways of using this docker image are ways to make them yours and fit your personal style of using Kali.

In-Image modifications

These changes are changes that need to be run only once and shouldn’t be re-executed each time the container starts. Examples of these are making changes in configurations, installing additional packages and writing out files.

Building locally

There is a section labeled # Add any personal modifications to the docker here available in the Dockerfile. Any changes you make to this layer will ensure that rebuilding the image afterwards won’t take too much time as long as caching is enabled.

Using Dockerhub

As described in the pullkali section, using the docker commit function, we can run create a new image from the base image that was just downloaded from dockerhub. Using the $mods variable in the pullkali.sh command, we can apply changes in an automated way.

‘Run at start’ modifications

Some commands you might want to start every time the container is started. For this reason, /root/post.sh exists. This script is being called from the startup.sh script every time the container is started. This script is a good place to put actions such as starting services and programs you know you’ll want to use each time you start up the container.

Building locally

In the last lines of the Dockerfile, a placeholder post.sh script is being inserted. Inside of it you can find an example (commented out) command running Burpsuite. You can change the contents of the file here. Be sure to end each non-last line with \n\ to ensure the file is being written out correctly.

Using Dockerhub

Using the same $mods variable, commands can also be inserted into the post.sh script. For example, the following mods command will run nmap -V on each start echo "nmap -V" >> /root/post.sh.

WARNING

Use this container with care! As you can see above this machine (if you do not modify the running parameters) has full access to your home folder and your network devices. Any rogue executed sudo rm -rf --no-preserve-root / will also affect your hosts home folder. If this risk is too great to tempt, I recommend changing the docker run parameters to suit your liking.

Conclusion

In this project I’ve created a dockerfile project that allows for easy customization with a robust base to work from. With this new dockerfile, a user can get started with his own customized version of the kalilinux/kali-rolling docker image within 5 minutes.

Inspirations

For the creation of this project I’ve let myself be inspired by a number of projects. Have a look at them and follow their stuff too if you like them: