############################################################# # # COMPASS SECURITY ADVISORY # https://www.compass-security.com/research/advisories/ # ############################################################# # # Product: openvpn-monitor # Vendor: https://github.com/furlongm/openvpn-monitor # CSNC ID: CSNC-2021-011 # CVE ID: CVE-2021-31604 # Subject: Cross-Site Request Forgery (CSRF) # Severity: Medium # Effect: Denial of Service # Author: Emanuel Duss # Sylvain Heiniger # Date: 2021-09-22 # ############################################################# Introduction ------------ openvpn-monitor is a simple Python program to generate HTML that displays the status of an OpenVPN server, including all current connections. It uses the OpenVPN management console. It typically runs on the same host as the OpenVPN server. [0][1] During a customer project, several security vulnerabilities were discovered in this software. This advisory describes an a CSRF vulnerability which allows an attacker to disconnect arbitrary VPN clients. Affected -------- - Vulnerable: openvpn-monitor <= 1.1.3 - Not vulnerable: none The vulnerability is already fixed in the source code [3], but there is no new release which contains the fix. Therefore, all currently available releases contain this vulnerability. Technical Description --------------------- The client disconnect feature does not require a CSRF token: [CUT BY COMPASS] @app.route('/', method='POST') def post_slash(): vpn_id = request.forms.get('vpn_id') ip = request.forms.get('ip') port = request.forms.get('port') client_id = request.forms.get('client_id') return render(vpn_id=vpn_id, ip=ip, port=port, client_id=client_id) [CUT BY COMPASS] An attacker can create the following CSRF attack page:
When a victim with access to the openvpn-monitor application accesses this attack page, the following HTTP request is automatically sent to the openvpn-monitor application: POST / HTTP/1.1 Host: openvpn-monitor.example.net Content-Type: application/x-www-form-urlencoded Origin: http://attacker.example.com Referer: http://attacker.example.com/attackpage.html [CUT BY COMPASS] vpn_id=UDP&ip=10.5.23.42&port=1194&client_id=5 This will disconnect the client with the ID 5. Knowing or guessing the `client_id` parameter is sufficient for an attacker. It's not needed to know the exact values of the other parameters, since they are only used on oder OpenVPN server versions: [CUT BY COMPASS] class OpenvpnMgmtInterface(object): def __init__(self, cfg, **kwargs): self.vpns = cfg.vpns if kwargs.get('vpn_id'): vpn = self.vpns[kwargs['vpn_id']] self._socket_connect(vpn) if vpn['socket_connected']: release = self.send_command('version\n') version = semver(self.parse_version(release).split(' ')[1]) if version.major == 2 and \ version.minor >= 4 and \ kwargs.get('client_id'): command = 'client-kill {0!s}\n'.format(kwargs['client_id']) else: command = 'kill {0!s}:{1!s}\n'.format(kwargs['ip'], kwargs['port']) self.send_command(command) self._socket_disconnect() [CUT BY COMPASS] This attack can even be performed if the client disconnect feature is disabled because of the authorization bypass vulnerability (CVE-2021-31606) and be combined with the management socket command injection vulnerability (CVE-2021-31604) to send arbitrary commands via a CSRF attack page to the OpenVPN server management interface socket. This would then have a much higher severity. Vulnerability Classification ---------------------------- CVSS v3.1 Metrics [2]: * CVSS Base Score: 4.7 * CVSS Vector: AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:N/A:L Workaround / Fix ---------------- # openvpn-monitor Vendor Each non-idempotent request must contain an additional token that cannot be predicted by the attacker. When receiving a request, the server needs to validate the token. If the token is incorrect, missing, or does not match the user's session, the request should be dropped. # openvpn-monitor Users Since there is no CSRF protection implemented at the moment, a possible workaround would be to add HTTP Basic Authentication e.g. via a reverse proxy using a strong password. An attacker could therefore not perform CSRF attacks anymore, because the password (which is unknown to the attacker) would have to be sent in the HTTP request header. Timeline -------- 2021-05-05: Vulnerability discovered 2021-04-20: Requested CVE ID @ MITRE 2021-04-20: Contacted vendor 2021-04-22: Sent details via email to vendor 2021-04-24: Vendor confirmed and already started to work on a fix 2021-09-08: Asked vendor for updates 2021-09-08: Vendor told it's planned to fix the CSRF issue but it's also OK to already publish the advisory 2021-09-22: Public disclosure References ---------- [0] http://openvpn-monitor.openbytes.ie/ [1] https://github.com/furlongm/openvpn-monitor [2] https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:N/A:L&version=3.1