Setting up a WireGuard VPN Server Architecture for Internal Network Access
By: Julian Runnels
Tags: Wireguard, Cloud, VPN, Pentesting, Infrastructure

WireGuard installation

For Kali, WireGuard is already installed in most cases, but if not a simple `apt install wireguard` will install it. You may need to restart your system. For other systems please refer to the specific installation instructions here.

WireGuard Configuration

Unlike other VPNs like OpenVPN, there is no specific server application that you start, WireGuard directly connects peer-to-peer. To create the server-client relationship, you simply must configure a specific client to route communications to the other clients while accepting inbound communication. Additionally, WireGuard works through the creation and usage of a set of private/public keys for each client which you create yourself. Once you have created a set of keys, the public key is placed in the configuration of the “server” which allows it to encrypt the traffic outbound. Meanwhile the “client” configuration contains the private key, which is used to decrypt the inbound traffic.

Basically, if you want data to be sent outbound to an endpoint, you simply need to create a peer with that endpoint’s public key (this will make more sense once you see some configurations). The WireGuard configuration file is placed at /etc/wireguard as a .conf file. Whatever you name that file will be reflected as the interface name, for my setup I used wg0 as the server interface and wg0-client for the client’s configs.

Key Creation

Creating a WireGuard key pair is pretty straightforward, on Linux the private key is created with wg genkey and the public key with wg pubkey with the private key piped in. To make it a bit easier and more repeatable I have included a small shell script that you run with ./create-key.sh <name of key> and that will create and save the private and public key pair with the correct name.

create_key.sh

Server Configuration

Here is the overall server configuration, which we will break down piece by piece below. In the interest of not releasing my own configuration, the private and public key values will be removed, you would just fill it in with your own. Remember that the PublicKey is the value of the peer, not the host.

C2 Server WireGuard Configuration

All WireGuard configurations start with [Interface], which is where you insert that client’s private key and IP in the network. In this case 10.10.20.1, like shown in the initial diagram, and the key is one created using the shell script shown previously. ListenPort is simply what UDP port you would like open for inbound connections, with 51820 being the default port.

PostUp is used to run commands when the interface is stood up, with PostDown doing the same when the interface is stood down. In this case, these values were copied from here and serve to route all traffic that comes in through the VPN interface back out through the eth0 interface. In other words, these lines tell the WireGuard client to act as NAT gateway for all incoming VPN traffic that is directed to the public internet. Make sure to adjust the eth0 value if you have a different interface for public internet.

The [Peer] values each represent a new VPN connection point. For these values, the PublicKey is the public key that matches whatever private key is in the [Interface] portion for the client. The AllowedIPs acts as a routing table and security group for each client. Basically, any value that is placed there the C2 server will route inbound traffic to those IPs. Notice for the 10.10.20.10 peer, how the internal IP range that is shown on the original Network Diagram is added to the AllowedIPs. This lets the C2 know that any incoming traffic for those ranges must be sent to this peer. It is this setup that allows our tester client’s to directly connect to the internal networks (there is also additional setup needed on the endpoint).

IMPORTANT! In order for this to work, you must configure IPv4 forwarding on the C2 host, the client endpoint and the tester endpoint. To do so: # Ensure forwarding is allowed by adding below to /etc/sysctl.conf net.ipv4.ip_forward=1 or run sudo sysctl net.ipv4.ip_forward=1

To spin up the C2 WireGuard, run the command wg-quick up wg0 as root, or with sudo. You should see the wg interface with ifconfig now. To have the interface run as startup, simply run sudo systemctl enable wg-quick@wg0

Replace the wg0 with whatever you named the configuration file (without the .conf)

Client Facing Endpoint Configuration

Now that the C2 is setup, we need to configure an endpoint to connect back and allow access into the customer’s internal network. I set up both VM OVA images and hardware Raspberry Pi’s with this configuration, to allow the easiest method for getting to a client’s network.

Client Endpoint WireGuard Configuration

The [Interface] and PostUp/PostDown is basically the same as the C2 setup, just with adjusted Address and PrivateKey. Unlike the C2 however, there is only one peer (which is actually the C2) and it has a few more options. Specifically, the Endpoint value is what actually connects the two clients. Since the RPi might be behind a NAT, or otherwise unreachable via direct internet we don’t add an endpoint value to the C2, instead we have the endpoint connect to the C2 public IP or DNS. Think of a reverse shell for all you hackers out there. To make sure this connect stays alive, we add the PersistentKeepalive option, which maintains the connection ever x seconds. Additionally, notice that instead of just having the specific /32 address, we allow connections from the whole WireGuard 10.10.20.0/24 network.

Remember to enable ipv4 forwarding and enable the wg service for bootup.

Pen Tester’s configuration

For the Pen Tester configuration, we need to setup some PostUp/PostDown ip route rules that create a static route out to the C2, so that when we send all traffic through the interface, it doesn’t try to send the C2 traffic and basically loop on itself. You will need to adjust the rules to match whatever your default gateway IP is.

