When playing around with VirtualBox last September, I came across some curious behaviour which I initially thought was a pretty severe vulnerability in VirtualBox: When running an unprivileged program inside a box spawned using Vagrant, it can obtain read and write access to the entire filesystem of the host. Turns out it was not actually a VirtualBox bug, and more of a misconfiguration by Vagrant.

This problem should be considered publicly known but still exists, although Vagrant now prints a warning exactly once on each host. If you are running untrusted code inside a Vagrant box, you should explicitly set the VAGRANT_DISABLE_VBOXSYMLINKCREATE environment variable, e.g. by adding

export VAGRANT_DISABLE_VBOXSYMLINKCREATE=1

to your .profile/.zprofile.

I posed this as the babyvm challenge at 34C3 CTF, where a simplified version was solved within 3 hours, and the hard version was also solved by one team within the 48 hours of the contest (shout out to valis from DragonSector!) So this article kills two birds with one stone by posing as both a mild rant about Vagrant and a writeup for the two CTF challenges.

The Problem

Vagrant by default sets the flag SharedFoldersEnableSymlinksCreate for each synced folder that it creates. This includes the vagrant share, which is enabled in each Vagrant box by default, unless supressed explicitly by adding

config.vm.synced_folder ".", "vagrant", disabled: true

to the Vagrantfile. The VirtualBox documentation has the following to say about this flag:

For security reasons the guest OS is not allowed to create symlinks by default. If you trust the guest OS to not abuse the functionality, you can enable creation of symlinks for “sharename” with:
VBoxManage setextradata "VM name" VBoxInternal2/SharedFoldersEnableSymlinksCreate/sharename 1

While the documentation could admittedly be clearer about the implications of enabling this flag, Vagrant apparently assumes that all boxes are implicitly trusted, which goes at least against my personal intuition: Vagrant in my mental model is just a very thin wrapper around the hypervisor used, and should not add additional attack surface. In short, I would expect it to give me the same security guarantees that VirtualBox gives me.

Accessing shared folders from an unprivileged context

Shared folders in VirtualBox are implemented as a HGCM service (short for Host-Guest Communication Manager). HGCM involves a rather simple RPC protocol through which the guest can make function calls to the hypervisor. Several other features of VirtualBox which also require guest cooperation are implemented through this mechanism, such as clipboard sharing, drag & drop and 3D acceleration.

In order to initiate a HGCM call, for example to read or write a file inside a shared folder, or put data into the clipboard, a custom request has to be made to the special VMM (Virtual Machine Monitor) device exposed to every VM via PCI. This would usually require kernel privileges. However, if the VirtualBox guest additions are installed, requests can be made through the VBoxGuest driver, exposed to the “world” – i.e. unprivileged processes in the guest – in the form of the VBoxGuest device on Windows, and the /dev/vboxuser device on Linux. Since Vagrant’s synced folders make use of the shared folders feature, Vagrant boxes will typically come with the VirtualBox guest additions already installed.

A quick side note: After I reported CVE-2018-2693, a privilege escalation + DoS in the guest via /dev/vboxuser on Linux, Oracle disabled the device completely in the 5.2.6 release of the VirtualBox guest additions. However, this was quickly reverted in the 5.2.7 testing and 5.2.8 stable release, since it turns out some of their features rely on this device being available, a fact which had somehow eluded them during their testing…

The core of the SharedFolders HGCM service is implemented by the function svcCall in the VirtualBox source file src/VBox/HostServices/SharedFolders/service.cpp. Relevant HGCM functions for our purposes are:

  • SHFL_FN_CREATE to open a file in a shared folder
  • SHFL_FN_{READ,WRITE} to read/write from/to a file opened via SHFL_FN_CREATE
  • SHFL_FN_SYMLINK to create a symlink in a shared folder (this requires the SharedFoldersEnableSymlinksCreate flag to be set)

If a shared folder is mounted via the normal guest addition filesystem driver, and a symlink inside the share is opened, it will be resolved inside the guest:

