WireGuard Security Testing: Peer Configuration, Private Key Exposure, Preshared Keys, and Endpoint Discovery

WireGuard has become the dominant modern VPN protocol due to its simplicity, performance, and minimal attack surface — but this simplicity concentrates the security risk in the configuration file. The central security concern with WireGuard is that wg0.conf stores the server's private key in plaintext on the filesystem, and a single file read from any process running as root gives an attacker the ability to decrypt all past and future VPN traffic from any peer. Key assessment areas: wg0.conf stores PrivateKey in plaintext and must be root-readable; wg show reveals the public key, endpoint IP:port, and last handshake timestamp for every active peer; preshared keys providing perfect forward secrecy are also stored in plaintext in the config; PostUp and PostDown directives execute shell commands as root when the interface comes up; and many WireGuard management UIs (WireGuard-UI, wg-easy) store peer configs and private keys in their own databases. This guide covers systematic WireGuard security assessment.

Table of Contents

  1. Configuration File and Private Key Access
  2. Peer Enumeration and Endpoint Discovery
  3. WireGuard Management UI Security
  4. WireGuard Security Hardening

Configuration File and Private Key Access

# WireGuard configuration file security assessment
# /etc/wireguard/ directory and wg0.conf permissions are critical

# Check directory and file permissions — should be 700/600 respectively
ls -la /etc/wireguard/ 2>/dev/null
# Correct: drwx------ (700) for directory, -rw------- (600) for .conf files
# Dangerous: any group or world read bit set

# Read wg0.conf — contains server private key in plaintext
cat /etc/wireguard/wg0.conf 2>/dev/null
# Example output:
# [Interface]
# Address = 10.0.0.1/24
# PrivateKey = SJLMbFB5oDIlgPOmFKlJi1k/XiNEm+aHpGbzKI5WnFo=  <-- plaintext private key
# ListenPort = 51820
# PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
#
# [Peer]
# PublicKey = ABC123...
# PresharedKey = xyz789...  <-- preshared key in plaintext
# AllowedIPs = 10.0.0.2/32
# Endpoint = 203.0.113.5:51820

# Extract private key for key compromise impact assessment
PRIVATE_KEY=$(awk '/PrivateKey/{print $3}' /etc/wireguard/wg0.conf 2>/dev/null)
if [ -n "$PRIVATE_KEY" ]; then
    echo "SERVER PRIVATE KEY OBTAINED: $PRIVATE_KEY"
    echo "Impact: Can decrypt all past recorded WireGuard traffic for this server"
    echo "Can impersonate the server to any peer"
fi

# Extract all peer preshared keys
echo "Preshared keys in config:"
awk '/PresharedKey/{print $3}' /etc/wireguard/wg0.conf 2>/dev/null

Peer Enumeration and Endpoint Discovery

# WireGuard peer enumeration via wg command — requires root
# wg show provides complete peer intelligence from kernel

# Show all WireGuard interfaces and peers
wg show 2>/dev/null
# Output reveals:
# - Server public key and listen port
# - All peer public keys
# - Peer endpoint IP addresses and ports (remote clients' public IPs)
# - Allowed IPs per peer (VPN IP allocation)
# - Last handshake timestamps (shows activity windows)
# - Transfer bytes (detects active vs inactive peers)

# Structured peer extraction
wg show wg0 2>/dev/null | python3 -c "
import sys
lines = sys.stdin.read()
peers = {}
current_peer = None
for line in lines.split('\n'):
    line = line.strip()
    if line.startswith('peer:'):
        current_peer = line.split('peer: ')[1]
        peers[current_peer] = {}
    elif current_peer and ':' in line:
        key, val = line.split(': ', 1)
        peers[current_peer][key.strip()] = val.strip()

print(f'Active peers: {len(peers)}')
for pubkey, data in peers.items():
    print(f'  Peer: {pubkey[:20]}...')
    print(f'    Endpoint: {data.get(\"endpoint\", \"not connected\")}')
    print(f'    Allowed IPs: {data.get(\"allowed ips\")}')
    print(f'    Last handshake: {data.get(\"latest handshake\")}')
    print(f'    Transfer: {data.get(\"transfer\")}')
" 2>/dev/null

WireGuard Management UI Security

# WireGuard-UI / wg-easy — common management UIs for WireGuard
# These UIs introduce additional attack surface

# wg-easy — Docker-based WireGuard management UI
# Default port 51821 with password-based web UI
# Check for unauthenticated access or default/weak password
curl -s http://wg-easy.example.com:51821/api/session 2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    print(f'Auth status: {d}')
except: print(sys.stdin.read()[:200])
" 2>/dev/null

# wg-easy stores WireGuard config and peer private keys in a Docker volume
# If the WG_PASSWORD is weak, brute force login gives access to all peer configs
# Check environment variable for WG_PASSWORD
docker inspect wg-easy 2>/dev/null | python3 -c "
import json,sys
containers = json.load(sys.stdin)
if not containers: import sys; sys.exit()
env = containers[0].get('Config',{}).get('Env',[])
for e in env:
    if 'PASSWORD' in e or 'SECRET' in e or 'KEY' in e:
        print(f'Credential: {e}')
" 2>/dev/null

# WireGuard-UI — another common management UI
# Default stores config in SQLite database at /app/db/
# Check if db directory is accessible
ls -la /var/lib/wireguard-ui/ 2>/dev/null || ls -la ./db/ 2>/dev/null

WireGuard Security Hardening

WireGuard Security Hardening Checklist:
Security TestMethodRisk
Server private key extraction from wg0.confRead /etc/wireguard/wg0.conf — PrivateKey field stored in plaintext; server private key compromise allows decryption of all recorded past and future VPN traffic; enables server impersonation to any peerCritical
Preshared key exposure in configurationRead /etc/wireguard/wg0.conf — PresharedKey fields stored in plaintext per peer; preshared key compromise eliminates quantum-resistant protection; combined with private key compromise enables full traffic decryptionHigh
Peer endpoint IP enumerationwg show all — reveals endpoint IP:port for all connected peers, last handshake timestamps, and transfer statistics; maps real-world IP addresses to VPN tunnel IPs for all remote clientsMedium
wg0.conf permission misconfigurationls -la /etc/wireguard/ — check for group or world read bits on directory or config files; world-readable config exposes all private keys and preshared keys to any local userCritical
Management UI credential and config accesswg-easy: POST /api/session with WG_PASSWORD; WireGuard-UI: web login; successful auth gives access to server configuration and all peer private keys and configs managed by the UICritical (requires valid credentials)

Automate WireGuard Security Testing

Ironimo tests WireGuard VPN deployments for configuration file permission assessment, server private key exposure in wg0.conf, preshared key plaintext storage audit, peer endpoint enumeration via wg show, PostUp/PostDown command injection risk, management UI credential strength (wg-easy, WireGuard-UI), and WireGuard volume database access in containerized deployments.

Start free scan