Land #3687, reworks the nat-pmp portscanner

bug/bundler_fix
HD Moore 2014-08-26 14:34:46 -05:00
commit ba1f7c3bf6
7 changed files with 140 additions and 88 deletions

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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} " +

View File

@ -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

View File

@ -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)

View File

@ -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