Skip to content

Commit 07f2d4d

Browse files
committed
Further improvements to NAT-PMP. Faster, more useful, less not useful
1 parent ea6824c commit 07f2d4d

File tree

3 files changed

+85
-81
lines changed

3 files changed

+85
-81
lines changed

lib/msf/core/auxiliary/natpmp.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,13 @@ def initialize(info = {})
2323
self.class
2424
)
2525
end
26+
27+
def lifetime
28+
@lifetime ||= datastore['LIFETIME']
29+
end
30+
31+
def protocol
32+
@protocol ||= datastore['PROTOCOL']
33+
end
2634
end
2735
end

modules/auxiliary/admin/natpmp/natpmp_map.rb

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,50 @@ 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]),
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']),
27+
OptInt.new('LIFETIME', [true, "Time in ms to keep this port forwarded (set to 0 to destroy a mapping)", 3600000]),
2828
OptEnum.new('PROTOCOL', [true, "Protocol to forward", 'TCP', %w(TCP UDP)]),
2929
],
3030
self.class
3131
)
3232
end
3333

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

@@ -40,22 +75,35 @@ def run_host(host)
4075
})
4176
add_socket(udp_sock)
4277

43-
# new
4478
external_address = get_external_address(udp_sock, host, datastore['RPORT']) || host
45-
actual_ext_port = map_port(udp_sock, host, datastore['RPORT'], datastore['INTERNAL_PORT'], datastore['EXTERNAL_PORT'], Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), datastore['LIFETIME'])
4679

47-
if actual_ext_port
80+
@external_ports.each_index do |i|
81+
external_port = @external_ports[i]
82+
internal_port = @internal_ports[i]
83+
84+
actual_ext_port = map_port(udp_sock, host, datastore['RPORT'], internal_port, external_port, Rex::Proto::NATPMP.const_get(protocol), lifetime)
4885
map_target = Rex::Socket.source_address(host)
49-
if (datastore['EXTERNAL_PORT'] != actual_ext_port)
50-
print_status( "#{external_address} " +
51-
"#{datastore['EXTERNAL_PORT']}/#{datastore['PROTOCOL']} -> #{map_target} " +
52-
"#{datastore['INTERNAL_PORT']}/#{datastore['PROTOCOL']} couldn't be forwarded")
86+
requested_forwarding = "#{external_address}:#{external_port}/#{protocol}" +
87+
" -> " +
88+
"#{map_target}:#{internal_port}/#{protocol}"
89+
if actual_ext_port
90+
map_target = Rex::Socket.source_address(host)
91+
actual_forwarding = "#{external_address}:#{actual_ext_port}/#{protocol}" +
92+
" -> " +
93+
"#{map_target}:#{internal_port}/#{protocol}"
94+
if external_port == 0
95+
print_good("#{actual_forwarding} forwarded")
96+
else
97+
if (external_port != 0 && external_port != actual_ext_port)
98+
print_good("#{requested_forwarding} could not be forwarded, but #{actual_forwarding} could")
99+
else
100+
print_good("#{requested_forwarding} forwarded")
101+
end
102+
end
103+
else
104+
print_error("#{requested_forwarding} could not be forwarded")
53105
end
54-
print_status( "#{external_address} " +
55-
"#{actual_ext_port}/#{datastore['PROTOCOL']} -> #{map_target} " +
56-
"#{datastore['INTERNAL_PORT']}/#{datastore['PROTOCOL']} forwarded")
57106

58-
# report NAT-PMP as being open
59107
report_service(
60108
:host => host,
61109
:port => datastore['RPORT'],

modules/auxiliary/scanner/natpmp/natpmp_portscan.rb

Lines changed: 15 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -28,48 +28,6 @@ def initialize
2828
], self.class)
2929
end
3030