Notice that like the client setup, we connect back the C2 and keep the connection alive. The only big difference is that we add 0.0.0.0/0 to the AllowedIPs, which basically informs the WireGuard interface to forward ALL traffic through the interface and let the C2 server handle sending to the internet. Additionally, we create a static route to the C2 via the normal internet interface so that the C2 traffic doesn’t try to get sent through the interface and loop on itself.

Once again, you have to make sure that IP FORWARDING IS ENABLED, for this to work correctly. If everything works as expected, you should see your public IP changed to the C2 public ip. Additionally, you should be able to ping and talk to the WireGuard internal network.

Example Time!

To clear up the practical use of this setup, let’s go through a little example. I am a pen tester, with a Kali VM on my local network. I configure this VM with a WireGuard client with an IP of 10.10.20.2, connecting to the Azure C2 Kali server that I have previously setup and allowed access through the security groups. I need to test a client’s internal network that has several machines behind a NAT. The client doesn’t own the outbound firewall, so they can’t set up a Site-to-Site VPN, but they can confirm that there is no outbound filtering in place on connections. I send over to them an OVA with a preconfigured VM that connect back to the C2 as 10.10.20.10.

(Pretend the steps below are setting up the network and VM before its shipped out.)

Sample Network Diagram

Set up VPN C2, client and tester key.

2. Configure C2 VPN configuration (/etc/wireguard/ex.conf).

[Interface]

Address = 10.10.20.1

PrivateKey = GFYJl5xBIhQqeedYGckNaFBIS+n9+5hWEwWeQM5Iyks=

ListenPort = 51820 [Peer]

AllowedIPs = 10.10.20.2

PublicKey = 5OeEHFIejwuEHoQUx/CeuT35IF5aWkvbjPUWPRCEfhE= [Peer]

AllowedIPs = 10.10.20.10

PublicKey = AgYvXOVn+6ED/ylxhIfMjlhgW4/CPvWfFCb3PzmS3mc

3. Spin up C2 VPN.

wg-quick up ex

4. Installation of Client configuration on remote endpoint (/etc/wireguard/ex-client.conf).

[Interface]

Address = 10.10.20.10

PrivateKey = ILaVhY6259CoqUY6/dMnjKZsIel578cF+ssqKC+wrXY= [Peer]

PublicKey = Q5+LvmPu8a76oKnKOnxGfbWzKZ7WhxxP4YNKy6LgJ0k=

Endpoint = <C2 Public IP>:51820

AllowedIPs = 10.10.20.0/24

PersistentKeepalive = 25

5. Spin up client VM, and set to start at bootup.

wg-quick up ex-client

sudo systemctl enable wg-quick@ex-client

6. Check connection.

7. Set up connection on Tester endpoint (/etc/wireguard/ex-client.conf).

[Interface]

Address = 10.10.20.2

PrivateKey = yJAl7s7xoRpYZI6B/bAdCX/Ccj1oleUD5gEjDOfv+nm= [Peer]

PublicKey = Q5+LvmPu8a76oKnKOnxGfbWzKZ7WhxxP4YNKy6LgJ0k=

Endpoint = <C2 Public IP>:51820

AllowedIPs = 10.10.20.0/24

PersistentKeepalive = 25

8. Test connection between C2 and tester.

9. Test connection from Tester->Client via C2.

10. Now comes the fun part, lets adjust the server to route to the internal network on the client endpoint, so that we can actually do something with this connection.

[Interface]

Address = 10.10.20.1

PrivateKey = GFYJl5xBIhQqeedYGckNaFBIS+n9+5hWEwWeQM5Iyks=

ListenPort = 51820 [Peer]

AllowedIPs = 10.10.20.2

PublicKey = 5OeEHFIejwuEHoQUx/CeuT35IF5aWkvbjPUWPRCEfhE= [Peer]

AllowedIPs = 10.10.20.10, 192.168.1.0/24

PublicKey = AgYvXOVn+6ED/ylxhIfMjlhgW4/CPvWfFCb3PzmS3mc=

11. And adjust the client configuration to add PostUp rules to forward traffic to the internal endpoint.

[Interface]

Address = 10.10.20.10

PrivateKey = ILaVhY6259CoqUY6/dMnjKZsIel578cF+ssqKC+wrXY= PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE [Peer]

PublicKey = Q5+LvmPu8a76oKnKOnxGfbWzKZ7WhxxP4YNKy6LgJ0k=

Endpoint = <C2 Public IP>:51820

AllowedIPs = 10.10.20.0/24

PersistentKeepalive = 25

12. And lastly, adjust the tester to route correctly.

[Interface]

Address = 10.10.20.2

PrivateKey = yJAl7s7xoRpYZI6B/bAdCX/Ccj1oleUD5gEjDOfv+nm= [Peer]

PublicKey = Q5+LvmPu8a76oKnKOnxGfbWzKZ7WhxxP4YNKy6LgJ0k=

Endpoint = <C2 Public IP>:51820

AllowedIPs = 10.10.20.0/24, 192.168.1.0/24

PersistentKeepalive = 25

13. Check connection.

From C2:

From tester:

You can see that the packet is routing though the C2 and through the client endpoint, and to my tester it is a seamless experience.

14. As my tester, I now have full access into the internal 192.168.1.0/24 network as if I was present on the network!