Immersive Labs Unearths Vulnerability in Aviatrix VPN
Aviatrix, an enterprise VPN company with customers including Nasa, Shell and BT, has recently patched a vulnerability uncovered by Immersive Labs researcher and content engineer Alex Seymour.
The vulnerability would have allowed an attacker who already had access to a machine to escalate privileges and achieve anything they wanted; for example, gaining access to files, folders and network services that the user would not previously have been able to access.
This comes just two months after the National Security Agency (NSA) and National Security Council (NSC) warned of state-sponsored attackers targeting vulnerabilities in VPNs.
Alex said, ‘Coming hot on the heels of the UK and US Government warnings about VPN vulnerabilities, this underlines that often the technology protecting enterprises needs to be managed as tightly as the people using it. People tend to think of their VPN as one of the more secure elements of their security posture, so it should be a bit of a wakeup call for the industry.’
‘Users should install the new patch as soon as possible to ensure there is no exploitation in the wild.’
Aviatrix has been responsive and open to discussion concerning this vulnerability, taking our remediation advice seriously. The changes made to resolve the issue were timely and well implemented. They have kept communication open throughout the disclosure process, remaining positive and showing that they take the security of their customers and product seriously.
If you want to get hands on with this vulnerability, head to Immersive Labs Lite and try our lab for free – you’ll find it in ‘Emerging Threats’. Below you can see Alex’s full breakdown of the vulnerability.
Multiple local privilege escalation vulnerabilities have been discovered in the Aviatrix VPN client. The client is designed to be a user-friendly VPN; it offers the convenience of SAML authentication and is built on top of OpenVPN.
On each operating system there are two main parts to the VPN client: a service enabled by default post-installation, and the user-client application that interfaces between the user and the services.
Research into the Aviatrix VPN client began after noting the verbose output when starting the client on a Linux machine.
The last two lines of the client’s startup output were what triggered the initial interest. They show that two local web servers are started when the client is launched.
Navigating to either server returns the text ‘SuccessAviatrix’; however, examining the terminal that was used to start the client revealed a request log. The particular format of this output, combined with the format of the last two lines of the client’s startup output, indicated that these services are based on Flask. This means that at least some of the application is written in Python.
Closer inspection of the AVPNC executable using strings reveals that the executable is a compiled Python application.
Several tools can be used to compile Python applications. PyInstaller seemed the most likely, as it can be used to compile applications for Windows, Linux and macOS, while py2exe only compiles applications for Windows.
PyInstaller has different modes; one-file mode will compile all of the required files into a single executable, while one-folder mode bundles all the required files into a directory. Looking at the installation directory, there is nothing to indicate a one-folder mode PyInstaller compilation. PyInstaller executables compiled with one-file mode, however, unpack files into the temporary directory when the application is executed. The files are placed into a directory whose name is a random value prepended with ‘_MEI’. Two of these directories are found in /tmp on Linux when both the client and the service are running.
During the installation process on Windows, Linux and FreeBSD, the permission set applied to the client’s installation directory is highly permissive. Examining the installation script extracted from the Linux .deb file and .tar.gz file reveals that world-writable permissions (777) are recursively applied to /usr/bin/AVPNC_bin.
For Windows, the client is distributed as an Inno Setup installer. After extracting the Inno Setup script (.iss) file, the setup process executes icacls to grant read-write permissions to Everyone on the application’s directory.
A similar process can be seen in the launch_mac.sh script included with the macOS .pkg file; however, as the permissions are not applied recursively, the macOS scripts are not affected by this issue.
The Linux, macOS and FreeBSD versions of the VPN client use the openvpn command’s –up and –down flags to execute shell scripts when a VPN connection is established and terminated respectively. On Linux the client uses /usr/bin/AVPNC_bin/scripts/linux.sh as the value for both of these arguments; similarly, FreeBSD uses /usr/bin/AVPNC_bin/scripts/bsd.sh for each argument. On macOS the client passes /Applications/Aviatrix VPN Client.app/Contents/Resources/scripts/up.sh to –up and /Applications/Aviatrix VPN Client.app/Contents/Resources/scripts/down.sh to –down.
Because of the weak file permissions set on the installation directory on Linux and FreeBSD, it is possible to modify these scripts. As the backend service executes the openvpn command, this results in the script being executed with elevated privileges.
Adding whoami > /tmp/exec_out to either linux.sh or bsd.sh will result in its execution whenever any user establishes or terminates a VPN connection on the corresponding operating system, causing the executing user (root) to be written to /tmp/exec_out for validation.
On Windows the client does not use the –up and –down arguments, making exploitation a little harder. Due to the file permissions, it is possible to replace the service executable ((AVPNC_RP.exe)) with a malicious executable that, when executed, would be running as NT AUTHORITY\SYSTEM. Of course, the service has to be stopped before this file can be replaced, which requires either an administrator account or the relevant permissions to restart the service. A simple executable was created using the C# code below to demonstrate this attack avenue.
static void Main(string args)
string UserName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
When the AVPNC_RP service starts, it initialises a custom Python SimpleHTTPServer on port 5006 bound to the loopback interface on 127.0.0.1. Examination of the CommandServerRequestHandler class shows that it inherits from the BaseCommandExec class, which sounded particularly interesting, as both of these classes reside in a file called command_exec.py. When this server receives an HTTP POST request, it calls the decode_and_execute method from the BaseCommandExec class and passes it the POST body, which is expected to be a JSON structure.
The decode_and_execute method uses the operation value from the request JSON to determine what task it should run. Three arguments are expected to be present in the request JSON: operation specifies the task the service should perform, request_no seems to be an incrementing integer used to track requests, and args is an array of arguments to pass to operation handling methods.
The available tasks are mapped in the Tasks class within the base_tasks.py file.
At first glance, the func_allow_local_perm and func_allow_local_perm_pfile seemed like they could be used to change permissions on possibly sensitive files. Upon examination, however, there didn’t seem to be any way of influencing the files or directories they operated on.
The func_disconnect_openvpn method looked a better bet for code execution, as it built and returned a command to execute depending on the operating system; on Windows it produced a taskkill command, on Linux a pkill command, and on FreeBSD and macOS a kill command. Closer inspection didn’t reveal any means of injecting additional commands or changing the process to be terminated.
None of the other operations turned up much apart from connect_openvpn. The func_connect_openvpn method comprised a far more complex command-building process than that of func_disconnect_openvpn and accepted three arguments from the POST request.
Interestingly, the handling of conffile_ varies depending on the operating system. On Linux and macOS it is processed by calling shellescape’s quote function on it. On Windows, however, the value is Base64 decoded. The value of tempfile_ is handled identically on every operating system and passed to the shellescape module’s quote function.
The resulting processed values are later fed into the arguments passed to openvpn; conffile is used as the value to the –config argument while tempfile is used as the value to the –auth-user-pass argument.
Before the method returns, the value of the command variable is updated, again determined by the operating system. On Linux and macOS the value is cast as a string; on Windows it is Base64 encoded.
The resulting command constructed by func_connect_openvpn is then passed to the Popen method of BaseCommandExec, which feeds it into subprocess.Popen to be executed. The Windows Base64-encoded command is decoded before being passed to win_subprocess.Popen, which does some encoding handling to ensure the command is executed correctly.
At this point func_openvpn_connect looks like it is a good avenue to gain code execution; all that’s left is to craft a valid request to send to the service to test the theory. On the surface, this is a simple task: having already identified the JSON structure and expected arguments, it should be easy to craft the request. After sending several requests, however, it became clear that this wasn’t as simple as it looked.
The service uses certificate authentication to validate that requests stem from a valid source, preventing anyone except the client application from making requests to the service. At least, that’s what it was supposed to do. The certificate has to come from somewhere for the client to use it when communicating with the service.
A bit of digging inside the RootProcessExec class in command_exec.py revealed how the client certificates were retrieved. All of the certificates and private keys used by the application are stored in certs.py. Before a request is made to the service, the application writes the relevant key and certificates to a temporary file using create_client_auth_files in file_utils.py.
The file is deleted by the exit_application function in exitapp.py when the application exists.
When the application is running, the required certificates can be recovered from the operating system’s temporary directory; the filename is prefixed with mvLOKecsEpki. Otherwise, the file can be recreated with the code below.
with open('client_cert', 'w') as cert_file:
cert_file.write(certs.CL_AUTH_KEY_CONTENT + certs.CL_AUTH_CERT_CONTENT)
With all this information in hand, it is possible to craft requests to the service and gain code execution by passing commands wrapped in subshells in args instead of the expected file paths.
The above code has been refined into a self-contained Python PoC for Linux and a PowerShell PoC for Windows. The PowerShell PoC varies, as it has to account for the required Base64 encoding of the arguments. The certificate also had to be converted into the PKCS#12 format to function correctly with PowerShell’s Invoke-WebRequest module.
7/10/2019 – Findings reported to Aviatrix.
10/10/2019 – Aviatrix confirmed they were addressing the findings.
21/10/2019 – Update and progress discussion with Aviatrix.