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/manage/vpn.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::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
)
33
)
34
35
register_options(
36
[
37
OptString.new('VPN_CONNECTION', [true, 'Name of VPN connection. `set ACTION LIST` to get a list.', 'OSX_VPN']),
38
OptString.new('SCUTIL_PATH', [true, 'Path to the scutil executable.', '/usr/sbin/scutil']),
39
OptString.new('NETWORKSETUP_PATH', [true, 'Path to the networksetup executable.', '/usr/sbin/networksetup'])
40
]
41
)
42
end
43
44
def run
45
fail_with(Failure::BadConfig, 'Invalid action') if action.nil?
46
47
scutil_path = datastore['SCUTIL_PATH'].shellescape
48
networksetup_path = datastore['NETWORKSETUP_PATH'].shellescape
49
vpn_name = datastore['VPN_CONNECTION']
50
51
if !file?(scutil_path)
52
print_error('Aborting, scutil binary not found.')
53
return
54
end
55
56
if !file?(networksetup_path)
57
print_error('Aborting, networksetup binary not found.')
58
return
59
end
60
61
# Fetch the list of configured VPN connections
62
cmd_list = "#{scutil_path} --nc list"
63
vprint_status(cmd_list)
64
vpn_data = cmd_exec(cmd_list)
65
connected_names = parse_vpn_connection_names(vpn_data, :connected)
66
disconnected_names = parse_vpn_connection_names(vpn_data, :disconnected)
67
68
if action.name == 'LIST'
69
if !connected_names.empty?
70
print_status('VPN Connections Status: UP')
71
connected_names.each do |vpn_name|
72
print_good(' ' + vpn_name)
73
end
74
end
75
if !disconnected_names.empty?
76
print_status('VPN Connections Status: DOWN')
77
disconnected_names.each do |vpn_name|
78
print_good(' ' + vpn_name)
79
end
80
end
81
elsif action.name == 'CONNECT'
82
if connected_names.include?(vpn_name)
83
print_status("#{vpn_name} already connected")
84
return
85
end
86
87
unless disconnected_names.include?(vpn_name)
88
print_error("#{vpn_name} not found")
89
return
90
end
91
92
cmd_up = "#{networksetup_path} -connectpppoeservice '#{vpn_name}'"
93
vprint_status(cmd_up)
94
cmd_exec(cmd_up)
95
elsif action.name == 'DISCONNECT'
96
if disconnected_names.include?(vpn_name)
97
print_status("#{vpn_name} already disconnected")
98
return
99
end
100
101
unless connected_names.include?(vpn_name)
102
print_error("#{vpn_name} not found")
103
return
104
end
105
106
identifier = parse_vpn_connection_identifier(vpn_data, vpn_name)
107
unless identifier
108
print_error("Could not parse #{vpn_name} identifier")
109
return
110
end
111
cmd_down = "#{scutil_path} --nc stop #{identifier}"
112
vprint_status(cmd_down)
113
cmd_exec(cmd_down)
114
end
115
end
116
117
def parse_vpn_connection_names(data, type = :connected)
118
lines = data.lines
119
connection_names = []
120
comp_str = type == :connected ? STR_CONNECTED : STR_DISCONNECTED
121
122
lines.each do |line|
123
if line.start_with?(comp_str) && line =~ /"(.*)"/
124
connection_names << ::Regexp.last_match(1)
125
end
126
end
127
return connection_names
128
end
129
130
def parse_vpn_connection_identifier(data, vpn_name)
131
lines = data.lines
132
lines.each do |line|
133
line.strip!
134
next if line.empty?
135
136
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})/
137
identifier = ::Regexp.last_match(1)
138
return identifier
139
end
140
end
141
return nil
142
end
143
end
144
145