# mount -t vboxsf <sharename> /mnt
# ls -alih /mnt
vagrant@ubu1710:/mnt$ ls -alih /mnt
? drwxr-xr-x  1 root root  44K Mar 25 12:19 .
2 drwxr-xr-x 24 root root 4.0K Nov  5 11:39 ..
3 lrwxrwxrwx  1 root root   11 Mar 25 12:19 test -> /etc/passwd
# sha1sum /mnt/test /etc/passwd
6ba8b11066c8159ea796209eed6b911d5cf1d6bd  /mnt/test
6ba8b11066c8159ea796209eed6b911d5cf1d6bd  /etc/passwd

In particular, the filesystem driver will first query the file type to figure out if it is a symlink, then issue a SHFL_FN_READLINK call to resolve the link, and then recurse back into the kernel to open the resulting path. But instead, since SHFL_FN_CREATE does not check if the requested file path is a symlink (and it would probably be racy if it tried), we can just as well force the symlink to be opened on the host.

My CTF challenge had an unintended easy way to obtain root privileges inside the guest – it turns out passwd -d root does not “delete” the root password but empties it; maybe I should read manpages more often. Team “pasten” noticed that the official filesystem driver had a flag that would make it open symlinks host-side, such that the following was an easy solution to the challenge:

$ su
# umount /vagrant
# rmmod vboxsf
# modprobe vboxsf follow_symlinks=1
# mount -t vboxsf vagrant /vagrant
# ln -s /flag /vagrant/flag
# cat /vagrant/flag

This would print the contents of the file /flag on the host. Had I not explicitly marked the shared folder as read-only in order to make the challenge somewhat robust, writing a file on the host would have been possible just as easily.

After they came up with this solution only 3 hours after the beginning of the CTF when I intended this challenge to be on the more difficult end of the scale, I decided to add a second version of the challenge called babyvm2, where hopefully it was not possible to easily become root. Instead the participants were supposed to abuse the guest additions to achieve the same effect.

Exploitation via /dev/vboxuser

There, I called it an exploit. When clearly this is all intended behaviour. Anyways, I already described everything that is needed to “use” this behaviour via the /dev/vboxuser device, what remains is just a programming challenge. We have to find out how to make HGCM calls through the device and what specific order of HGCM calls we need to achieve what we want. Because I’m lazy, instead of reimplementing the HGCM protocol, I patched an existing client, called VBoxControl, and added the additional “features”.

The patch should apply cleanly to the VirtualBox 5.2.4 codebase. I’m going to skip build instructions here since they are described in detail by the VirtualBox documentation. Building VirtualBox is generally fairly unpleasant. Arch Linux users have a certain advantage because they can just modify the official PKGBUILD:

$ svn checkout --depth=empty svn://svn.archlinux.org/community
$ cd community
$ svn update virtualbox
$ cd virtualbox/trunk

The modified VBoxControl has two additional commands called symlink and getfile, which can be used as follows:

$ ./VBoxControl symlink vagrant foo /flag
$ ./VBoxControl getfile vagrant foo ./test
$ cat ./test

This will dump the contents of the file /flag on the host. The putfile command can be used to write to a previously created symlink:

$ ./VBoxControl symlink vagrant foo /home/niklas/.bashrc
$ echo 'echo owned' > ./test
$ ./VBoxControl putfile vagrant foo ./test 0644

Vagrant’s response

I reported this issue to Hashicorp. Their solution is as follows: If you run vagrant up for the very first time on your computer, it will show the following warning:

Vagrant is currently configured to create VirtualBox synced folders with
the `SharedFoldersEnableSymlinksCreate` option enabled. If the Vagrant
guest is not trusted, you may want to disable this option. For more
information on this option, please refer to the VirtualBox manual:

  https://www.virtualbox.org/manual/ch04.html#sharedfolders

This option can be disabled globally with an environment variable:

  VAGRANT_DISABLE_VBOXSYMLINKCREATE=1

or on a per folder basis within the Vagrantfile:

  config.vm.synced_folder '/host/path', '/guest/path', SharedFoldersEnableSymlinksCreate: false

After showing this warning, the file ~/.vagrant.d/data/vbox_symlink_create_warning is created, preventing it from ever being shown again. I’m not sure for how many potentially vulnerable installations this will actually catch the attention of the right person, but at least they give you the chance of doing the right thing, which is to set the environment variable globally if you don’t 100% trust code running inside your boxes.