Protect your VPN from TunnelVision attacks with NetworkManager

Photo by Redd Francisco on Unsplash (modified)

Many users trust in a variety of VPN solutions to securely protect their privacy or access to personal or company’s resources when connecting from an untrusted network. This may be an airport or cafe’s WiFi network. This article describes how to use NetworkManager to improve that security.

Some months ago, researchers disclosed the TunnelVision vulnerability with ID CVE-2024-3661. The CVE describes a way to redirect traffic outside of the encrypted channel of a large number of VPN solutions. The attack is trivial for malicious agents with access to the network to which the user connects. They don’t even need to exploit any obscure bug or hidden vulnerability. They only need to reconfigure one standard feature of the DHCP protocol.

The attack is based on DHCP installing some routes to your system. Let’s begin by investigating what DHCP and routing are and how the attack makes use of them. If you only wish to know the solution, jump to the “Policy routing to the rescue” section, below. Jump to the “Configure NetworkManager” section to see how to effectively apply the mitigation in your system when configuring your VPN with NetworkManager.

DHCP

DHCP stands for Dynamic Host Configuration Protocol. It is a protocol to automatically assign IP addresses and other IP related configurations to hosts that connect to a network. When you connect to your home network and automatically get a private IP address, instead of having to configure it manually, it is very likely that it is thanks to a DHCP server running in your router, as DHCP is the most widely used method for this purpose.

The typical pieces of configuration that a host gets from DHCP are the host’s IP address, the network mask, and the gateway. These three settings are normally enough to figure out how to reach other hosts in the same private network and to the Internet.

SLAAC normally replaces DHCP for IPv6, but it works similarly.

Basic explanation about routing

Routing is how your host decides where to send each packet depending on the destination IP address. With the ip route command you can see some of the routes that your system is using. They look something like this (simplified):

192.168.1.0/24 dev eth0 src 192.168.1.10
172.16.0.0/16 dev eth1 src 172.16.1.2
10.0.0.0/8 via 192.168.1.25 dev eth0 src 192.168.1.10
default via 192.168.1.1 dev eth0 src 192.168.1.10

Note: the definition of subnets in the format 192.168.1.0/24 is following the CIDR notation. See here if you don’t know what does it mean.

First we see a direct route instructing to send packets directed to IP addresses within the 192.168.1.0/24 subnet through eth0. Then, a direct route instructing to send packets directed to IP addresses within the 172.16.0.0/16 subnet through eth1. Direct routes are used for packets directed to the same subnet as the host, since they are directly reachable. The kernel uses the routes to decide what the right device to send the packet is. It would be a mistake to send a packet directed to 192.168.1.20 by the eth1 interface, which is connected to a different physical network.

The third route contains via 192.168.1.25. This is a next hop, and is used to send packets to destinations not directly reachable by our system. Instead they are sent via an intermediate device with the IP address 192.168.1.25. This must be a router with capability to forward the packet to other networks where the destination is reachable.

The last one is known as a default route. The kernel uses it to send packets that didn’t match any other route. They are typically used to configure the gateway, the next hop address to which to route all the packets that we don’t explicitly know the route to. This is how our packets reach the Internet.

How the attack works

Most of the VPN clients are routing-based VPNs. This type of clients establish a tunnel to the VPN network by creating a virtual device. This is typically named tun0 or something similar. The VPN program encrypts all the traffic routed through that virtual device and sends it to the other end of the tunnel. The idea is that, although the traffic still goes through the potentially compromised network, it now does so as encrypted data.

To select what traffic to send through the tunnel interface, some routes are added to your system. At a minimum, a route similar to 10.0.0.0/8 dev tun0 is created. This routes the traffic directed to other systems inside the same VPN. For those who want to route all the traffic through the VPN, a default route similar to default via 10.11.12.13 dev tun0 is added, as well.

When you connect to a network using DHCP or SLAAC, the received IP configuration might contain some routes to install in your system. This has legitimate uses that we won’t dig into in this article, but it is also what attackers use to bypass the VPN’s configurations.

What happens when 2 routes overlaps in a way that a destination IP address matches with both of them? For example, the address 10.0.0.1 is within both subnets 10.0.0.0/8 and 10.0.0.0/24. Then, the route with the greater prefix wins. In this case, 10.0.0.0/24. This is because it is a more specific route, as it defines a smaller range of addresses, so they are a subset of the other which is more general.