31-
def run_host(host)
32-
begin
33-
34-
udp_sock = Rex::Socket::Udp.create({
35-
'LocalHost' => datastore['CHOST'] || nil,
36-
'Context' => {'Msf' => framework, 'MsfExploit' => self}
37-
})
38-
add_socket(udp_sock)
39-
40-
# new
41-
external_address = get_external_address(udp_sock, host, datastore['RPORT']) || host
42-
actual_ext_port = map_port(udp_sock, host, datastore['RPORT'], datastore['INTERNAL_PORT'], datastore['EXTERNAL_PORT'], Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), datastore['LIFETIME'])
43-
44-
if actual_ext_port
45-
map_target = Rex::Socket.source_address(host)
46-
if (datastore['EXTERNAL_PORT'] != actual_ext_port)
47-
print_status( "#{external_address} " +
48-
"#{datastore['EXTERNAL_PORT']}/#{datastore['PROTOCOL']} -> #{map_target} " +
49-
"#{datastore['INTERNAL_PORT']}/#{datastore['PROTOCOL']} couldn't be forwarded")
50-
end
51-
print_status( "#{external_address} " +
52-
"#{actual_ext_port}/#{datastore['PROTOCOL']} -> #{map_target} " +
53-
"#{datastore['INTERNAL_PORT']}/#{datastore['PROTOCOL']} forwarded")
54-
55-
# report NAT-PMP as being open
56-
report_service(
57-
:host => host,
58-
:port => datastore['RPORT'],
59-
:proto => 'udp',
60-
:name => 'natpmp',
61-
:state => Msf::ServiceState::Open
62-
)
63-
end
64-
rescue ::Interrupt
65-
raise $!
66-
rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused
67-
nil
68-
rescue ::Exception => e
69-
print_error("Unknown error: #{e.class} #{e.backtrace}")
70-
end
71-
end
72-
7331
def run_host(host)
7432
begin
7533
udp_sock = Rex::Socket::Udp.create({
@@ -78,34 +36,24 @@ def run_host(host)
7836
)
7937
add_socket(udp_sock)
8038
peer = "#{host}:#{datastore['RPORT']}"
81-
vprint_status("#{peer} Scanning #{datastore['PROTOCOL']} ports #{datastore['PORTS']} using NATPMP")
82-
83-
# first, send a request to get the external address
84-
udp_sock.sendto(external_address_request, host, datastore['RPORT'], 0)
85-
external_address = nil
86-
while (r = udp_sock.recvfrom(12, 0.25) and r[1])
87-
(ver,op,result,epoch,external_address) = parse_external_address_response(r[0])
88-
end
39+
vprint_status("#{peer} Scanning #{protocol} ports #{datastore['PORTS']} using NATPMP")
8940

41+
external_address = get_external_address(udp_sock, host, datastore['RPORT'])
9042
if (external_address)
9143
print_good("#{peer} responded with external address of #{external_address}")
9244
else
9345
vprint_status("#{peer} didn't respond with an external address")
9446
return
9547
end
9648

97-
Rex::Socket.portspec_crack(datastore['PORTS']).each do |port|
98-
# send one request to clear the mapping if *we've* created it before
99-
clear_req = map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 0)
100-
udp_sock.sendto(clear_req, host, datastore['RPORT'], 0)
101-
while (r = udp_sock.recvfrom(16, 1.0) and r[1])
102-
end
49+
# clear all mappings
50+
map_port(udp_sock, host, datastore['RPORT'], 0, 0, Rex::Proto::NATPMP.const_get(protocol), lifetime)
10351

104-
# now try the real mapping
52+
Rex::Socket.portspec_crack(datastore['PORTS']).each do |port|
10553
map_req = map_port_request(port, port, Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), 1)
10654
udp_sock.sendto(map_req, host, datastore['RPORT'], 0)
10755
while (r = udp_sock.recvfrom(16, 1.0) and r[1])
108-
handle_reply(host, external_address, r)
56+
break if handle_reply(host, external_address, r)
10957
end
11058
end
11159

@@ -136,6 +84,14 @@ def handle_reply(host, external_addr, pkt)
13684
if (int != ext)
13785
state = Msf::ServiceState::Open
13886
print_good("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with unmatched ports")
87+
if inside_workspace_boundary?(external_addr)
88+
report_service(
89+
:host => external_addr,
90+
:port => int,
91+
:proto => protocol,
92+
:state => state
93+
)
94+
end
13995
else
14096
state = Msf::ServiceState::Closed
14197
print_status("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of successful mapping with matched ports") if (datastore['DEBUG'])
@@ -145,21 +101,13 @@ def handle_reply(host, external_addr, pkt)
145101
print_status("#{peer} #{external_addr} - #{int}/#{protocol} #{state} because of code #{result} response") if (datastore['DEBUG'])
146102
end
147103

148-
if inside_workspace_boundary?(external_addr)
149-
report_service(
150-
:host => external_addr,
151-
:port => int,
152-
:proto => protocol,
153-
:state => state
154-
)
155-
end
156-
157104
report_service(
158105
:host => host,
159106
:port => pkt[2],
160107
:name => 'natpmp',
161108
:proto => 'udp',
162109
:state => Msf::ServiceState::Open
163110
)
111+
true
164112
end
165113
end

0 commit comments

Comments
 (0)