Land #3687, reworks the nat-pmp portscanner
commit
ba1f7c3bf6
|
@ -20,6 +20,7 @@ require 'msf/core/auxiliary/login'
|
|||
require 'msf/core/auxiliary/rservices'
|
||||
require 'msf/core/auxiliary/cisco'
|
||||
require 'msf/core/auxiliary/nmap'
|
||||
require 'msf/core/auxiliary/natpmp'
|
||||
require 'msf/core/auxiliary/iax2'
|
||||
require 'msf/core/auxiliary/ntp'
|
||||
require 'msf/core/auxiliary/pii'
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rex/proto/natpmp'
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module provides methods for working with NAT-PMP
|
||||
#
|
||||
###
|
||||
module Auxiliary::NATPMP
|
||||
|
||||
include Auxiliary::Scanner
|
||||
include Rex::Proto::NATPMP
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(Rex::Proto::NATPMP::DefaultPort),
|
||||
Opt::CHOST
|
||||
],
|
||||
self.class
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -12,20 +12,20 @@ module Proto
|
|||
module NATPMP
|
||||
|
||||
# Return a NAT-PMP request to get the external address.
|
||||
def self.external_address_request
|
||||
def external_address_request
|
||||
[ 0, 0 ].pack('nn')
|
||||
end
|
||||
|
||||
# Parse a NAT-PMP external address response +resp+.
|
||||
# Returns the decoded parts of the response as an array.
|
||||
def self.parse_external_address_response(resp)
|
||||
(ver, op, result, epoch, addr) = resp.unpack("CCvVN")
|
||||
def parse_external_address_response(resp)
|
||||
(ver, op, result, epoch, addr) = resp.unpack("CCnNN")
|
||||
[ ver, op, result, epoch, Rex::Socket::addr_itoa(addr) ]
|
||||
end
|
||||
|
||||
# Return a NAT-PMP request to map remote port +rport+/+protocol+ to local port +lport+ for +lifetime+ ms
|
||||
def self.map_port_request(lport, rport, protocol, lifetime)
|
||||
[ Rex::Proto::NATPMP::Version, # version
|
||||
def map_port_request(lport, rport, protocol, lifetime)
|
||||
[ Rex::Proto::NATPMP::Version, # version
|
||||
protocol, # opcode, which is now the protocol we are asking to forward
|
||||
0, # reserved
|
||||
lport,
|
||||
|
@ -36,8 +36,8 @@ module NATPMP
|
|||
|
||||
# Parse a NAT-PMP mapping response +resp+.
|
||||
# Returns the decoded parts as an array.
|
||||
def self.parse_map_port_response(resp)
|
||||
resp.unpack("CCvVnnN")
|
||||
def parse_map_port_response(resp)
|
||||
resp.unpack("CCnNnnN")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex/proto/natpmp'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::NATPMP
|
||||
include Rex::Proto::NATPMP
|
||||
|
||||
def initialize
|
||||
super(
|
||||
|
@ -21,12 +22,10 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
register_options(
|
||||
[
|
||||
Opt::LPORT,
|
||||
Opt::RPORT,
|
||||
OptInt.new('NATPMPPORT', [true, "NAT-PMP port to use", Rex::Proto::NATPMP::DefaultPort]),
|
||||
OptPort.new('EXTERNAL_PORT', [true, 'The external port to foward from']),
|
||||
OptPort.new('INTERNAL_PORT', [true, 'The internal port to forward to']),
|
||||
OptInt.new('LIFETIME', [true, "Time in ms to keep this port forwarded", 3600000]),
|
||||
OptEnum.new('PROTOCOL', [true, "Protocol to forward", 'TCP', %w(TCP UDP)]),
|
||||
Opt::CHOST
|
||||
],
|
||||
self.class
|
||||
)
|
||||
|
@ -43,21 +42,20 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
# get the external address first
|
||||
vprint_status "#{host} - NATPMP - Probing for external address"
|
||||
req = Rex::Proto::NATPMP.external_address_request
|
||||
udp_sock.sendto(req, host, datastore['NATPMPPORT'], 0)
|
||||
udp_sock.sendto(external_address_request, host, datastore['RPORT'], 0)
|
||||
external_address = nil
|
||||
while (r = udp_sock.recvfrom(12, 1) and r[1])
|
||||
(ver, op, result, epoch, external_address) = Rex::Proto::NATPMP.parse_external_address_response(r[0])
|
||||
(ver, op, result, epoch, external_address) = parse_external_address_response(r[0])
|
||||
end
|
||||
|
||||
vprint_status "#{host} - NATPMP - Sending mapping request"
|
||||
# build the mapping request
|
||||
req = Rex::Proto::NATPMP.map_port_request(
|
||||
datastore['LPORT'].to_i, datastore['RPORT'].to_i,
|
||||
req = map_port_request(
|
||||
datastore['INTERNAL_PORT'], datastore['EXTERNAL_PORT'],
|
||||
Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), datastore['LIFETIME']
|
||||
)
|
||||
# send it
|
||||
udp_sock.sendto(req, host, datastore['NATPMPPORT'], 0)
|
||||
udp_sock.sendto(req, host, datastore['RPORT'], 0)
|
||||
# handle the reply
|
||||
while (r = udp_sock.recvfrom(16, 1) and r[1])
|
||||
handle_reply(Rex::Socket.source_address(host), host, external_address, r)
|
||||
|
@ -78,12 +76,12 @@ class Metasploit3 < Msf::Auxiliary
|
|||
pkt[1] = pkt[1].sub(/^::ffff:/, '')
|
||||
end
|
||||
|
||||
(ver, op, result, epoch, internal_port, external_port, lifetime) = Rex::Proto::NATPMP.parse_map_port_response(pkt[0])
|
||||
(ver, op, result, epoch, internal_port, external_port, lifetime) = parse_map_port_response(pkt[0])
|
||||
|
||||
if (result == 0)
|
||||
if (datastore['RPORT'].to_i != external_port)
|
||||
if (datastore['EXTERNAL_PORT'] != external_port)
|
||||
print_status( "#{external_address} " +
|
||||
"#{datastore['RPORT']}/#{datastore['PROTOCOL']} -> #{map_target} " +
|
||||
"#{datastore['EXTERNAL_PORT']}/#{datastore['PROTOCOL']} -> #{map_target} " +
|
||||
"#{internal_port}/#{datastore['PROTOCOL']} couldn't be forwarded")
|
||||
end
|
||||
print_status( "#{external_address} " +
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex/proto/natpmp'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Exploit::Remote::Udp
|
||||
include Msf::Auxiliary::UDPScanner
|
||||
include Msf::Auxiliary::NATPMP
|
||||
include Rex::Proto::NATPMP
|
||||
|
||||
def initialize
|
||||
super(
|
||||
|
@ -19,68 +21,43 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'License' => MSF_LICENSE
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(Rex::Proto::NATPMP::DefaultPort),
|
||||
Opt::CHOST
|
||||
],
|
||||
self.class
|
||||
)
|
||||
end
|
||||
|
||||
def run_host(host)
|
||||
begin
|
||||
udp_sock = Rex::Socket::Udp.create({
|
||||
'LocalHost' => datastore['CHOST'] || nil,
|
||||
'Context' => {'Msf' => framework, 'MsfExploit' => self}
|
||||
})
|
||||
add_socket(udp_sock)
|
||||
vprint_status "#{host}:#{datastore['RPORT']} - NATPMP - Probing for external address"
|
||||
def scan_host(ip)
|
||||
scanner_send(@probe, ip, datastore['RPORT'])
|
||||
end
|
||||
|
||||
udp_sock.sendto(Rex::Proto::NATPMP.external_address_request, host, datastore['RPORT'].to_i, 0)
|
||||
while (r = udp_sock.recvfrom(12, 1.0) and r[1])
|
||||
handle_reply(host, r)
|
||||
def scanner_prescan(batch)
|
||||
@probe = external_address_request
|
||||
end
|
||||
|
||||
def scanner_process(data, shost, sport)
|
||||
(ver, op, result, epoch, external_address) = parse_external_address_response(data)
|
||||
|
||||
peer = "#{shost}:#{sport}"
|
||||
if (ver == 0 && op == 128 && result == 0)
|
||||
print_good("#{peer} -- external address #{external_address}")
|
||||
# report its external address as alive
|
||||
if inside_workspace_boundary?(external_address)
|
||||
report_host(
|
||||
:host => external_address,
|
||||
:state => Msf::HostState::Alive
|
||||
)
|
||||
end
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused
|
||||
nil
|
||||
rescue ::Exception => e
|
||||
print_error("#{host}:#{datastore['RPORT']} Unknown error: #{e.class} #{e}")
|
||||
end
|
||||
end
|
||||
|
||||
def handle_reply(host, pkt)
|
||||
return if not pkt[1]
|
||||
|
||||
if(pkt[1] =~ /^::ffff:/)
|
||||
pkt[1] = pkt[1].sub(/^::ffff:/, '')
|
||||
end
|
||||
|
||||
(ver, op, result, epoch, external_address) = Rex::Proto::NATPMP.parse_external_address_response(pkt[0])
|
||||
|
||||
if (result == 0)
|
||||
print_status("#{host} -- external address #{external_address}")
|
||||
else
|
||||
print_error("#{peer} -- unexpected version/opcode/result/address: #{ver}/#{op}/#{result}/#{external_address}")
|
||||
end
|
||||
|
||||
# report the host we scanned as alive
|
||||
report_host(
|
||||
:host => host,
|
||||
:host => shost,
|
||||
:state => Msf::HostState::Alive
|
||||
)
|
||||
|
||||
# also report its external address as alive
|
||||
if inside_workspace_boundary?(external_address)
|
||||
report_host(
|
||||
:host => external_address,
|
||||
:state => Msf::HostState::Alive
|
||||
)
|
||||
end
|
||||
|
||||
# report NAT-PMP as being open
|
||||
report_service(
|
||||
:host => host,
|
||||
:port => pkt[2],
|
||||
:host => shost,
|
||||
:port => sport,
|
||||
:proto => 'udp',
|
||||
:name => 'natpmp',
|
||||
:state => Msf::ServiceState::Open
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex/proto/natpmp'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::NATPMP
|
||||
include Rex::Proto::NATPMP
|
||||
|
||||
def initialize
|
||||
super(
|
||||
|
@ -22,10 +23,8 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(Rex::Proto::NATPMP::DefaultPort),
|
||||
OptString.new('PORTS', [true, "Ports to scan (e.g. 22-25,80,110-900)", "1-1000"]),
|
||||
OptEnum.new('PROTOCOL', [true, "Protocol to scan", 'TCP', %w(TCP UDP)]),
|
||||
Opt::CHOST
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
@ -36,32 +35,33 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'Context' => {'Msf' => framework, 'MsfExploit' => self} }
|
||||
)
|
||||
add_socket(udp_sock)
|
||||
vprint_status "Scanning #{datastore['PROTOCOL']} ports #{datastore['PORTS']} on #{host} using NATPMP"
|
||||
peer = "#{host}:#{datastore['RPORT']}"
|
||||
vprint_status("#{peer} Scanning #{datastore['PROTOCOL']} ports #{datastore['PORTS']} using NATPMP")
|
||||
|
||||
# first, send a request to get the external address
|
||||
udp_sock.sendto(Rex::Proto::NATPMP.external_address_request, host, datastore['RPORT'].to_i, 0)
|
||||
udp_sock.sendto(external_address_request, host, datastore['RPORT'], 0)
|
||||
external_address = nil
|
||||
while (r = udp_sock.recvfrom(12, 0.25) and r[1])
|
||||
(ver,op,result,epoch,external_address) = Rex::Proto::NATPMP.parse_external_address_response(r[0])
|
||||
(ver,op,result,epoch,external_address) = parse_external_address_response(r[0])
|
||||
end
|
||||
|
||||
if (external_address)
|
||||
print_good("External address of #{host} is #{external_address}")
|
||||
print_good("#{peer} responded with external address of #{external_address}")
|
||||
else
|
||||
print_error("Didn't get a response for #{host}'s external address")
|
||||
vprint_status("#{peer} didn't respond with an external address")
|
||||
return
|
||||
end
|
||||
|
||||
Rex::Socket.portspec_crack(datastore['PORTS']).each do |port|
|
||||
# send one request to clear the mapping if *we've* created it before
|
||||
clear_req = Rex::Proto::NATPMP.map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 0)
|
||||
udp_sock.sendto(clear_req, host, datastore['RPORT'].to_i, 0)
|
||||
clear_req = map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 0)
|
||||
udp_sock.sendto(clear_req, host, datastore['RPORT'], 0)
|
||||
while (r = udp_sock.recvfrom(16, 1.0) and r[1])
|
||||
end
|
||||
|
||||
# now try the real mapping
|
||||
map_req = Rex::Proto::NATPMP.map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 1)
|
||||
udp_sock.sendto(map_req, host, datastore['RPORT'].to_i, 0)
|
||||
map_req = map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 1)
|
||||
udp_sock.sendto(map_req, host, datastore['RPORT'], 0)
|
||||
while (r = udp_sock.recvfrom(16, 1.0) and r[1])
|
||||
handle_reply(host, external_address, r)
|
||||
end
|
||||
|
@ -85,21 +85,22 @@ class Metasploit3 < Msf::Auxiliary
|
|||
host = pkt[1]
|
||||
protocol = datastore['PROTOCOL'].to_s.downcase
|
||||
|
||||
(ver, op, result, epoch, int, ext, lifetime) = Rex::Proto::NATPMP.parse_map_port_response(pkt[0])
|
||||
(ver, op, result, epoch, int, ext, lifetime) = parse_map_port_response(pkt[0])
|
||||
peer = "#{host}:#{datastore['RPORT']}"
|
||||
if (result == 0)
|
||||
# we always ask to map an external port to the same port on us. If
|
||||
# we get a successful reponse back but the port we requested be forwarded
|
||||
# is different, that means that someone else already has it open
|
||||
if (int != ext)
|
||||
state = Msf::ServiceState::Open
|
||||
print_status("#{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with unmatched ports")
|
||||
print_good("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with unmatched ports")
|
||||
else
|
||||
state = Msf::ServiceState::Closed
|
||||
print_status("#{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with matched ports") if (datastore['DEBUG'])
|
||||
print_status("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with matched ports") if (datastore['DEBUG'])
|
||||
end
|
||||
else
|
||||
state = Msf::ServiceState::Closed
|
||||
print_status("#{external_addr} - #{int}/#{protocol} #{state} because of code #{result} response") if (datastore['DEBUG'])
|
||||
print_status("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of code #{result} response") if (datastore['DEBUG'])
|
||||
end
|
||||
|
||||
if inside_workspace_boundary?(external_addr)
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/proto/natpmp/packet'
|
||||
describe Rex::Proto::NATPMP do
|
||||
subject do
|
||||
mod = Module.new
|
||||
mod.extend described_class
|
||||
mod
|
||||
end
|
||||
|
||||
describe '#parse_external_address_response' do
|
||||
it 'should properly parse non-error responses' do
|
||||
data = "\x00\x80\x00\x00\x00\x33\x50\x53\xc0\xa8\x01\x02"
|
||||
subject.parse_external_address_response(data)
|
||||
ver, opcode, result, epoch, addr = subject.parse_external_address_response(data)
|
||||
expect(ver).to eq(0)
|
||||
expect(opcode).to eq(128)
|
||||
expect(result).to eq(0)
|
||||
expect(epoch).to eq(3362899)
|
||||
expect(addr).to eq('192.168.1.2')
|
||||
end
|
||||
it 'should properly parse error responses' do
|
||||
data = "\x00\x80\x00\x03\x00\x00\x70\x90\x00\x00\x00\x00"
|
||||
subject.parse_external_address_response(data)
|
||||
ver, opcode, result, epoch, addr = subject.parse_external_address_response(data)
|
||||
expect(ver).to eq(0)
|
||||
expect(opcode).to eq(128)
|
||||
expect(result).to eq(3)
|
||||
expect(epoch).to eq(28816)
|
||||
expect(addr).to eq('0.0.0.0')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parse_map_port_response' do
|
||||
it 'should properly parse responses' do
|
||||
data = "\x00\x82\x00\x00\x00\x33\x6f\xd8\x11\x5c\x15\xb3\x00\x36\xee\x80"
|
||||
ver, opcode, result, epoch, internal, external, lifetime = subject.parse_map_port_response(data)
|
||||
expect(ver).to eq(0)
|
||||
expect(opcode).to eq(130)
|
||||
expect(result).to eq(0)
|
||||
expect(epoch).to eq(3370968)
|
||||
expect(internal).to eq(4444)
|
||||
expect(external).to eq(5555)
|
||||
expect(lifetime).to eq(3600000)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue