Skip to content

Commit ea6824c

Browse files
committed
WIP of NAT-PMP rework
1 parent 8e58efb commit ea6824c

File tree

3 files changed

+94
-59
lines changed

3 files changed

+94
-59
lines changed

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: 22 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,29 @@ def run_host(host)
4040
})
4141
add_socket(udp_sock)
4242

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
43+
# new
44+
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'])
46+
47+
if actual_ext_port
48+
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")
53+
end
54+
print_status( "#{external_address} " +
55+
"#{actual_ext_port}/#{datastore['PROTOCOL']} -> #{map_target} " +
56+
"#{datastore['INTERNAL_PORT']}/#{datastore['PROTOCOL']} forwarded")
5057

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)
58+
# report NAT-PMP as being open
59+
report_service(
60+
:host => host,
61+
:port => datastore['RPORT'],
62+
:proto => 'udp',
63+
:name => 'natpmp',
64+
:state => Msf::ServiceState::Open
65+
)
6266
end
6367
rescue ::Interrupt
6468
raise $!
@@ -69,43 +73,4 @@ def run_host(host)
6973
end
7074
end
7175

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
11176
end

modules/auxiliary/scanner/natpmp/natpmp_portscan.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,48 @@ 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+
3173
def run_host(host)
3274
begin
3375
udp_sock = Rex::Socket::Udp.create({

0 commit comments

Comments
 (0)