Skip to Main Content
March 11, 2025

Abusing Windows Built-in VPN Providers

Written by Christopher Paschen
Research

Some interesting things happen when you connect to a virtual private network (VPN). One that recently caught my interest is updates to the routing table. Normally, a non-administrative Windows user can’t modify the system routing table; it is a privileged action due to the routing table being shared by all processes at all privilege levels on the entire system. This included connections by services, other users if multiple users are signed in, or network connections initiated by the kernel.

However, with basic, user-level access to a Windows workstation, it is possible to modify the system routing table to blind logging and defensive products or transparently man-in-the-middle (MitM) all HTTPS connections.

Throughout this post, we’ll walk through my premise that a standard user can modify the system routing table if they can reach a VPN server they control, and the implications this has for network and host security. First, we will fully cover what VPN protocols and options are built into the Windows provider along with most of the various ways to add new connections. Then, we’ll discuss setting up a VPN server. Finally, we’ll cover detection and prevention of the abuses mentioned.

Windows Built-in VPN Providers

You are likely already familiar with the concept of a VPN. In simple terms, it's an umbrella of technology that allows computers or entire networks in different locations to tunnel traffic over the Internet and then communicate as if they were on a private network. Many companies provide VPN software. I’m most familiar with VPNs requiring a third-party application (OpenVPN, WireGuard) or specialized hardware (network vendor firewalls usually support VPNs, e.g., Palo Alto) to be installed that facilitates the technological requirements to connect to a given VPN server. With that said, Windows itself has built-in providers that can connect to a VPN using a variety of protocols. While third-party VPNs typically require an administrator to install them, a standard user will already have access to providers built in to Windows.

Windows has providers for a variety of VPN protocols, as shown in the image below.

Figure 1 - Menu when adding a VPN

If you want to connect to a VPN using any of these protocols, you do not need to install third-party software. The ability to add a connection is allowed with standard, user-level permissions. There are a variety of ways to add a connection:

  • Settings UI
  • Network and Sharing Center
  • Rasphone.exe
  • Modifying the Phonebook file on disk
  • Powershell.exe VPNConnection cmdlets
  • WMI class at root/Microsoft/Windows/RemoteAccess/Client/PS_VPNConnection
  • Calling the win32 APIs related to RAS (Remote Access Service)

We’ll walk through most of these methods below.

Settings UI

On Windows 10, the easiest way to add a VPN is using the Settings UI.

From the base settings menu, select Network & Internet.

Select VPN on the left-hand sidebar.

Finally, select Add a VPN connection.

On Windows 11, the settings are in a similar place, with slightly different imagery.

Figure 2 - Windows 11 modern VPN menu

Once Add VPN is selected, you are presented with a dialog that collects the following information:

  • Connection name: How the connection will be referenced in the UI/commands
  • Server name or address: DNS/IP address of the VPN server you will be connecting to
  • VPN type: [PPTP, L2TP, SSTP, IKev2, Automatic (tries them all)]
  • Type of Sign-in info: [User/Pwd, Smart Card, One-time Pwd, Cert]
  • Extra info based on sign-in info
  • Checkbox for Remember my Sign-in info: When checked, the username and password are saved as an LSA secret on a successful connection.

Once you’ve entered all the required fields, you can save the configuration. It is possible to connect with the Win32 API, rasdial.exe, or the UI.

Modifying the Phonebook

The built-in VPN protocol stack lives on top of the same technology that powered dial-up connections all the way back to Windows NT 4. This is relevant because all the other methods that add in a VPN connection end up modifying the phonebook. There is a phonebook file per-user ( %appdata%\Microsoft\Network\Connections\Pbk\rasphone.pbk) and system wide (C:\Users\All Users\Microsoft\Network\Connections\Pbk\rasphone.pbk and C:\ProgramData\Microsoft\Network\Connections\Pbk\rasphone.pbk).

There are over 100 entries in this file that make up a given VPN connection. I’m not going to go through each of them here, as it wasn’t needed to answer my initial premise on user-level route modification. With that said, if a correctly formatted file is dropped into one (1) of these locations, the VPN will be usable from all the various methods of connecting without requiring a system reboot. A partial example of how entries in this file look is below.

