Last updated on 2019-05-03
Contents
Preface
This blog post presents some examples and explains some concepts that can generally be applied to any VM that you’d craft with Vagrant, such as configuring the amount of memory and CPU available to the VM.
That said, the intent of this article is to demonstrate how to craft a VM for the purposes of studying and practicing offensive security skills; therefore, certain configuration choices I’ve made are rooted on that intent, and I will explain along the way why I’ve made them.
The examples in this article were executed on Debian Buster, which was Debian’s testing distribution at the time of writing, and with Vagrant 2.2.3 and VirtualBox 6.0.
Lastly, you may find the following article useful before continuing:
Assigning VM resources
Assuming you’ve installed Vagrant and initialized a project, let’s get to work on Vagrantfile
.
The following configuration is pretty self-explanatory, with sensible defaults for a VM without a desktop environment:
...
hostname = "attack-vm"
config.vm.provider "virtualbox" do |vb|
# What will the VM be called within VirtualBox?
vb.name = hostname
# Display the VirtualBox GUI when booting the machine
vb.gui = false
# Customize the amount of CPU cores on the VM:
vb.cpus = 2
# Customize the amount of memory on the VM:
vb.memory = "4096"
# Customize the amount of video memory on the VM:
# vb.customize ["modifyvm", :id, "--vram", "64"]
end
...
The attack VM will indeed not have a desktop environment; we’ll use vagrant ssh
to connect to it, and run all of our attacks from the terminal, so the above settings will work just fine.
Network configuration
Host-only interface
With the basics all set, let’s move on to configure a DHCP-enabled private network as follows:
...
# Create a private network, which allows host-only access to the machine
# using a specific IP.
config.vm.network "private_network", type: "dhcp"
...
This configuration will cause Vagrant to create a host-only network named vboxnet0
and addressed as 172.28.128.0/24
.
If you have multiple Vagrant VMs configured the same way as shown above, no additional host-only networks will be created. Rather, they will all share vboxnet0
.
The 0
in vboxnet0
will increment for each host-only network that already exists and was not created by Vagrant.
Despite their name, host-only networks not only allow communication between the host (your physical machine) and each guest (VM), but also among guests.
Therefore, two distinct VMs configured with network interfaces assigned to the host-only network vboxnet0
will be able to send and receive network communication to and from each other.
The ability for guests to communicate over a host-only network is a feature of VirtualBox and does not depend on Vagrant. If one VM was provisioned with Vagrant and another VM was downloaded from, say, vulnhub.com, and both were configured to use vboxnet0
, they will be able to communicate.
What host-only networks do not provide is Internet access. If Internet access is desirable, a separate network interface should be configured, preferably as a NAT interface.
For details about networking in VirtualBox, see https://www.virtualbox.org/manual/ch06.html.
NAT interface
Turns out that Internet access is desirable for our attack VM. Also turns out that we don’t need to do anything because Vagrant already configures the first network adapter of every Vagrant-provisioned VM as a NAT adapter.
The host-only adapter that Vagrant will create from config.vm.network "private_network", type: "dhcp"
will actually become the second network adapter in the VM.
Done!
Provisioning: customizing the VM
Vagrant supports a few different methods of provisioning VMs. Among the simplest, requiring no additional tools, are the file and shell provisioners; the former tells Vagrant to copy supplied files/directories to the VM, while the latter tells it to execute supplied shell scripts within the VM.
We’re going to provision the attack VM by specifying the following provisioning commands within our Vagrantfile
:
...
# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
config.vm.provision "shell", inline: "apt-get install -qq apt-transport-https"
config.vm.provision "file", source: "sources.list", destination: "/tmp/sources.list"
config.vm.provision "file", source: "dotfiles", destination: "/home/vagrant/"
config.vm.provision "shell", path: "provision-as-root.sh"
config.vm.provision "shell", path: "provision.sh", privileged: false
...
Each invocation of a provisioner starts with config.vm.provision
, followed by the provisioner type specified as a string, and additional parameters.
Each file provisioner will copy the specified file/directory from the host to the specified destination within the VM. Paths for specifying source files are relative to the project directory; i.e., where Vagrantfile
resides.
Keep in mind the following limitation of Vagrant’s file provisioner:
The file uploads by the file provisioner are done as the SSH or PowerShell user. This is important since these users generally do not have elevated privileges on their own.
Source: https://www.vagrantup.com/docs/provisioning/file.html
Therefore, the first file provisioner will copy a file called sources.list
from the host’s project directory to /tmp/sources.list
within the VM – not directly to /etc/apt/sources.list
, since this provisioner does not run as root. As you may have guessed, the provision-as-root.sh
script will take care of that step.
The second file provisioner will copy the entire dotfiles
directory to /home/vagrant/
in the VM. Take note of these caveats when the intent is to copy multiple files or entire directories.
The very first shell provisioner simply executes a quick inline script to install apt-transport-https
so that all of our subsequent packages will be downloaded over TLS.
Following the file provisioners are two shell provisioners which will make use of the copied files and perform other provisioning tasks, such as tweaking the operating system as well as downloading, installing and configuring software.
Running as the root user with full privileges is the default behavior of shell provisioners. In order to run a shell provisioner as the built-in vagrant
user, who is not all-powerful, specify privileged: false
when invoking a shell provisioner.
The contents of provision-as-root.sh
and provision.sh
are shown below with inline comments.
Appendix
Vagrantfile
...
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
config.vm.box = "debian/buster64"
...
# Create a private network, which allows host-only access to the machine
# using a specific IP.
config.vm.network "private_network", type: "dhcp"
...
# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
hostname = "attack-vm"
config.vm.provider "virtualbox" do |vb|
# What will the VM be called within VirtualBox?
vb.name = hostname
# Display the VirtualBox GUI when booting the machine
vb.gui = false
# Customize the amount of CPU cores on the VM:
vb.cpus = 2
# Customize the amount of memory on the VM:
vb.memory = "4096"
# Customize the amount of video memory on the VM:
# vb.customize ["modifyvm", :id, "--vram", "64"]
end
# View the documentation for the provider you are using for more
# information on available options.
# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
config.vm.provision "shell", inline: "apt-get install -qq apt-transport-https"
config.vm.provision "file", source: "sources.list", destination: "/tmp/sources.list"
config.vm.provision "file", source: "dotfiles", destination: "/home/vagrant/"
config.vm.provision "shell", path: "provision-as-root.sh"
config.vm.provision "shell", path: "provision.sh", privileged: false
# What will Vagrant call this VM?
config.vm.define hostname
# What will be the VM's hostname?
config.vm.hostname = hostname
end
sources.list
deb https://deb.debian.org/debian buster main contrib non-free
deb-src https://deb.debian.org/debian buster main contrib non-free
deb https://deb.debian.org/debian-security/ buster/updates main contrib non-free
deb-src https://deb.debian.org/debian-security/ buster/updates main contrib non-free
deb https://deb.debian.org/debian buster-updates main contrib non-free
deb-src https://deb.debian.org/debian buster-updates main contrib non-free
provision-as-root.sh
#! /bin/bash
# To avoid warnings during provisioning; for more information, see:
# https://www.debian.org/releases/stable/amd64/ch05s03.html.en#installer-args
export DEBIAN_FRONTEND=noninteractive
cp /tmp/sources.list /etc/apt/sources.list
dpkg --add-architecture i386
apt-get update
apt-get upgrade -qq
#----- System utilities -----#
apt-get install -qq apt-file
apt-get install -qq mlocate
apt-get install -qq ntp
apt-get install -qq tree
apt-get install -qq vim
#----- Programming -----#
apt-get install -qq git
apt-get install -qq golang
apt-get install -qq python3
apt-get install -qq python3-dev
apt-get install -qq python3-pip
apt-get install -qq python3-venv
apt-get install -qq subversion
#----- Networking and reconnaissance -----#
apt-get install -qq arping
apt-get install -qq curl
apt-get install -qq ncat
apt-get install -qq net-tools
apt-get install -qq nikto
apt-get install -qq nmap
apt-get install -qq python3-nmap
apt-get install -qq smbclient
apt-get install -qq sshuttle
#----- RE and binary exploitation -----#
# apt-get install -qq radare2
#----- Virtual display -----#
# apt-get install -qq fluxbox x11vnc xvfb
#----- Graphical tools -----#
# These require a virtual display or a full graphical environment
# apt-get install -qq wine32 wine64
# apt-get install -qq chromium-driver python3-selenium
#----- Metasploit and searchsploit -----#
# apt-get install -qq curl git gnupg
# curl -sS https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > /tmp/msfinstall
# chmod 700 /tmp/msfinstall
# /tmp/msfinstall
# git clone -q https://github.com/offensive-security/exploitdb.git /opt/exploitdb
# sed 's|path_array+=(.*)|path_array+=("/opt/exploitdb")|g' /opt/exploitdb/.searchsploit_rc > ~/.searchsploit_rc
# ln -sf /opt/exploitdb/searchsploit /usr/local/bin/searchsploit
#----- Reporting utilities -----#
# https://weasyprint.readthedocs.io/en/latest/install.html#linux
# apt-get install -qq pandoc
# apt-get install -qq build-essential
# apt-get install -qq python3-dev
# apt-get install -qq python3-pip
# apt-get install -qq python3-setuptools
# apt-get install -qq python3-wheel
# apt-get install -qq python3-cffi
# apt-get install -qq libcairo2
# apt-get install -qq libpango-1.0-0
# apt-get install -qq libpangocairo-1.0-0
# apt-get install -qq libgdk-pixbuf2.0-0
# apt-get install -qq libffi-dev
# apt-get install -qq shared-mime-info
which apt-file && apt-file update
which locate && updatedb
exit 0
provision.sh
#! /bin/bash
which vim && cp /usr/share/vim/vim*/defaults.vim ${HOME}/.vimrc
for item in $(ls -AC1 ${HOME}/dotfiles)
do
# Copy or append the contents of each file in ${HOME}/dotfiles
# into the corresponding file at ${HOME}
[[ -f ${HOME}/dotfiles/${item} ]] \
&& touch ${HOME}/${item} \
&& cat ${HOME}/dotfiles/${item} >> ${HOME}/${item}
# Recursively copy the contents of each directory in ${HOME}/dotfiles
# into ${HOME}
[[ -d ${HOME}/dotfiles/${item} ]] \
&& cp -a ${HOME}/dotfiles/${item} ${HOME}
done
# Install gobuster if go is already installed
if which go
then
git clone -q https://github.com/OJ/gobuster ${HOME}/gobuster
pushd ${HOME}/gobuster
export GOPATH=$(pwd)
go get
go build
echo "alias gobuster=$(pwd)/gobuster" >> ${HOME}/.bashrc
popd
fi
# Install weasyprint and publisher if pandoc is already installed
if which pandoc
then
# weasyprint easily converts HTML to PDF
pip3 install weasyprint
# publisher is a script that reads markdown files
# and creates a styled PDF report out of them
git clone -q https://github.com/elespike/publisher ${HOME}/publisher
fi
# If not already present, write an export call to ${HOME}/.bashrc
# that will add /usr/sbin and ${HOME}/.local/bin to ${PATH}
append_to_path=":/usr/sbin:${HOME}/.local/bin"
grep ${append_to_path} ${HOME}/.bashrc \
|| echo "export PATH=${PATH}${append_to_path}" >> ${HOME}/.bashrc
sudo reboot