Malicious agents with access to the network can take advantage of this. They can configure their DHCP server to send 2 routes with greater prefix than those configured by your VPN. This makes your system use the higher prefix and not send the traffic through the virtual tunnel device, so it goes unencrypted.

10.0.0.0/8 dev tun0   <-- route from VPN client
10.0.0.0/9 dev eth0   <-- route from DHCP
10.128.0.0/9 dev eth0 <-- route from DHCP

default route can also be shadowed. Default is equivalent to 0.0.0.0/0, so the routes for 0.0.0.0/1 and 128.0.0.0/1 would overrule it, as they have a greater prefix. This means that the attacker can redirect all the traffic.

Policy routing to the rescue

We can use policy routing to mitigate the TunnelVision attack and other similar attacks like TunnelCrack. [Edit] This mitigation doesn’t protect against TunnelCrack’s ServerIP attack as this attack relies on spoofing the DNS reply when resolving the hostname of the VPN. It is believed that it can be avoided by configuring the IP of the VPN server instead of its hostname (this is not confirmed).

Policy routing is a technique for making routing decisions based on custom policies. In Linux based distros like Fedora we can see the policies with the command ip rule.

To prevent the attack, we can use policy routing to move the VPN routes to a dedicated routing table with higher priority than the table that contains the routes configured by DHCP.

Do this by creating a policy rule that looks up a custom routing table, for example table 75, for all the outgoing packets. Use a lower priority value than the main table, which is 32766, to ensure that our routes match first. Note that lower values mean higher priority.

ip rule add to all table 75 pref 32000

Then recreate the same routes as the VPN in the new table.

ip route add 10.0.0.0/8 dev tun0 table 75
...(etc)...

Check that the rule is in place with the ip rule command. Inspect that the routes have really been added to the table with ip route list table 75.

That’s it. If any route from our custom table is applicable, it is used. Otherwise, the main table is checked as usual. Since routes from DHCP are added to the main table by default, they are unable to override the VPN’s routes.

Configure NetworkManager

At this point, most readers might have already noticed that manually configuring this every time is going to be very tedious and error prone. Also, non expert users might have lot of difficulties to determine which are the VPN routes among all the others. Moreover, routes for a device that doesn’t exist cannot be added, so we need to wait until the VPN device (i.e. tun0) is created. Once created, it is normally brought up immediately. This presents the risk of leaking data before the policy routing is configured.

If you are managing the VPN connection with NetworkManager, you can instruct it to configure the policy routing. The following command will perform the configuration. You only need to run it once and it will set this as a permanent configuration (even after a reboot).

nmcli connection modify MY_VPN_CONNECTION_NAME \
    ipv4.route-table 75 \
    ipv4.routing-rules "priority 32000 from all table 75" \
    ipv6.route-table 75 \
    ipv6.routing-rules "priority 32000 from all table 75"

Tip: find what the connection name is with the nmcli connection command.

Now deactivate and reactivate the connection. With these settings, NetworkManager will add the rules and put the routes in the specified table.

These are pretty advanced configurations, thus they are normally not available in your desktop’s GUI settings panel.

Note: Due to a bug in NetworkManager, these settings were partially ignored for VPN connections. This is fixed in development version 1.51.6. The newest versions in Fedora 40 and 41 also contain the fix. Update Fedora and ensure that you have at least NetworkManager version 1.46.6-1 or 1.50.2-1.

Possible side effects

People use VPN to securely access resources inside the VPN itself. Optionally, they use it to route all the traffic through the VPN, mainly for privacy reasons. This behavior can be enabled with the “use only for resources in this connection” option (called never-default in nmcli).

When the VPN is used to route all the traffic, the explained policy routing configuration routes all the traffic through the VPN. Even the traffic directed to the local network (i.e. to 192.168.1.20) that, as a consequence, will stop working. Captive portals like those used to login to hotels’ WiFi network won’t work, either. This is because a default route like default via VPN_ADDRESS is added to the new table with higher priority. If this happens, you might need to disable this configuration. If you are experienced enough, you may add some custom routes to the table to fix it.

Any other connection that also needs to add routes might stop working, and you will need to tweak its route-table and routing-rules configurations to create a rule with higher priority than the other.

Even with the potential drawbacks, this is a very robust configuration that should protect you from any data leak based on these kinds of routing attacks.

Fedora Project community