Figure 3 - Partial entry in rasphone.pbk

This defines a connection named “winpassionvpn” and all of the various parameters needed to connect. If you want to abuse direct placement of entries into the phonebook file, one (1) idea is to use another method given in this section on a separate controlled computer and then copying the entries into this file on the target computer.

Finally, there is another rasphone file: at %appdata%\Microsoft\Network\Connections\Pbk\_hiddenPbk\rasphone.pbk. Connections configured in this file can be connected to via the API if called out directly, but they will not appear in the settings UI. In my own testing, anything placed into these files is deleted when any settings UI that lists VPNs is opened.

PowerShell

PowerShell can be used to add, modify, and delete VPN connections. The following cmdlets are available:

  • Add-VPNConnectionTriggerDnsConfiguration
  • Get-VPNConnection
  • Set-VPNConnection
  • New-VPNServerAddress
  • Add-VPNConnectionRoute
  • Add-VPNConnection
  • Set-VPNConnectionTriggerDnsConfiguration
  • Remove-VPNConnectionTriggerTrustedNetwork
  • Set-VPNConnectionTriggerTrustedNetwork
  • Remove-VPNConnectionTriggerDnsConfiguration
  • Add-VPNConnectionTriggerTrustedNetwork
  • Add-VPNConnectionTriggerApplication
  • Remove-VPNConnectionRoute
  • Set-VPNConnectionIPsecConfiguration
  • Remove-VPNConnectionTriggerApplication
  • Remove-VPNConnection
  • Get-VPNConnectionTrigger
  • Set-VPNConnectionProxy

Adding a new VPN connection is fairly simple using the Add-VPNConnection route:

Add-VPNConnection -Name exampleVPN -ServerAddress example.org -TunnelType Sstp -EncryptionLevel Optional -AuthenticationMethod MSChapv2 -SplitTunneling –RememberCredential

As a user, you can add a route that is sent through this VPN using the following PowerShell command:

Add-VPNConnectionRoute -ConnectionName "exampleVPN" -DestinationPrefix "1.2.3.4/32"

Full documentation about all of these commands is available via learn.microsoft.com. Given their power, I wanted to figure out how these commands were implemented. Was it manually parsing and modifying the phonebook file, using the Win32 API, or something else?

After attempting to open the .psd1 file that defined these commands, I observed that they were mapped to CDXML files. In short, this means the PowerShell cmdlet is a wrapper around a CIM class. This means the class referenced could be accessed via WMI or WinRM, which opens the possibility of remotely modifying VPN connections. Using the PowerShell cmdlets, the argument -CimSession allows the commands above to be executed against a remote system.

WMI

As mentioned in PowerShell, there exists a WMI provider that allows us to query and configure the VPN profiles on a Windows-based computer system. The provider dynamic-link library (DLL) is available at C:\Windows\system32\wbem\VPNclientpsprovider.dll. We can see the associated methods and objects in the .mof file available at C:\Windows\System32\wbem\VPNclientpsprovider.mof. If you open this file, you’ll see that the methods presented closely align with what we just went over in the PowerShell section. We can call in to this provider via WMI, and actions will be taken that affect the VPNs registered on this system. An example of adding a VPN to a computer remotely via WMI is shown below:

wmic /NODE:"target.example.org" /NAMESPACE:"\\root\Microsoft\Windows\RemoteAccess\Client" PATH PS_VPNConnection CALL Add Name="MyRemoteVPNall" ServerAddress="VPN.example.com" TunnelType="Ikev2" AllUserConnection=1

By having the argument AllUserConnection=1, this profile will be created and placed into the system-wide phonebook in ProgramData and All Users. If AllUserConnection isn’t present, then the phonebook for the user who authenticated will be used.

The WMI provider calls undocumented APIs within rasapi32.dll. That explains why some of the features, such as adding a triggering application to launch the VPN, are accessible via this WMI provider but not readily apparent in how to access via the documented Win32 API.

It’s also worth mentioning that many of the actions taken by the undocumented APIs use local RPC to communicate with the RasMan service. This service then executes as the system and can make system-level modifications when required, such as modify registry keys under HKLM. For example, the following registry keys are created under HKLM when adding a triggering application using a command such as:

Add-VPNConnectionTriggerApplication -Name "passionVPN" -ApplicationID "C:\\this\\does\\nopt\\exist.exe"
Figure 4 - RasMan autotrigger config

Normally, a standard user could never write to these paths, but since the request is actually made to a local RPC server, it is the services privileges that matter, not the users.

Win32 API

The various Win32 APIs that control the remote access service are well documented online at https://learn.microsoft.com/en-us/windows/win32/rras/remote-access-service-functions These functions control the addition of a new VPN connection, manage its lifecycle (connect/disconnect) and modify an existing VPN profile. 

Some of the core functionality of built-in VPN providers, such triggering a VPN to connect when a given application starts and adding routes to a VPN from the connecting computer, are not exposed via the documented API.

Given that we know the WMI provider has methods that target adding triggering applications and user-controlled routes, we can disassemble the WMI provider and determine what it is doing to enable these new features. The methods we are interested in call RasSetEntryAdvancedProperties. This method goes into a giant dispatch table and, depending on what needs to be done, uses RPC to call the local RasMan (Remote Access Connection Manager) service. The RasMan service will then perform the required actions. This is why we, as a user, are able to run commands that potentially end up writing into privilege locations as noted above.

At a basic level, if we want to add a new VPN using the Win32 API and then connect to it, the APIs we need to call are RasSetEntryPropertiesW to create the new entry in the user’s phonebook file, and then RasDialW to make the connection.

VPN Server

Now that we know a user can create a new VPN profile and connect to it, we should consider what type of server we want to host. There are many different VPN protocols, but we can’t use any that are not built into Windows natively.

From the angle of a trusted insider or malicious attacker, the primary concern comes down to what protocol we are able to get out of a target network. If the target already uses a specific VPN protocol within their own environment, I would match it. If that information isn’t known, then Secure Socket Tunneling Protocol (SSTP) seems like an obvious choice to me.

SSTP was developed by Microsoft and is well documented here. It is a VPN protocol expressly designed to egress most common firewall configurations by tunneling the VPN connection over an HTTPS connection. In my testing, this performed well enough to simply blend traffic with existing HTTPS connections. It is worth noting that this protocol does not use the system-configured proxy settings. If the only way out of the network is through an HTTP proxy, I was unable to successfully use this to exit the network.

I initially had difficulty finding a server that would both run on Linux and support SSTP. After a few hours of searching, one (1) name continued to pop up, SoftEther. Without giving a full review, I’ll say that I’m surprised after having used this software that it wasn’t more well known to me or others I asked. It’s well polished, has both CLI and GUI management options, and even contains a fully integrated userland DHCP/NAT stack, which makes common deployment scenarios as simple as forwarding the ports and adding some user accounts.

For our purposes, it supports every VPN technology that Windows has built in and can listen to them all simultaneously. It easily allows bridging to a TAP interface, allowing us to use more traditional iptables rules to manage forwarding, redirecting and blocking any traffic coming through our VPN tunnel.

While I decided to use SoftEther, the concepts mentioned throughout this post require client-side commands. The server software used does not really matter beyond supporting one of the built-in protocols and being reachable from the client side.

With that said, I’ll walk through the setup of the SoftEther server I used in my testing for anyone following along.

Server Install

I scripted the full compile, install, and setup here. While I discuss the install and setup process below, reading the linked script will get down to the exact commands to create a functional bridged VPN setup.

For my use case, I installed and configured the development branch of SoftEther. This is a unique project in that it has a development version and stable version in two (2) separate repositories. The development version accepts public pulls, while the stable version appears to be managed by a single individual who backports features of interest.

On non-BSD/Windows systems, we will need to compile this project from source ourselves to install it. The repository lays out the requirements and instructions quite well here, so I won’t be repeating it. I will point out that following the default install places logging and other directories right next to the server executable, which we don’t really want. To avoid this, make sure you read further down the build instructions here, which talks about how to specify the auxiliary directories.

Server Setup

After you’ve compiled and installed the server, we need to do some setup to make it usable. At its core, this includes:

  1. Adding users
  2. Adding a new TAP interface to bridge traffic
  3. Installing a DHCP server for addressing on TAP interface
  4. Configuring traffic forwarding/redirection
  5. Restarting server on system reboot
  6. Reapplying firewall rules on system reboot

Before we can interact with the VPN server, we must first start it. Unlike most Linux server software, a systemctl/init script is not configured by default. To start the server, we must execute the command vpnserver start as root. After the server is started, we can utilize the command vpncmd to interact with the VPN server locally and configure it. vpncmd can also be scripted as shown in the setup script linked above.

vpncmd supports a variety of commands. To list them all, you can enter ?. To search for all commands starting with some prefix, you can use prefix?, which will print all works starting with prefix. For example, Hub? will list all commands starting with Hub. UserCreate and UserPasswordSet are the two (2) commands required to create and add a new user.

SecureNat is a feature of SoftEther that grants a server administrator the ability to host VPN clients without manually configuring bridging to a physical interface or setting up a DHCP server. Unfortunately, this removes our ability to redirect traffic, so we need to create a more traditional bridge/iptables setup. In order to do this, we must use the BridgeCreate command with /TAP:yes. This will add a TAP interface bound to our hub of interest every time the VPN server starts. In order to properly serve as a gateway to forward traffic, we also need to assign our TAP interface an IP address. This can be done via the ip address command.

For a typical Microsoft VPN client to successfully connect, we need to have a working DHCP server. For this purpose, I chose isc-dhcp-server, as it's fairly full featured and relatively easy to configure. /etc/default/isc-dhcp-server needs to be configured to listen on our new TAP interface. Next, /etc/dhcp/dhcpd.conf needs to be modified to advertise addresses and routes on this interface.

With DHCP serving addresses, a client should be able to connect. If we want to forward and potentially redirect traffic, then we need to get iptables involved. The core rules you want to consider are:

  • Forward rules allowing the interface to handle traffic going through our TAP interface
  • Forward rules to drop traffic going to IP addresses you want to block
  • A MASQUERADE rule on the NAT postrouting table to allow NAT-style traffic forwarding to the Internet
  • Redirect rules on the NAT prerouting table if you want to intercept and MitM traffic with something like mitmproxy

Finally, a systemctl config script should be deployed. I decided it was easiest to write a bash script to chain the required startup order:

  1. vpnserver start is executed, creating our TAP interface
  2. The TAP interface is assigned an IP address
  3. The DHCP server is only started after the tap interface is up and has an IP address

Iptables also need to be restored at some point in this process. You can observe the script I created in the setup script linked above. isc-dhcp-server was marked to no longer auto-start since this script would take care of starting it moving forward. After it is placed, it can be enabled using systemctl enable .

Modifying Routing Table

With the VPN server set up, the target computer routing table can now be modified. We have a few options for deciding what traffic travels into the VPN in the first place.

  1. We can configure the VPN server to advertise a specific subset of routes that are pushed when a user connects.
  2. We can configure Windows with splittunnel=$false on our VPN connection, causing the VPN to be used to tunnel all system traffic, including other users or the kernel
  3. We can locally add routes using the PowerShell cmdlet/WMI class to access Add-VPNConnectionRoute.

The client side can specify whether they want to tunnel all traffic through the VPN connection. This option is controlled by a property called SplitTunnel. When SplitTunnel is false, the Windows client will add a default route for the VPN’s gateway when it connects regardless of any routes pushed by the DHCP server.

There are advantages and disadvantages to each. Let's dive into each of these methods a bit more.

Pushing Routes with VPN Server

Any VPN server should be able to push new routes to clients given it is DHCP that controls the new route editions. This setup is going to be specific to the setup of the VPN server, so I will only be going over how it works in my SoftEther environment. We are able to push routes through the VPN server by updating the configuration of the DHCP server. First, we will define on the top level a new option name, the code it uses, and its format. I used the name classless-win w/ code 249. Its value will be an array of unsigned 8-bit integers representing the subnet mask and the octets of the respective IP addresses. This definition looks like: option classless-win code 249 = array of unsigned integer 8; 

