
A delicious, but malicious SSL-VPN server 🌮
NachoVPN is a Proof of Concept that demonstrates exploitation of SSL-VPN clients, using a rogue VPN server.
It uses a plugin-based architecture so that support for additional SSL-VPN products can be contributed by the community. It currently supports various popular corporate VPN products, such as Cisco AnyConnect, SonicWall NetExtender, Palo Alto GlobalProtect, and Ivanti Connect Secure.
For further details, see our blog post, and HackFest Hollywood 2024 presentation [slides|video].
NachoVPN is built and tested on Ubuntu 22.04.
Install python3-nftables and nftables
Optionally use setcap to avoid sudo requirement:
sudo setcap 'cap_net_raw,cap_net_bind_service,cap_net_admin=eip' /usr/bin/python3.10
Enable IP forwarding:
sudo sysctl -w net.ipv4.ip_forward=1
NachoVPN can be installed from GitHub using pip. Note that this requires git to be installed.
First, create a virtual environment.
On Linux, ensure that the virtual env has access to the system site-packages, so that nftables works:
python3 -m venv env --system-site-packages
source env/bin/activate
On Windows, nftables (and thus packet forwarding) is disabled, so use:
python -m venv env
.\env\Scripts\activate
Then, install NachoVPN:
pip install git+https://github.com/AmberWolfCyber/NachoVPN.git
If you prefer to use Docker, then you can pull the container from the GitHub Container Registry:
docker pull ghcr.io/AmberWolfCyber/nachovpn:release
First, clone this repository, and install setuptools and wheel via pip. You can then run the setup.py script:
git clone https://github.com/AmberWolfCyber/NachoVPN
pip install -U setuptools wheel
python setup.py bdist_wheel
This will generate a wheel file in the dist directory, which can be installed with pip:
pip install dist/nachovpn-1.0.0-py3-none-any.whl
Alternatively, for local development you can install the package in editable mode using:
pip install -e .
You can build the container image with the following command:
docker build -t nachovpn:latest .
To run the server as standalone, use:
python -m nachovpn.server
Alternatively, you can run the server using Docker:
docker run -e SERVER_FQDN=connect.nachovpn.local -e EXTERNAL_IP=1.2.3.4 -v ./certs:/app/certs -p 80:80 -p 443:443 --rm -it nachovpn
This will generate a certificate for the SERVER_FQDN using certbot, and save it to the certs directory, which we've mounted into the container.
Alternatively, for testing purposes, you can skip the certificate generation by setting the SKIP_CERTBOT environment variable.
This will generate a self-signed certificate instead.
docker run -e SERVER_FQDN=connect.nachovpn.local -e SKIP_CERTBOT=1 -e EXTERNAL_IP=1.2.3.4 -p 443:443 --rm -it nachovpn
An example docker-compose file is also provided for convenience.
You can run nachovpn with the -d or --debug command line arguments in order to increase the verbosity of logging, which can aid in debugging.
Alternatively, if the logging is too noisy, you can use the q or --quiet command line argument instead.
NachoVPN supports the following plugins and capabilities:
/pulse URL on the NachoVPN server./sonicwall URL on the NachoVPN server. This requires that the SonicWall Connect Agent is installed on the client machine./delinea URL on the NachoVPN server.NACAgent.exe payload with a self-signed certificate. For testing purposes, you can download and install this CA certificate from /sonicwall/ca.crt before triggering the exploit. For production use-cases, you will need to obtain a valid codesigning certificate from a public CA, sign your NACAgent.exe payload, and place it in the payloads directory (or volume mount it into /app/payloads, if using docker).NACAgent.exe payload is generated for the SonicWall plugin, and written to the payloads directory. This simply spawns a new cmd.exe process on the current user's desktop, running as SYSTEM.To disable a plugin, add it to the DISABLED_PLUGINS environment variable. For example:
DISABLED_PLUGINS=CiscoPlugin,SonicWallPlugin
NachoVPN is configured using environment variables. This makes it easily compatible with containerised deployments.
Global environment variables:
Plugin specific environment variables:
We recommend the following mitigations:
We welcome contributions! Please open an issue or raise a Pull Request.
If you're interested in developing a new plugin, you can take a look at the ExamplePlugin to get started.
NachoVPN is licensed under the MIT license. See the LICENSE file for details.
| Plugin | Product | CVE | Windows RCE | macOS RCE | Privileged | URI Handler | Packet Capture | Demo |
|---|
| Cisco | Cisco AnyConnect | N/A | ✅ | ✅ | ❌ | ❌ | ✅ | Windows / macOS |
| SonicWall | SonicWall NetExtender | CVE-2024-29014 | ✅ | ❌ | ✅ | ✅ | ❌ | Windows |
| PaloAlto | Palo Alto GlobalProtect | CVE-2024-5921 (partial fix) | ✅ | ✅ | ✅ | ❌ | ✅ | Windows / macOS / iOS |
| PulseSecure | Ivanti Connect Secure | CVE-2020-8241 (bypassed) | ✅ | ✅ | ✅ | ✅ (Windows only - disabled by default in 22.8R1) | ✅ | Windows |
| Netskope | Netskope | CVE-2025-0309 | ✅ | ❌ | ✅ | ❌ | ❌ | Windows |
| Delinea | Protocol Handler | CVE-2026-???? | ✅ | ✅ | ❌ | ✅ | ❌ | Windows |
msi_version.txtdownloadsmsi_downloader.pyGlobalProtect.msi.old and GlobalProtect64.msi.old are present in the downloads folder. These files should contain the unmodified MSI installers for a version prior to 6.2.6 (e.g. 6.2.5).| Variable | Description | Default |
|---|
SERVER_FQDN | The fully qualified domain name of the server. | connect.nachovpn.local |
EXTERNAL_IP | The external IP address of the server. | 127.0.0.1 |
WRITE_PCAP | Whether to write captured PCAP files to disk. | false |
DISABLED_PLUGINS | A comma-separated list of plugins to disable. | |
USE_DYNAMIC_SERVER_THUMBPRINT | Whether to calculate the server certificate thumbprint dynamically from the server (useful if behind a proxy). | false |
SERVER_SHA1_THUMBPRINT | Allows overriding the calculated SHA1 thumbprint for the server certificate. | |
SERVER_MD5_THUMBPRINT | Allows overriding the calculated MD5 thumbprint for the server certificate. | |
SMB_ENABLED | Enables the SMB share, available via the tunnel at \\10.10.0.1\<SMB_SHARE_NAME> | false |
SMB_SHARE_NAME | The name to use for the SMB share | SHARE |
SMB_SHARE_PATH | The path to the directory to use for the SMB share | smb |
TUNNEL_PRIVATE | When set to true, enables tunneling but disables internet forwarding for VPN clients. Clients can only access the SMB share. | false |
TUNNEL_FULL | When set to true, enables full tunneling and allows VPN clients to access the internet. Also implies TUNNEL_PRIVATE=true. | false |
| Variable | Description | Default |
|---|
VPN_NAME | The name of the VPN profile, which is presented to the client for Cisco AnyConnect. | NachoVPN |
PULSE_LOGON_SCRIPT | The path to the Pulse Secure logon script. | C:\Windows\System32\calc.exe |
PULSE_LOGON_SCRIPT_MACOS | The path to the Pulse Secure logon script for macOS. | |
PULSE_DNS_SUFFIX | The DNS suffix to be used for Pulse Secure connections. | nachovpn.local |
PULSE_USERNAME | The username to be pre-filled in the Pulse Secure logon dialog. | |
PULSE_SAVE_CONNECTION | Whether to save the Pulse Secure connection in the user's client. | false |
PULSE_ANONYMOUS_AUTH | Whether to use anonymous authentication for Pulse Secure connections. If set to true, the user will not be prompted for a username or password. | false |
PULSE_HOST_CHECKER_RULES_FILE | A JSON file containing a list of registry-based host-checker rules for ICS. See example in src/nachovpn/plugins/pulse/test/example_rules.json | |
PALO_ALTO_MSI_ADD_FILE | The path to a file to be added to the Palo Alto installer MSI. | |
PALO_ALTO_MSI_COMMAND | The command to be executed by the Palo Alto installer MSI. | net user pwnd Passw0rd123! /add && net localgroup administrators pwnd /add |
PALO_ALTO_FORCE_PATCH | Whether to force the patching of the MSI installer if it already exists in the payloads directory. | false |
PALO_ALTO_PKG_COMMAND | The command to be executed by the Palo Alto installer PKG on macOS. | touch /tmp/pwnd |
CISCO_COMMAND_WIN | The command to be executed by the Cisco AnyConnect OnConnect.vbs script on Windows. | calc.exe |
CISCO_COMMAND_MACOS | The command to be executed by the Cisco AnyConnect OnConnect.sh script on macOS. | touch /tmp/pwnd |