Skip to content

Commit 935a232

Browse files
author
HD Moore
committed
Updates to NAT-PMP, lands rapid7#4041
2 parents 6b9742b + 8fdae8f commit 935a232

File tree

4 files changed

+129
-91
lines changed

4 files changed

+129
-91
lines changed

lib/msf/core/auxiliary/natpmp.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,20 @@ def initialize(info = {})
1818
register_options(
1919
[
2020
Opt::RPORT(Rex::Proto::NATPMP::DefaultPort),
21-
Opt::CHOST
21+
Opt::CHOST,
22+
OptInt.new('LIFETIME', [true, "Time in ms to keep this port forwarded (set to 0 to destroy a mapping)", 3600000]),
23+
OptEnum.new('PROTOCOL', [true, "Protocol to forward", 'TCP', %w(TCP UDP)])
2224
],
2325
self.class
2426
)
2527
end
28+
29+
def lifetime
30+
@lifetime ||= datastore['LIFETIME']
31+
end
32+
33+
def protocol
34+
@protocol ||= datastore['PROTOCOL']
35+
end
2636
end
2737
end

lib/rex/proto/natpmp/packet.rb

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
#
44
# NAT-PMP protocol support
55
#
6-
# by Jon Hart <[email protected]>
7-
#
86
##
97

108
module Rex
@@ -16,13 +14,42 @@ def external_address_request
1614
[ 0, 0 ].pack('nn')
1715
end
1816

17+
def get_external_address(udp_sock, host, port, timeout=1)
18+
vprint_status("#{host}:#{port} - Probing NAT-PMP for external address")
19+
udp_sock.sendto(external_address_request, host, port, 0)
20+
external_address = nil
21+
while (r = udp_sock.recvfrom(12, timeout) and r[1])
22+
(ver, op, result, epoch, external_address) = parse_external_address_response(r[0])
23+
if external_address
24+
vprint_good("#{host}:#{port} - NAT-PMP external address is #{external_address}")
25+
break
26+
end
27+
end
28+
external_address
29+
end
30+
1931
# Parse a NAT-PMP external address response +resp+.
2032
# Returns the decoded parts of the response as an array.
2133
def parse_external_address_response(resp)
2234
(ver, op, result, epoch, addr) = resp.unpack("CCnNN")
2335
[ ver, op, result, epoch, Rex::Socket::addr_itoa(addr) ]
2436
end
2537

38+
def map_port(udp_sock, host, port, int_port, ext_port, protocol, lifetime, timeout=1)
39+
vprint_status("#{host}:#{port} - Sending NAT-PMP mapping request")
40+
# build the mapping request
41+
req = map_port_request(int_port, ext_port,
42+
Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), datastore['LIFETIME'])
43+
# send it
44+
udp_sock.sendto(req, host, datastore['RPORT'], 0)
45+
# handle the reply
46+
while (r = udp_sock.recvfrom(16, timeout) and r[1])
47+
(_, _, result, _, _, actual_ext_port, _) = parse_map_port_response(r[0])
48+
return (result == 0 ? actual_ext_port : nil)
49+
end
50+
nil
51+
end
52+
2653
# Return a NAT-PMP request to map remote port +rport+/+protocol+ to local port +lport+ for +lifetime+ ms
2754
def map_port_request(lport, rport, protocol, lifetime)
2855
[ Rex::Proto::NATPMP::Version, # version
@@ -39,6 +66,7 @@ def map_port_request(lport, rport, protocol, lifetime)
3966
def parse_map_port_response(resp)
4067
resp.unpack("CCnNnnN")
4168
end
69+
4270
end
4371

4472
end

modules/auxiliary/admin/natpmp/natpmp_map.rb

Lines changed: 72 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,48 @@ def initialize
2222

2323
register_options(
2424
[
25-
OptPort.new('EXTERNAL_PORT', [true, 'The external port to foward from']),
26-
OptPort.new('INTERNAL_PORT', [true, 'The internal port to forward to']),
27-
OptInt.new('LIFETIME', [true, "Time in ms to keep this port forwarded", 3600000]),
28-
OptEnum.new('PROTOCOL', [true, "Protocol to forward", 'TCP', %w(TCP UDP)]),
25+
OptString.new('EXTERNAL_PORTS', [true, 'The external ports to foward from (0 to let the target choose)', 0]),
26+
OptString.new('INTERNAL_PORTS', [true, 'The internal ports to forward to', '22,135-139,80,443,445'])
2927
],
3028
self.class
3129
)
3230
end
3331

32+
def build_ports(ports_string)
33+
# We don't use Rex::Socket.portspec_crack because we need to allow 0 and preserve order
34+
ports = []
35+
ports_string.split(/[ ,]/).map { |s| s.strip }.compact.each do |port_part|
36+
if /^(?<port>\d+)$/ =~ port_part
37+
ports << port.to_i
38+
elsif /^(?<low>\d+)\s*-\s*(?<high>\d+)$/ =~ port_part
39+
ports |= (low..high).to_a.map(&:to_i)
40+
else
41+
fail ArgumentError, "Invalid port specification #{port_part}"
42+
end
43+
end
44+
ports
45+
end
46+
47+
def setup
48+
super
49+
@external_ports = build_ports(datastore['EXTERNAL_PORTS'])
50+
@internal_ports = build_ports(datastore['INTERNAL_PORTS'])
51+
52+
if @external_ports.size > @internal_ports.size
53+
fail ArgumentError, "Too many external ports specified (#{@external_ports.size}); " +
54+
"must be one port (0) or #{@internal_ports.size} ports"
55+
end
56+
57+
if @external_ports.size < @internal_ports.size
58+
if @external_ports != [0]
59+
fail ArgumentError, "Incorrect number of external ports specified (#{@external_ports.size}); " +
60+
"must be one port (0) or #{@internal_ports.size} ports"
61+
else
62+
@external_ports = [0] * @internal_ports.size
63+
end
64+
end
65+
end
66+
3467
def run_host(host)
3568
begin
3669

@@ -40,25 +73,42 @@ def run_host(host)
4073
})
4174
add_socket(udp_sock)
4275

43-
# get the external address first
44-
vprint_status "#{host} - NATPMP - Probing for external address"
45-
udp_sock.sendto(external_address_request, host, datastore['RPORT'], 0)
46-
external_address = nil
47-
while (r = udp_sock.recvfrom(12, 1) and r[1])
48-
(ver, op, result, epoch, external_address) = parse_external_address_response(r[0])
49-
end
76+
external_address = get_external_address(udp_sock, host, datastore['RPORT']) || host
77+
78+
@external_ports.each_index do |i|
79+
external_port = @external_ports[i]
80+
internal_port = @internal_ports[i]
5081

51-
vprint_status "#{host} - NATPMP - Sending mapping request"
52-
# build the mapping request
53-
req = map_port_request(
54-
datastore['INTERNAL_PORT'], datastore['EXTERNAL_PORT'],
55-
Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), datastore['LIFETIME']
56-
)
57-
# send it
58-
udp_sock.sendto(req, host, datastore['RPORT'], 0)
59-
# handle the reply
60-
while (r = udp_sock.recvfrom(16, 1) and r[1])
61-
handle_reply(Rex::Socket.source_address(host), host, external_address, r)
82+
actual_ext_port = map_port(udp_sock, host, datastore['RPORT'], internal_port, external_port, Rex::Proto::NATPMP.const_get(protocol), lifetime)
83+
map_target = Rex::Socket.source_address(host)
84+
requested_forwarding = "#{external_address}:#{external_port}/#{protocol}" +
85+
" -> " +
86+
"#{map_target}:#{internal_port}/#{protocol}"
87+
if actual_ext_port
88+
map_target = datastore['CHOST'] ? datastore['CHOST'] : Rex::Socket.source_address(host)
89+
actual_forwarding = "#{external_address}:#{actual_ext_port}/#{protocol}" +
90+
" -> " +
91+
"#{map_target}:#{internal_port}/#{protocol}"
92+
if external_port == 0
93+
print_good("#{actual_forwarding} forwarded")
94+
else
95+
if (external_port != 0 && external_port != actual_ext_port)
96+
print_good("#{requested_forwarding} could not be forwarded, but #{actual_forwarding} could")
97+
else
98+
print_good("#{requested_forwarding} forwarded")
99+
end
100+
end
101+
else
102+
print_error("#{requested_forwarding} could not be forwarded")
103+
end
104+
105+
report_service(
106+
:host => host,
107+
:port => datastore['RPORT'],
108+
:proto => 'udp',
109+
:name => 'natpmp',
110+
:state => Msf::ServiceState::Open
111+
)
62112
end
63113
rescue ::Interrupt
64114
raise $!
@@ -69,43 +119,4 @@ def run_host(host)
69119
end
70120
end
71121

72-
def handle_reply(map_target, host, external_address, pkt)
73-
return if not pkt[1]
74-
75-
if(pkt[1] =~ /^::ffff:/)
76-
pkt[1] = pkt[1].sub(/^::ffff:/, '')
77-
end
78-
79-
(ver, op, result, epoch, internal_port, external_port, lifetime) = parse_map_port_response(pkt[0])
80-
81-
if (result == 0)
82-
if (datastore['EXTERNAL_PORT'] != external_port)
83-
print_status( "#{external_address} " +
84-
"#{datastore['EXTERNAL_PORT']}/#{datastore['PROTOCOL']} -> #{map_target} " +
85-
"#{internal_port}/#{datastore['PROTOCOL']} couldn't be forwarded")
86-
end
87-
print_status( "#{external_address} " +
88-
"#{external_port}/#{datastore['PROTOCOL']} -> #{map_target} " +
89-
"#{internal_port}/#{datastore['PROTOCOL']} forwarded")
90-
end
91-
92-
# report NAT-PMP as being open
93-
report_service(
94-
:host => host,
95-
:port => pkt[2],
96-
:proto => 'udp',
97-
:name => 'natpmp',
98-
:state => Msf::ServiceState::Open
99-
)
100-
101-
# report the external port as being open
102-
if inside_workspace_boundary?(external_address)
103-
report_service(
104-
:host => external_address,
105-
:port => external_port,
106-
:proto => datastore['PROTOCOL'].to_s.downcase,
107-
:state => Msf::ServiceState::Open
108-
)
109-
end
110-
end
111122
end

modules/auxiliary/scanner/natpmp/natpmp_portscan.rb

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ def initialize
2323

2424
register_options(
2525
[
26-
OptString.new('PORTS', [true, "Ports to scan (e.g. 22-25,80,110-900)", "1-1000"]),
27-
OptEnum.new('PROTOCOL', [true, "Protocol to scan", 'TCP', %w(TCP UDP)]),
26+
OptString.new('PORTS', [true, "Ports to scan (e.g. 22-25,80,110-900)", "1-1000"])
2827
], self.class)
2928
end
3029

@@ -36,34 +35,24 @@ def run_host(host)
3635
)
3736
add_socket(udp_sock)
3837
peer = "#{host}:#{datastore['RPORT']}"
39-
vprint_status("#{peer} Scanning #{datastore['PROTOCOL']} ports #{datastore['PORTS']} using NATPMP")
40-
41-
# first, send a request to get the external address
42-
udp_sock.sendto(external_address_request, host, datastore['RPORT'], 0)
43-
external_address = nil
44-
while (r = udp_sock.recvfrom(12, 0.25) and r[1])
45-
(ver,op,result,epoch,external_address) = parse_external_address_response(r[0])
46-
end
38+
vprint_status("#{peer} Scanning #{protocol} ports #{datastore['PORTS']} using NATPMP")
4739

40+
external_address = get_external_address(udp_sock, host, datastore['RPORT'])
4841
if (external_address)
4942
print_good("#{peer} responded with external address of #{external_address}")
5043
else
5144
vprint_status("#{peer} didn't respond with an external address")
5245
return
5346
end
5447

55-
Rex::Socket.portspec_crack(datastore['PORTS']).each do |port|
56-
# send one request to clear the mapping if *we've* created it before
57-
clear_req = map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 0)
58-
udp_sock.sendto(clear_req, host, datastore['RPORT'], 0)
59-
while (r = udp_sock.recvfrom(16, 1.0) and r[1])
60-
end
48+
# clear all mappings
49+
map_port(udp_sock, host, datastore['RPORT'], 0, 0, Rex::Proto::NATPMP.const_get(protocol), 0)
6150

62-
# now try the real mapping
51+
Rex::Socket.portspec_crack(datastore['PORTS']).each do |port|
6352
map_req = map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 1)
6453
udp_sock.sendto(map_req, host, datastore['RPORT'], 0)
6554
while (r = udp_sock.recvfrom(16, 1.0) and r[1])
66-
handle_reply(host, external_address, r)
55+
break if handle_reply(host, external_address, r)
6756
end
6857
end
6958

@@ -94,6 +83,14 @@ def handle_reply(host, external_addr, pkt)
9483
if (int != ext)
9584
state = Msf::ServiceState::Open
9685
print_good("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with unmatched ports")
86+
if inside_workspace_boundary?(external_addr)
87+
report_service(
88+
:host => external_addr,
89+
:port => int,
90+
:proto => protocol,
91+
:state => state
92+
)
93+
end
9794
else
9895
state = Msf::ServiceState::Closed
9996
print_status("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with matched ports") if (datastore['DEBUG'])
@@ -103,21 +100,13 @@ def handle_reply(host, external_addr, pkt)
103100
print_status("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of code #{result} response") if (datastore['DEBUG'])
104101
end
105102

106-
if inside_workspace_boundary?(external_addr)
107-
report_service(
108-
:host => external_addr,
109-
:port => int,
110-
:proto => protocol,
111-
:state => state
112-
)
113-
end
114-
115103
report_service(
116104
:host => host,
117105
:port => pkt[2],
118106
:name => 'natpmp',
119107
:proto => 'udp',
120108
:state => Msf::ServiceState::Open
121109
)
110+
true
122111
end
123112
end

0 commit comments

Comments
 (0)