With this option defined, we can go further down into our subnet definition and use this newly defined option.  Some examples of things you may want to push are:

  • Default route via 1.2.3.4:
    • option classless-win 0, 1,2,3,4;
  • 192.168.20.0/24 via 1.2.3.4
    • Option classless-win 24, 192,168,20, 1,2,3,4;
  • 4.3.2.1/32 via 1.2.3.4
    • Option classless-win 32, 4,3,2,1, 1,2,3,4;
  • 10.11.12.128/26 via 192.168.30.1
    • Option classless-win  26, 10,11,12,128, 192,168,30,1

If a misconfiguration is present in the setup file, the DHCP server will fail to start. The system logs can be checked using journalctl –u isc-dhcp-server. The line on which the error occurs will be marked in the presented logs. The VPN server will start and be available as it does not care if DHCP is working or not. A Windows client that attempts to connect will error out with error code 720 (ERROR_PPP_NO_PROTOCOLS_CONFIGURED).

The usage of , in the IP addresses is intentional, as we are simply separating our 8-bit integers for the array that is formatted and sent with this option. When this method is used, our single VPN server will push the same routes to all clients. If the client side has enabled split tunneling, the connection experience will be preserved, as we only begin redirection of traffic we are interested in handling.

Tunneling all Traffic

If tunneling of all traffic is desired, the split tunnel attribute should be set to false. If more specific route control is desired (such as using the method above and below), then it should be set to true. Modifying this attribute can be done via the PowerShell cmdlet as shown below:

Set-VpnConnection -Name "examplevpn" -SplitTunneling $false.

Another option for configuring this is to use the properties of the connection under Network and Sharing Center.

Figure 5 - UI setting for SplitTunnel

Finally, in the rasphone.pbk file, this option is specified as IpPrioritizeRemote=(0|1) where 0 means we are split tunneling (not adding a default route) and 1 means we are not split tunneling (we do add a default route). The client side can specify whether they want to tunnel all traffic through the VPN connection. From an abuse point of view, tunneling all traffic obscures what we are trying to modify. It does, however, pose the chance of degrading the client’s experience and potentially getting IT involved. Also, any local network subnet for which the machine does not have an explicit route will now fail to reach its intended destination. 

Adding Routes Locally

Something I found quite interesting was the ability to add routes on the user side for the VPN. These routes are added to the phonebook file. Upon connecting to the VPN, the routes will be applied to the system routing table. Adding these routes via the PowerShell is quite simple. The below command would force 1.2.3.4 specifically to be routed through our VPN tunnel.

Add-VpnConnectionRoute -ConnectionName "examplevpn" -DestinationPrefix "1.2.3.4/32"

For this to be meaningful, split tunneling should be set to true on the VPN profile.

Redirecting Traffic Flows

Now that we have modified the flow of traffic, we need to decide what we want to do. Two (2) primary use cases are discarding traffic we want to block and performing MitM attacks on traffic we want to snoop on.

To blackhole traffic, we can utilize iptables to block the traffic. Adding a rule to drop traffic will look similar to:

iptables –I FORWARD –d <ip to block> -j DROP

Now, the above traffic will go into our VPN tunnel because we added a route to direct it to us, then we drop the traffic, causing whatever application is making that connection to fail. This could be an EDR, log forwarder, or other application of interest.

If instead we wanted to do something with this traffic, say modify and examine it with mitmproxy, we instead would add a redirect rule. For example:

iptables -t nat -A PREROUTING -p tcp -s 192.168.30.0/24 --dport 443 -j REDIRECT --to-port 6789

There is an important caveat to consider when redirecting and modifying traffic for either of these scenarios. Any TCP connections that were established prior to the computer connecting to the VPN will not switch to traversing the new routes. Those already established connections will live outside our VPN tunnel, even if an added route would cause a new connection to go through our tunnel.