10 Comments

  1. CrunchyCrunch

    This is pretty interesting… How such a small thing has been overlooked for years 🙂

  2. Matthew Phillips

    Great article! Somehow I had never bothered to look at the routing table while on VPN. It looks like for Proton VPN using Wireguard there is already a separate table created using a lower priority than main or default. I had to do an

    ip route show all

    command to even see the route and then noticed it was in a separate table when I did an

    ip rule show

    command.

    I don’t really understand what is going on with the second rule… it looks like it is suppressing the normal default route, but I’m not sure why that is needed if the WireGuard table is already a lower/better priority than main and default.

    0:  from all lookup local
    31214:  from all lookup main suppress_prefixlength 0
    31215:  not from all fwmark 0xcba5 lookup 52133 &lt;&lt;&lt;&lt;&lt; WireGuard VPN table
    32766:  from all lookup main
    32767:  from all lookup default
    • Here it is explained: https://ro-che.info/articles/2021-02-27-linux-routing

      As you can see, those policies are designed with the contrary goal: to allow overriding the routes added by wg-quick with other routes. This is a reasonable idea because, generally, we consider that routes are added locally, under the control of the user. If the user adds a route, it must be needed for something. So, basically, the idea is to avoid the “possible side effects” that I describe in the article.

      But then, the problem comes when you connect to a compromised network, and happens what the article describes.

      • bmenant

        […] generally, we consider that routes are added locally [ndr: the

        main

        routing policy rule], under the control of the user [ndr:

        ip-rule(8)

        man page says

        administrator

        ]

        […] routes from DHCP are added to the main table by default […]

        Why the rules coming from DHCP (which are by design external, not under the control of the user or local administrator, and therefore sensibly untrustworthy) aren’t added by default (setup by the kernel) to a separate routing policy rule table, with a lower priority than the main (where the default route would be made absent)?

        • Íñigo Huguet

          It’s a reasonable idea, but probably would bring some unexpected behaviours and in many cases it will be useless. We normally only have these routes:
          default via 192.168.1.1 <— make this one absent, as you say
          192.168.1.0/24 dev eth0

          Then, if DHCP installs the route “1.1.0.0/16”, it doesn’t collide with any of your routes, so even being in a table with less priority, it would apply. Moreover, they don’t even need to install any route, because if you are connecting to an untrusted network, the gateway might be compromised and they might redirect the traffic when it reaches the gateway, anyway. This probes that the solution is useless in many cases and ineffective in others, so it’s not a good solution.

          The problem is not really in DHCP. DHCP is known to be insecure, and connecting to an untrusted network is widely known, even by non expert users, as insecure. The problem is that VPNs claim to protect you in this cases, and they don’t.

      • Matthew Phillips

        Thanks Íñigo!

  3. Ingmar

    I would suggest to add a brief command box before

    nmcli connection modify MY_VPN_CONNECTION_NAME

    I think of users who would now need to look for what MY_VPN_CONNECTION_NAME is.

    So, for such users, a command to show with nmcli the connections and a comment on how to identify likely VPN connections would be helpful.

  4. Alex

    Many thanks to the Fedora Magazine team for this eye-opening post, very informative!

    To follow up on this matter, I would think of 2 questions please:

    a lower number than 32766 means higher priority. In the example, the proposed number is 32000. Is it correct to assume that if a DHCP is compromised, the attacker can set a number below 32000 and get prioritized ? If yes, what would be the number that guarantees the highest priority to the legitimate user?
    in the webpage featuring the video with the explanations by the Leviathan group, you can see how this happens dynamically. An average user like me wouldn’t be able to track such changes live. Is there a command that could prevent and/or monitor such routing changes (and also creation of unexpected tunnels) ?

    • Íñigo Huguet

      No, DHCP can’t choose the priority of the rule. DHCP only sends routes, not rules, and this priority is set for the rules. The DHCP client in your system installs the routes from DHCP in the main table, which has priority 32766 (with NetworkManager you can choose to install it to a different table, not the main table, but just don’t do that).

Leave a Reply


The interval between posting a comment and its appearance will be irregular so please DO NOT resend the same post repeatedly. All comments are moderated but this site is not monitored continuously so comments will not appear as soon as posted.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The opinions expressed on this website are those of each author, not of the author's employer or of Red Hat. Fedora Magazine aspires to publish all content under a Creative Commons license but may not be able to do so in all cases. You are responsible for ensuring that you have the necessary permission to reuse any work on this site. The Fedora logo is a trademark of Red Hat, Inc. Terms and Conditions