-
Notifications
You must be signed in to change notification settings - Fork 429
Security
This page gives an overview of the attack surface of the ESP32 NAT Router and recommends hardening measures for each deployment scenario.
The router exposes several management interfaces over the network. Understanding where each listens and what access it grants is the starting point for any hardening effort.
| Interface | Protocol | Default port | Auth | Encryption | Default state |
|---|---|---|---|---|---|
| Web UI | HTTP/TCP | 80 | Optional password | None (plaintext) | Enabled |
| Remote Console | TCP | 2323 | Optional password | None (plaintext) | Disabled |
| MCP Bridge | TCP | 3000 | None | None (plaintext) | Disabled |
| PCAP stream | UDP | 19000 | None | None (plaintext) | Off |
Both the Web UI and the Remote Console carry credentials and full configuration in plaintext. Anyone who can reach the port can intercept passwords and configuration data or take over the device. Due to memory restrictions and required certificate handling TLS is not implemented. WireGuard is the light-weight alternative here.
By default, management interfaces listen on all active interfaces (AP, STA/ETH, VPN). In a typical deployment the interfaces have very different trust levels:
Internet ──────── STA/ETH (untrusted uplink)
│
ESP32 Router
│
AP clients ─────── AP (internal, semi-trusted)
│
VPN tunnel ──────── VPN (trusted, encrypted)
The AP interface is under your control and its clients are known devices. The STA/ETH uplink connects to infrastructure you do not own. The VPN tunnel is the safest channel because the transport itself is encrypted by WireGuard before it even reaches the router.
With no password set, the web interface and remote console (if enabled) are completely open to anyone who can reach them. Always set a password before exposing the router on a shared network:
set_router_password <strong-password>
This protects both the web UI and the remote console with the same credential. Use a strong, unique password — the credential is transmitted in plaintext so a short or reused password is easy to capture.
Even on a home network the STA/ETH interface connects to equipment you do not fully control. Restrict management to the AP interface only:
Web UI:
web_ui bind ap
Remote Console (if used):
remote_console bind ap
This ensures that even if someone on the upstream network scans your IP, neither management port is reachable from that side.
Restrict management to the AP interface as above, and additionally set a strong password. Consider disabling the remote console entirely if you do not need it:
remote_console disable
If the AP itself serves untrusted clients (e.g. IoT devices or guests), consider restricting management to VPN only (see below) so that AP clients cannot reach the configuration interface either.
In hostile environments (public WiFi, hotel networks, conference networks) the only safe option is to route management traffic through the WireGuard VPN tunnel:
Web UI — VPN only:
web_ui bind vpn
Remote Console — VPN only:
remote_console bind vpn
With these settings the router rejects HTTP and console connections arriving on the STA and AP interfaces. You can only reach the management interface after your client machine has an active WireGuard connection to the same VPN server.
To lock out AP clients as well, use vpn as the only allowed interface for both services.
For production deployments where you configure once and then leave the device running, the safest option is to disable the web interface entirely after initial setup:
web_ui disable
All parameters can still be changed via the serial console or the remote console (if enabled and bound to a trusted interface). Re-enable with:
web_ui enable
The ACL firewall can add a second layer of protection independent of the interface-bind settings. The to_ap ACL controls traffic arriving from AP clients toward the router itself, and to_esp controls traffic arriving from the uplink.
Block web UI access from AP clients (port 80):
acl add to_ap TCP any * 192.168.4.1 80 deny
Block remote console from the uplink:
acl add to_esp TCP any * any 2323 deny
Allow management only from a specific trusted client:
acl add to_ap TCP 192.168.4.100 * 192.168.4.1 80 allow
acl add to_ap TCP any * 192.168.4.1 80 deny
ACL rules are evaluated in order; the first match wins. A deny rule without a preceding allow drops all matching traffic regardless of the interface-bind settings — the two mechanisms are independent and complementary.
See Firewall for the full ACL reference.
Binding management interfaces to the VPN interface and routing your admin machine through the same VPN server gives you:
- Encrypted transport: WireGuard uses ChaCha20-Poly1305; credentials and configuration are not readable in transit.
- Authentication at the transport layer: Only peers with a valid key pair can establish the tunnel.
- No open ports on the uplink: From the perspective of the public network the only visible traffic is WireGuard UDP (port 51820 by default).
Setup steps:
- Configure WireGuard with your VPN server as the peer.
- Connect your admin machine to the same VPN server.
- Set
web_ui bind vpnandremote_console bind vpn. - Access the router at its VPN tunnel IP (e.g.
http://10.0.0.2).
See WireGuard VPN for detailed VPN configuration.
The config export (/api/config-export) downloads a JSON file containing all router settings including WiFi passwords and WireGuard keys.
Enter a passphrase in the Passphrase field before clicking Write Config. The file is then an opaque JSON envelope — the payload is encrypted with XChaCha20-Poly1305 and the key is derived from your passphrase using PBKDF2-HMAC-SHA256 with 10 000 iterations and a random 16-byte salt. A different salt and nonce are generated for every export, so two exports of the same config with the same passphrase produce different files.
The Poly1305 authentication tag means any tampering with the file is detected on import: a wrong passphrase or a modified ciphertext causes the import to be rejected before any settings are written.
Encrypted exports include the WireGuard private key and PSK, making them suitable as a complete backup. Encrypted files are safe to store in cloud storage or send over untrusted channels. The passphrase is never stored on the device.
If no passphrase is entered the file is plain JSON. The following secrets are intentionally omitted from plain exports: STA WiFi password, AP WiFi password, WireGuard private key, and WireGuard PSK. The remaining settings (SSIDs, IPs, ACL rules, port maps, etc.) are exported in cleartext. Treat the file like sensitive configuration material:
- Do not store it in shared drives, cloud storage, or version control.
- Delete it after use.
- Transmit only over encrypted channels (e.g. through a WireGuard-protected connection to the router).
The export endpoint is protected by the web UI password (if set) and subject to the same interface-bind restrictions as all other management operations.
The PCAP stream (port 19000/UDP) carries raw packet data with no authentication. It is only active when explicitly enabled and always disabled after reboot. Once enabled, disable it when not used any more:
pcap off
The MCP Bridge (port 3000/TCP) provides full programmatic control of the router with no authentication. It is a separate binary and should only be started on isolated or VPN-only networks.
Passwords are stored as salted SHA-256 hashes in NVS. WiFi credentials and WireGuard keys are stored as plaintext strings in NVS. Physical access to the device (serial console or flash read) grants access to these stored credentials. For high-security deployments, use ESP32's flash encryption and secure boot.
NVS is not erased by re-flashing the firmware. To wipe all stored credentials:
esptool.py -p /dev/ttyUSB0 erase_flash| Step | Command / Action |
|---|---|
| Set a strong password | set_router_password <password> |
| Encrypt config exports | Enter a passphrase in the Web UI before exporting |
| Restrict web UI to AP only (home) | web_ui bind ap |
| Restrict web UI to VPN only (untrusted) | web_ui bind vpn |
| Restrict remote console similarly |
remote_console bind ap or remote_console bind vpn
|
| Disable remote console if not needed | remote_console disable |
| Disable web UI after initial config | web_ui disable |
| Add ACL rules for defense in depth | acl add to_esp TCP any * any 2323 deny |
| Disable PCAP when not in use (default off) | pcap off |
| Don't use the MCP Bridge on an unsave network | Don't start/install Python code |
| Erase NVS before decommissioning | esptool.py erase_flash |