Subscribe

As people head off to DEF CON this week, many are probably relying on
OpenVPN
to safely tunnel their Internet traffic through "the world's most hostile network"
back to an ordinarily hostile network.
While I believe OpenVPN itself to be quite secure, the way in which
it interacts with the operating system to route your traffic is quite
unrobust and can be subverted in numerous ways on a hostile local area
network. This article will describe some of the problems and suggest
countermeasures. The article is Linux-centric, since I'm
most familiar with Linux, but many of these concerns apply to other operating
systems as well.

IPv6

Unless you explicitly configure your OpenVPN tunnel to support IPv6
(which is only possible if your server has IPv6 connectivity), then all
IPv6 traffic from your client will bypass the VPN and egress
over the local network. This should concern you as more and more
websites are available over IPv6 (including this blog), and clients
generally prefer to use IPv6 if it's available.

The easiest countermeasure is to just disable IPv6 while you're at DEF CON.
As a bonus, you'll reduce your network stack's attack surface and will be safe
in the unlikely event someone drops an IPv6-specific 0day at DEF CON.

DNS

If you're using a VPN, you want to make sure you're using a trusted
DNS server. If you use an attacker-controlled DNS server, they can
return rogue IP addresses and redirect all your traffic back to a
network they control after it passes through your VPN, rendering your
VPN moot. Unfortunately, Unix-based operating systems (including OS
X, though it may have improved since I last looked at this a few years ago)
handle DNS server configuration incredibly poorly. Even if your VPN
server specifies the address of a trusted DNS server, the DHCP server
on the local network might return the address of a rogue DNS server.
Which DNS server you end up using depends on too many factors to discuss here,
but needless to say it does not inspire confidence.

I recommend doing whatever it takes to disable the retrieval of DNS
information over DHCP, and hard-coding the IP address of a trusted DNS
server in /etc/resolv.conf.
On Debian, if isc-dhcp-client is your
DHCP client, the most airtight way to stop DHCP messing with your DNS
settings is to place the following in /etc/dhcp/dhclient-enter-hooks.d/zzz-preserve-resolvconf:

make_resolv_conf() {
true
}

I don't know enough about other operating systems/DHCP clients to provide
specific instructions.

Denial of service

An attacker can always block your VPN, preventing you from using it. If they
did this continuously, you'd probably notice that your VPN failed to start, and
then proceed cautiously (or not at all) knowing you didn't have the protection
of a VPN. A more clever attacker would let you establish the VPN connection, and
only start blocking it later. If the OpenVPN client times out and quits, you'll
start sending traffic over the untrusted network, and you might not notice.

I will present a countermeasure for this along with the countermeasure for the next attack.

Attacks on redirect-gateway

The usual way of telling OpenVPN to route all Internet traffic over the VPN is
to use the redirect-gateway def1 option. When this option is used,
the OpenVPN client adds three routes to your system's main routing table:

A specific route for the VPN server, via the local network's default gateway.

A route for 0.0.0.0/1 via the VPN.

A route for 128.0.0.0/1 via the VPN.

The first route prevents the encrypted VPN traffic from being routed via the VPN itself,
which would cause a feedback loop.
The last two routes are a clever hack: together, 0.0.0.0/1 and 128.0.0.0/1
cover the entire IPv4 address space, and since they are more specific than the default
route for 0.0.0.0/0 that came from the local DHCP server, they take precedence.

However, a DHCP server can also push its own routes (called
"classless static routes") to the DHCP client.
So a rogue DHCP server can push routes even more specific
than the OpenVPN routes, such as for 0.0.0.0/2, 64.0.0.0/2, 128.0.0.0/2, and 192.0.0.0/2. These
routes cover the entire IPv4 address space, and take precedence over the less-specific OpenVPN routes.

You could tell your DHCP client to ignore classless static routes, but there's another attack:
a rogue DHCP server could push a subnet mask for an extremely large subnet, such as /2. Then the interface route
for the local network would be more specific than your OpenVPN routes. The attacker can only
grab 25% of the IPv4 address space this way, but that's a sizable percentage of the Internet.

A better countermeasure is to take advantage of Linux's advanced routing
and use multiple routing tables. Although rarely used, a Linux system
can have multiple routing tables, and you can use routing policy rules to
specify which routing table a packet should use. The idea is to put all
your OpenVPN routes in a dedicated routing table, and then add routing
policy rules that say:

If a packet is destined for the VPN server, use the main routing table.

Otherwise, use the OpenVPN routing table.

This keeps your OpenVPN routes safely segregated from routes pushed by the DHCP server.
The only packets that will ever use the routing table controlled by the DHCP server will
be encrypted packets to the VPN server itself. Everything else will use a routing table
controlled only by OpenVPN.

The first step is to configure the routing rules. Unfortunately, distros
don't provide a good way of managing these, leaving you to run a series
of ip rule commands by hand. The changes made by these commands
are lost when the system reboots, so I suggest placing
them in a system startup script such as rc.local.

Replace 203.0.113.41 with the IP address of your VPN server. The preferences (1000-1003) ensure
the rules are sorted correctly. 94 is the number of the OpenVPN routing table, which we'll
reference below. The second rule prevents VPN server packets from being routed over the VPN
itself in case the main routing table is empty, and the final rule prevents packets from using
the main routing table in case the OpenVPN routing table is empty (which would happen if OpenVPN
quit unexpectedly).

The next step is to configure the OpenVPN client to add its routes to table 94 instead of the main
routing table. OpenVPN itself lacks support for this, but I wrote a routing hook that provides
support. Download the hook
and install it to /usr/local/lib/openvpn/route. Make it executable
with chmod +x. Then, add the following options to your OpenVPN client config:

Remove the existing redirect-gateway option (also check the server config in case it's
being pushed to the client).

The first option sets the routing table number. This has to match the number used in the
ip rule command above. The second and third options tell OpenVPN to use my routing hook
instead of its builtin routing code. The final option tells OpenVPN to route all traffic
over the VPN.

Even worse attacks

If you want to be really careful, you should redirect your network device to an isolated VM and run
all of your networking config (e.g. DHCP client, wireless supplicant) inside it. The Linux
userspace networking stack is pretty hairy, and it all runs as root. A vulnerability would allow
an attacker to take over your system before you even start your VPN.

Using a dedicated network VM is pretty complicated and beyond the scope
of this blog post. Fortunately, if you're using an up-to-date operating
system you're probably safe, since it seems unlikely anyone would burn
a 0day at DEF CON just to take over random conference-goers' laptops.
I'd be much more worried about the other attacks, which are straightforward
enough to be in script kiddie territory.