Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/osx/manage/vpn.rb
19813 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
9
STR_CONNECTED = '* (Connected)'
10
STR_DISCONNECTED = '* (Disconnected)'
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'OSX VPN Manager',
17
'Description' => %q{
18
This module lists VPN connections and tries to connect to them using stored credentials.
19
},
20
'License' => MSF_LICENSE,
21
'Author' => [
22
'Peter Toth <globetother[at]gmail.com>'
23
],
24
'Platform' => [ 'osx' ],
25
'SessionTypes' => [ 'shell', 'meterpreter' ],
26
'Actions' => [
27
[ 'LIST', { 'Description' => 'Show a list of VPN connections' } ],
28
[ 'CONNECT', { 'Description' => 'Connect to a VPN using stored credentials' } ],
29
[ 'DISCONNECT', { 'Description' => 'Disconnect from a VPN' } ]
30
],
31
'DefaultAction' => 'LIST',
32
'Notes' => {
33
'Stability' => [CRASH_SAFE],
34
'SideEffects' => [],
35
'Reliability' => []
36
}
37
)
38
)
39
40
register_options(
41
[
42
OptString.new('VPN_CONNECTION', [true, 'Name of VPN connection. `set ACTION LIST` to get a list.', 'OSX_VPN']),
43
OptString.new('SCUTIL_PATH', [true, 'Path to the scutil executable.', '/usr/sbin/scutil']),
44
OptString.new('NETWORKSETUP_PATH', [true, 'Path to the networksetup executable.', '/usr/sbin/networksetup'])
45
]
46
)
47
end
48
49
def run
50
fail_with(Failure::BadConfig, 'Invalid action') if action.nil?
51
52
scutil_path = datastore['SCUTIL_PATH'].shellescape
53
networksetup_path = datastore['NETWORKSETUP_PATH'].shellescape
54
vpn_name = datastore['VPN_CONNECTION']
55
56
if !file?(scutil_path)
57
print_error('Aborting, scutil binary not found.')
58
return
59
end
60
61
if !file?(networksetup_path)
62
print_error('Aborting, networksetup binary not found.')
63
return
64
end
65
66
# Fetch the list of configured VPN connections
67
cmd_list = "#{scutil_path} --nc list"
68
vprint_status(cmd_list)
69
vpn_data = cmd_exec(cmd_list)
70
connected_names = parse_vpn_connection_names(vpn_data, :connected)
71
disconnected_names = parse_vpn_connection_names(vpn_data, :disconnected)
72
73
if action.name == 'LIST'
74
if !connected_names.empty?
75
print_status('VPN Connections Status: UP')
76
connected_names.each do |vpn_name|
77
print_good(' ' + vpn_name)
78
end
79
end
80
if !disconnected_names.empty?
81
print_status('VPN Connections Status: DOWN')
82
disconnected_names.each do |vpn_name|
83
print_good(' ' + vpn_name)
84
end
85
end
86
elsif action.name == 'CONNECT'
87
if connected_names.include?(vpn_name)
88
print_status("#{vpn_name} already connected")
89
return
90
end
91
92
unless disconnected_names.include?(vpn_name)
93
print_error("#{vpn_name} not found")
94
return
95
end
96
97
cmd_up = "#{networksetup_path} -connectpppoeservice '#{vpn_name}'"
98
vprint_status(cmd_up)
99
cmd_exec(cmd_up)
100
elsif action.name == 'DISCONNECT'
101
if disconnected_names.include?(vpn_name)
102
print_status("#{vpn_name} already disconnected")
103
return
104
end
105
106
unless connected_names.include?(vpn_name)
107
print_error("#{vpn_name} not found")
108
return
109
end
110
111
identifier = parse_vpn_connection_identifier(vpn_data, vpn_name)
112
unless identifier
113
print_error("Could not parse #{vpn_name} identifier")
114
return
115
end
116
cmd_down = "#{scutil_path} --nc stop #{identifier}"
117
vprint_status(cmd_down)
118
cmd_exec(cmd_down)
119
end
120
end
121
122
def parse_vpn_connection_names(data, type = :connected)
123
lines = data.lines
124
connection_names = []
125
comp_str = type == :connected ? STR_CONNECTED : STR_DISCONNECTED
126
127
lines.each do |line|
128
if line.start_with?(comp_str) && line =~ /"(.*)"/
129
connection_names << ::Regexp.last_match(1)
130
end
131
end
132
return connection_names
133
end
134
135
def parse_vpn_connection_identifier(data, vpn_name)
136
lines = data.lines
137
lines.each do |line|
138
line.strip!
139
next if line.empty?
140
141
if line.include?(vpn_name) && line =~ /([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})/
142
identifier = ::Regexp.last_match(1)
143
return identifier
144
end
145
end
146
return nil
147
end
148
end
149
150