It’s occasionally argued that Network Address Translation, as commonly used with domestic Internet connections, somehow protects your network from attack. The assumption seems to be that since translation of packets on their way through the router means that external packets that are addressed to “internal” host will not be forwarded.
Although it is normal to configure NATing routers to prohibit ingress to the “internal” network, other than for packets from established connections, it is not the NAT that excludes inbound packets. This is easy to see, by simply configuring a router to perform NAT but omit any further packet filtering.
The router is called wampoon
.
Its eth0
is attached to 172.17.207.0/24; in a
typical domestic configuration this would represent the ISP’s
network (and might be a point-to-point link rather than an
ethernet). Usually this would not
use RFC1918
addresses; the fact that I’m doing so here doesn’t make any
meaningful difference..
richard@wampoon:~$ ip addr show eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:15:5d:cf:42:07 brd ff:ff:ff:ff:ff:ff inet 172.17.207.76/24 brd 172.17.207.255 scope global eth0 valid_lft forever preferred_lft forever inet6 2001:470:1f09:11ed::4c/64 scope global valid_lft forever preferred_lft forever inet6 fe80::215:5dff:fecf:4207/64 scope link valid_lft forever preferred_lft forever
eth1
is attached to 172.20.211.0/24. This
represents the internal network.
richard@wampoon:~$ ip addr show eth1 3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:15:5d:cf:42:09 brd ff:ff:ff:ff:ff:ff inet 172.20.211.1/24 brd 172.20.211.255 scope global eth1 valid_lft forever preferred_lft forever inet6 fe80::215:5dff:fecf:4209/64 scope link valid_lft forever preferred_lft forever
…and here’s the routing table:
richard@wampoon:~$ ip route show default via 172.17.207.1 dev eth0 onlink 172.17.207.0/24 dev eth0 proto kernel scope link src 172.17.207.76 172.20.211.0/24 dev eth1 proto kernel scope link src 172.20.211.1
The router’s packet filtering is established as follows. There’s only one entry, which enables NAT on outbound packets.
#!/bin/sh set -e for table in INPUT OUTPUT FORWARD; do iptables -P $table ACCEPT iptables -F $table done for table in POSTROUTING PREROUTING INPUT OUTPUT; do iptables -t nat -F $table done # NAT connections from the internal network to the “outside” iptables -t nat -A POSTROUTING -j MASQUERADE -o eth0
The client behind the router, called tasp
, has
very simple network configuration. It uses the router’s “internal”
(eth1) address as its default gateway.
root@tasp:~# ip addr show eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:15:5d:cf:42:0a brd ff:ff:ff:ff:ff:ff inet 172.20.211.20/24 brd 172.20.211.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::215:5dff:fecf:420a/64 scope link valid_lft forever preferred_lft forever root@tasp:~# ip route show default via 172.20.211.1 dev eth0 169.254.0.0/16 dev eth0 scope link metric 1000 172.20.211.0/24 dev eth0 proto kernel scope link src 172.20.211.20
First, NAT is happening as expected:
root@tasp:~# ping -c2 172.17.207.1 PING 172.17.207.1 (172.17.207.1) 56(84) bytes of data. 64 bytes from 172.17.207.1: icmp_seq=1 ttl=63 time=0.668 ms 64 bytes from 172.17.207.1: icmp_seq=2 ttl=63 time=0.596 ms --- 172.17.207.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 999ms rtt min/avg/max/mdev = 0.596/0.632/0.668/0.036 ms
Here’s the untranslated packets on the internal network. Note the internal address, underlined:
root@wampoon:~# tcpdump -npieth1 icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes 21:11:00.244644 IP 172.20.211.20 > 172.17.207.1: ICMP echo request, id 18811, seq 1, length 64 21:11:00.245087 IP 172.17.207.1 > 172.20.211.20: ICMP echo reply, id 18811, seq 1, length 64 21:11:01.243867 IP 172.20.211.20 > 172.17.207.1: ICMP echo request, id 18811, seq 2, length 64 21:11:01.244313 IP 172.17.207.1 > 172.20.211.20: ICMP echo reply, id 18811, seq 2, length 64 ^C
…and here’s the NATed packets as forwarded by the router. The internal address has been replaced with an external address.
root@wampoon:~# tcpdump -npieth0 icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 21:11:00.244688 IP 172.17.207.76 > 172.17.207.1: ICMP echo request, id 18811, seq 1, length 64 21:11:00.245073 IP 172.17.207.1 > 172.17.207.76: ICMP echo reply, id 18811, seq 1, length 64 21:11:01.243918 IP 172.17.207.76 > 172.17.207.1: ICMP echo request, id 18811, seq 2, length 64 21:11:01.244297 IP 172.17.207.1 > 172.17.207.76: ICMP echo reply, id 18811, seq 2, length 64 ^C
This is all exactly what is expected from NAT.
Now, what happens if the “internal” host (tasp) is pinged from
the outside? In the demo setup, this is enabled by configuring
another VM (deodand
, 172.17.207.12) on the external
network to deliver packets for the internal network to the
router. The routing table looks like this:
richard@deodand:~$ ip route show default via 172.17.207.1 dev eth0 onlink 172.17.207.0/24 dev eth0 proto kernel scope link src 172.17.207.12 172.20.211.0/24 via 172.17.207.76 dev eth0 172.25.61.0/24 dev eth0 proto kernel scope link src 172.25.61.3
The underlined route was added for the purposes of the demo; it
means that packets from deodand
to the “internal”
network are routed via wampoon
.
The test is straightforward:
richard@deodand:~$ ping -c 2 172.20.211.20 PING 172.20.211.20 (172.20.211.20) 56(84) bytes of data. 64 bytes from 172.20.211.20: icmp_seq=1 ttl=63 time=0.793 ms 64 bytes from 172.20.211.20: icmp_seq=2 ttl=63 time=0.670 ms --- 172.20.211.20 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 0.670/0.731/0.793/0.067 ms
It works just fine. NAT has not saved us. The packets as seen by the router on the external network:
root@wampoon:~# tcpdump -npieth0 icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 21:20:27.983129 IP 172.17.207.12 > 172.20.211.20: ICMP echo request, id 21370, seq 1, length 64 21:20:27.983468 IP 172.20.211.20 > 172.17.207.12: ICMP echo reply, id 21370, seq 1, length 64 21:20:28.983255 IP 172.17.207.12 > 172.20.211.20: ICMP echo request, id 21370, seq 2, length 64 21:20:28.983418 IP 172.20.211.20 > 172.17.207.12: ICMP echo reply, id 21370, seq 2, length 64
…and on the internal network. No translation or filtering has occurred:
root@wampoon:~# tcpdump -npieth1 icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes 21:20:27.983180 IP 172.17.207.12 > 172.20.211.20: ICMP echo request, id 21370, seq 1, length 64 21:20:27.983455 IP 172.20.211.20 > 172.17.207.12: ICMP echo reply, id 21370, seq 1, length 64 21:20:28.983287 IP 172.17.207.12 > 172.20.211.20: ICMP echo request, id 21370, seq 2, length 64 21:20:28.983410 IP 172.20.211.20 > 172.17.207.12: ICMP echo reply, id 21370, seq 2, length 64 ^C
Traffic flows without any difficulty.
ICMP’s mostly harmless though. How about TCP?
richard@deodand:~$ ssh root@172.20.211.20 The authenticity of host '172.20.211.20 (172.20.211.20)' can't be established. ECDSA key fingerprint is SHA256:vr1YQG+gN8LdRoweSPMpvBTAI1bpZx8ckzHvBJKlP8o. Are you sure you want to continue connecting (yes/no)? no Host key verification failed.
Again, it works fine. Here are the the packet traces, from the outside interface:
root@wampoon:~# tcpdump -npieth1 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes 21:25:23.447581 IP 172.17.207.12.52670 > 172.20.211.20.22: Flags [S], seq 3306468235, win 29200, options [mss 1460,sackOK,TS val 735802076 ecr 0,nop,wscale 7], length 0 21:25:23.447979 IP 172.20.211.20.22 > 172.17.207.12.52670: Flags [S.], seq 2780826088, ack 3306468236, win 28960, options [mss 1460,sackOK,TS val 2216515 ecr 735802076,nop,wscale 7], length 0 21:25:23.449244 IP 172.17.207.12.52670 > 172.20.211.20.22: Flags [.], ack 1, win 229, options [nop,nop,TS val 735802076 ecr 2216515], length 0 21:25:23.449259 IP 172.17.207.12.52670 > 172.20.211.20.22: Flags [P.], seq 1:33, ack 1, win 229, options [nop,nop,TS val 735802076 ecr 2216515], length 32 21:25:23.449409 IP 172.20.211.20.22 > 172.17.207.12.52670: Flags [.], ack 33, win 227, options [nop,nop,TS val 2216516 ecr 735802076], length 0 […]
…and the inside interface:
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 21:25:23.447522 IP 172.17.207.12.52670 > 172.20.211.20.22: Flags [S], seq 3306468235, win 29200, options [mss 1460,sackOK,TS val 735802076 ecr 0,nop,wscale 7], length 0 21:25:23.447993 IP 172.20.211.20.22 > 172.17.207.12.52670: Flags [S.], seq 2780826088, ack 3306468236, win 28960, options [mss 1460,sackOK,TS val 2216515 ecr 735802076,nop,wscale 7], length 0 21:25:23.449230 IP 172.17.207.12.52670 > 172.20.211.20.22: Flags [.], ack 1, win 229, options [nop,nop,TS val 735802076 ecr 2216515], length 0 21:25:23.449230 IP 172.17.207.12.52670 > 172.20.211.20.22: Flags [P.], seq 1:33, ack 1, win 229, options [nop,nop,TS val 735802076 ecr 2216515], length 32 21:25:23.449421 IP 172.20.211.20.22 > 172.17.207.12.52670: Flags [.], ack 33, win 227, options [nop,nop,TS val 2216516 ecr 735802076], length 0 […]
The issue is easy to fix, and the reason most people don’t have a problem is that a normal router configuration will do so. In this example only a couple of commands are required:
# Forward established connections... iptables -A FORWARD -m state -j ACCEPT --state ESTABLISHED,RELATED # ...but nothing else from outside to inside iptables -A FORWARD -j REJECT -i eth0 -o eth1
With this in place, attempts to enter the network from outside fail:
richard@deodand:~$ ping -c 2 172.20.211.20 PING 172.20.211.20 (172.20.211.20) 56(84) bytes of data. From 172.17.207.76 icmp_seq=1 Destination Port Unreachable From 172.17.207.76 icmp_seq=2 Destination Port Unreachable --- 172.20.211.20 ping statistics --- 2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1009ms richard@deodand:~$ ssh root@172.20.211.20 ssh: connect to host 172.20.211.20 port 22: Connection refused
This is exactly the same configuration one would use if there were not NAT in play at all.
It’s sometimes argued that an attacker won’t know what addresses to target. That may be true when they start but in there are only about 1.7 million RFC1918 addresses to search, and we’re in an era when people routinely scan the entire IPv4 Internet, so an attacker probably won’t remain nescient for long.
The other argument sometimes advanced is that packets addressed to your internal network won’t reach your router in the first place, because none of the intermediate routers know how to get there.
If all is going well this is true. However, anyone who can control the ISP’s router, or the network between it and your router, can (in principle) inject and receive packets of their choice.
People who may be able to control your ISP’s router include:
You may trust your ISP’s staff vetting processes, and the security measures used by your ISP and their equipment vendors, and you may be a law-abiding citizen, who isn’t in some downtrodden minority, in a largely non-oppressive country. If all of these are true then you might well regard the risk here as very low. Personally I think it’s more sensible to add the two lines of router configuration than attempt to assess these risks.