This effectively means that any system services that establish a persistent connection with their server won’t be affected by our modifications unless we can somehow break the existing connection. If your access has a session context of “console” (your token includes the well-known SID S-1-2-1), then breaking the connection is as simple as releasing and renewing the DHCP address of the primary adapter that is connected to the Internet. We can do this while the VPN is considered connected, and the system will not remove the routes as it reconnects to the existing active session. We will effectively have broken all existing connections, and they will all need to be re-established. When this occurs, they will be subject to our routes.

If you are executing code from a session that is missing the SID S-1-2-1, then as far as I can tell, you’re out of luck. The ability to release a DHCP address in this situation requires administrative privileges when using ipconfig to release the address. With that said, the checks for this group membership appear to be included in the ipconfig.exe binary itself. I did not investigate if this was a simple preemptive check to prevent a later failure, or if we could bypass this check by coding against the relevant APIs ourselves.

Auto Connect VPN

The built-in VPNs on Windows support a feature named application triggers. In short, when the identified application starts, the associated VPN profile should be connected. There does not appear to be any meaningful check regarding what application initiates the trigger. Adding a trigger can be done with a command like below:

Add-VPNConnectionTriggerApplication -ConnectionName "VPN.passionlabs.org" -ApplicationID "C:\Windows\explorer.exe"

This command has two (2) primary effects. First, some extra entries are added to the phonebook file. Second, HKLM\CurrentControlSet\Services\Rasman\Config\AutoTrigger is populated with information referencing the profile being used in the auto-trigger. After a system reboots, the VPN will connect for any user who can read the referenced phonebook file if they start one (1) of the applications referenced with ApplicationID.

Prevention and Detection

There is not a simple GPO option to outright disable usage of the Windows built-in VPN providers. With that said, we are not without options to limit or disable their usage.

Let’s start with what doesn’t properly limit access to the built-in providers. Host controls under Group Policy do not appear to exist to prevent the usage of the windows built-in VPN providers.  Some policies appear related under User Configuration > Administrative Template > Network > Network Connection, but after attempting to test many of these options, they did not appear to affect the modern VPN usage experience.

It is possible to remove access to the VPN settings page with the following policy: Computer Configuration > Administrative Templates > Control Panel.

Figure 6 - GPO to hide modern VPN menu

This only disables access to the modern Settings UI page related to VPNs. It does not prevent connecting to already configured VPNs or affect any other method of adding in new VPN connections (PowerShell, WMI, network and sharing, etc.).

Now that we know what doesn’t work, let’s talk about what does work. We need to target the components that the remote access service relies on. This includes the various phonebook directories, the remote access service, and its registry keys.

In order to disable setting an AutoTrigger application and profile, RasMan should be prevented from writing to the Config key under its service in the registry. That can be done with settings similar to what is shown in the image below.

Figure 7 - GPO to protect trigger config

To prevent standard users from using the phonebook file, we must prevent them from writing it. This can be done in many ways, including a scheduled task or logon script. Once the user no longer has write permissions, attempting to add a new VPN connection via the Windows shell results in the following error.

Figure 8 - System error when phonebook is not writable

Finally, it would be possible to administratively disable the RasMan service. This prevents usage of the components that connect to a VPN via the built-in providers. 

From a detection point of view, the easiest detection involves the Windows event log. Every time a user attempts to connect to a VPN using the built-in providers, it is logged. The event logs will be under the “Application” log, and they have a source of “RasClient”. These events correspond to starting a connection, connection success, and disconnection, among a few other informational events. Searching for these events in an environment would quickly identify if Windows built-in VPN providers were being used. I specifically would look for event IDs 20221, which will inform on any profile that begins a connection attempt (evidence that the built in VPN provider is in use at all), and 20222/20223, which record the destination of a connection attempt and that connection successfully being established, respectively.

Closing

What I’ve presented here isn’t novel or some zero-day attack. It is well understood that when a user connects to a VPN, their routing table will be modified. The reason for performing and presenting this research relates to the VPN providers being built into Windows and being specifically accessible from any standard user. There are well-defined programmatic APIs for adding new connections that do not depend on any third-party software being installed prior to abuse. I believe it is pertinent that system administrators and security providers examine whether this vector has been abused to bypass controls on a network, and for testing teams to leverage if usage of this built-in utility is monitored.