Network Security Implications of Host Models
This blog post is about a concept in network stacks called “Host Model” and its implications on network security.
It is important for both, the offensive and the defensive side to know the differences between the host model paradigms and to be aware of the defaults that are used in common operating systems.
In this blog post the following 2 hosts will be considered:
Host A
eth0
IPv4 address: 192.168.100.1/24
MAC: 00:0C:29:6D:57:01
eth1
IPv4 Address: 10.0.0.10/24
MAC: 00:0C:29:6D:57:10
Open Port: 443/TCP
Host B
eth0
IPv4 address: 192.168.100.5/24
MAC: 00:0C:29:6D:57:05
Default Gateway: 192.168.100.1
Host A has 2 externally accessible network interfaces, eth0 and eth1 which are configured to be in different subnets. Host B has only 1 externally accessible network interface, which is eth0. It is configured with an IP address that is located in the same subnet as eth0 on Host A.
Weak Host Model
IPv4 or IPv4 stacks that implement the weak host model would accept incoming packets even if the destination IP address of the packet is different from the IP address of the receiving network interface. Also it would allow the system to send packets from an interface, that has a different IP address than the source IP address of the packet being sent. When I first read about this, it seemed to be counterintuitive for me. The important aspect to understand is, that the receiving or sending entity in the weak host model is the host - NOT the interface. As long as the destination or source IP address of a packet is present on a host, regardless on which interface it is configured, it will be accepted.
Let's take a look at a practical example, starting with a simple TCP-SYN packet:
Before sending the TCP packet, it's necessary to provide Host B with a default gateway, pointing to 192.168.100.1.
user@host-b: $ sudo ip r add default via 192.168.100.1
Setting the gateway ensures that all packets will be sent to 192.168.100.1 / 00:0c:29:6d:57:01.
To prepare a nice target for the TCP port, a python HTTP server will be launched. It is configured to listen on TCP port 8080:
user@host-a: $ python -m SimpleHTTPServer 8080
Now let's try to scan this IP address by running nmap on Host B:
user@host-b: $ nmap -n -Pn 10.0.0.10
Starting Nmap 7.60 ( https://nmap.org ) at 2019-11-29 01:05 CET
Nmap scan report for 10.0.0.10
Host is up (0.000051s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
8080/tcp open http-proxy
Nmap done: 1 IP address (1 host up) scanned 1.46 seconds
As you can see we got a response from 10.0.0.10 although it is located on a different subnet than eth0 on Host B. IP forwarding is not necessary at this stage, since in the weak model an address belongs to the whole host rather than to a single network interface.
Strong Host Model
Stacks that implement the strong host model would stop the system from responding to incoming packets, if the destination IP address does not match the IP address of the receiving interface. Also the host would only be allowed to send packets if source IP address of the packet is equal to the address of the egress network interface.
For the next example, the environment changes a little bit: Host A is no longer running Ubuntu Server 18.10 but gets a fresh installation of HardenedBSD with the same IPv4 configuration we've seen
before. HardenedBSD implements the Strong Host Model
by default. Like in the first example we'll scan the IP address using a TCP SYN-Scan.
Host B will keep its default gateway and a python server will be started on the HardenedBSD box.
Time to start a port scan of Host A:
user@host-b: $ nmap -n -Pn 10.0.0.10
Starting Nmap 7.60 ( https://nmap.org ) at 2019-11-29 01:13 CET
Nmap scan report for 10.0.0.10
Host is up.
All 1000 scanned ports on 10.0.0.10 are filtered
Nmap done: 1 IP address (1 host up) scanned 201.27 seconds
This time the kernel of Host A drops the packet, because its destination IP address is different from the IP address of the interface on which the packet is received.
Behaviour on Layer 2
Although Strong Host Model
and Weak Host Model
relate to IPv4/IPv6 stacks, a similar mechanism exists on Layer 2.
What happens if Host B sends an ARP packet, asking for the hardware address associated with 10.0.0.100? Well that depends on the target operating systems. Let's assume, that Host A is running Ubuntu Server 18.10 with a default configuration.
Example of an arp-scan against Host A:
$ sudo arp-scan -l
Interface: eth0, datalink type: EN10MB (Ethernet)
Starting arp-scan 1.9 with 256 hosts (http://www.nta-monitor.com/tools/arp-scan/)
192.168.100.1 00:0c:29:6d:57:01 VMware, Inc.
$ sudo arp-scan 10.0.0.0/24
Interface: eth0, datalink type: EN10MB (Ethernet)
Starting arp-scan 1.9 with 256 hosts (http://www.nta-monitor.com/tools/arp-scan/)
10.0.0.10 00:0c:29:6d:57:01 VMware, Inc.
The first scan sends ARP requests to all IP addresses within the subnet of eth0 on Host B (192.168.100.0/24). As a result of this scan, the IP address 192.168.100.1 appears, which is the address of eth0 on Host A. The second scan defines a different IP range: 10.0.0.0/24. Although this network is located on a different subnet than eth0 on Host B, the scan results show an answer from IP address 10.0.0.10, which is the address of eth1 on Host A. You might notice that the MAC address next to the IP address is not the actual MAC address of eth1, but the one of eth0. This is because eth0 responds to the ARP request on behalf of eth1.
This behaviour can be controlled by setting the kernel parameter net.ipv4.conf.<all|ifname>.arp_ignore
to 1
. Please note that this parameter might not be available on every operating system and can
mainly be found on Linux.
Edge Case: Localnet
With weak host model and cross-interface ARP responses in mind, you might wonder if other hosts within the same subnet would be able to access services bound to localhost on your machine.
This is not possible by default, since packets from or to localnet
(127.0.0.0/8
) coming from an unexpected source IP address are flagged as “martian packets” dropped by default. If you would like to
log these kinds of packets, you could execute the following sysctl command on Linux-based machines:
sysctl -w net.ipv4.conf.<all|interface>.log_martians=1
However there is a kernel parameter on Linux-based operating systems that would allow the kernel to route packets destined to localhost, coming from an external source. This parameter is called
net.ipv4.conf.<all|interface>.route_localnet
and defaults to 0
.
Setting this value to 1 poses a significant security risk, since services listening on 127.0.0.1 might be accessible from the subnets of other network interfaces that are configured on that host. A
description of how to spot hosts having this setting enabled is part of the Scanning Techniques section.
Security Considerations
An initial thought might be that the weak host model could be considered as a security weakness. Actually the weak host model is the default on most modern operating systems and securing the infrastructure should take these defaults into account. Network interfaces being placed in different subnets should not be regarded as a security boundary. Sensitive management interfaces that are listening on interfaces like vboxnet0, docker0 or similar virtual interfaces wouldn't prevent attackers from accessing these services. Instead it is important to have proper firewall rules in place that would prevent unauthorised access, or to configure sensitive services to listen on localhost only. Furthermore the strong host model would break core routing functionality and is not an option for devices that act as a router.
Scanning Techniques
In order to scan your subnets to find other internal networks, different strategies can be followed. Finding other networks by performing an ARP scan is the quickest way, but might miss hosts that disabled cross-interface ARP responses.
A good start is to scan all available private network segments:
$ arp-scan -l (Scans your local subnet) $ arp-scan 192.168.0.0/16 $ arp-scan 172.16.0.0/12 $ arp-scan 10.0.0.0/8
And don't forget the IPv4 link-local range, which is automatically picked if automatic configuration methods like DHCP fail:
$ arp-scan 169.254.0.0/16
A last quick (but unlikely) example would cover the case of route_localnet
being set to 1
:
$ arp-scan 127.0.0.1
Scanning for open TCP or UDP ports on unknown other networks requires a bit more effort, especially if it shouldn't take too long. In a scenario where IP forwarding is not enabled, each host within the known subnet needs to be targeted separately. The reason for this is, that TCP or UDP packets needs to be sent directly to the MAC addresses of each host.
Let's assume again that Host A has two network interfaces, eth0 (10.0.0.10/24)
and eth1 (192.168.100.1/24)
. Our attacker host is Host B, which only has 1 interface, eth0 (192.168.100.2/24)
. Based
on earlier arp-scans (arp-scan -l) we already know that 192.168.100.1 exists, and that it belongs to our subnet.
After some ARP Scans of other private IP ranges no additional IP addresses showed up. This could either mean that no other interfaces were available or that cross-interface ARP responses were disabled.
To find out more about Host A, let's start a port scan of the IP address that's within our own subnet:
user@host-b: $ nmap -n 192.168.100.1
Starting Nmap 7.60 ( https://nmap.org ) at 2019-11-30 11:32 CET
Nmap scan report for 192.168.100.1
Host is up (0.000050s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
22/tcp open ssh
As you can see the scan revealed an open SSH port on 192.168.100.1. At this stage we could make the assumption that if SSH is listening on 192.168.100.1, it might listen on other network interfaces on that host as well, especially if SSH is configured to listen on 0.0.0.0. To speed further scans up, we will limit the port to TCP/22 and scan another subnet. But wait .. Before we do that, keep in mind that we need a route to the imaginary target network first:
user@host-b: $ ip r add 172.16.0.0/12 via 192.168.100.1 user@host-b: $ nmap -n -p22 -T5 -Pn --open 172.16.0.0/12
This scan doesn't return any hosts and open ports. From the whitebox perspective we know why: None of the interfaces was assigned an IP address within that subnet. Now let's scan another network, but before we need to add another route:
user@host-b: $ ip r add 10.0.0.0/16 via 192.168.100.1
user@host-b: $ nmap -n -p22 -T5 -Pn --open 10.0.0.0/8
Starting Nmap 7.60 ( https://nmap.org ) at 2019-11-30 11:35 CET
Nmap scan report for 10.0.0.10
Host is up (0.000051s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
22/tcp open ssh
This time we got an answer from 10.0.0.10, a network segment that we did not know before. Now we know that Host A has a second network interface, reachable via 10.0.0.10. With that knowledge we could run a full port scan against 10.0.0.10, which might reveal further open ports.
This strategy can be adapted to other hosts, but keep in mind that you have to change routes each time you change your scan target.
Of course you could also try to achieve the same by sending ICMP echo requests. However many systems tend to block ICMP, and TCP or UDP might be a better choice.
OS Defaults
When I started observing the behaviour of different operating systems, I created some virtual machines to research what the defaults are. While Linux mostly follows the weak host model and also permits cross-interface ARP responses, *BSD-based systems tend to block cross-interface ARP responses, but follow the weak host model for their IP stacks. For Windows it's a different story: Since Windows Vista, Microsoft decided to implement the strong host model by default. Did you expect that?
OS | Host Model | Toggle Host Model |
---|---|---|
Ubuntu Server 18.10 | Weak | Routing table and rules for each interface necessary |
Arch Linux | Weak | Routing table and rules for each interface necessary |
CentOS 7 | Weak | Routing table and rules for each interface necessary |
Solaris 11 | Weak | ndd -set /dev/ip ip_strict_dst_multihoming <0|1> |
ndd -set /dev/ip ip_strict_src_multihoming <0|1> | ||
FreeBSD 12.0 | Weak | sysctl -w net.inet.ip.check_interface=<0|1> |
NetBSD 8.1 | Weak | sysctl -w net.inet.ip.checkinterface=<0|1> |
HardenedBSD 12.1 | Strong | sysctl -w net.inet.ip.check_interface=<0|1> |
OpenBSD 6.5 | Weak | Reversed Path Source Validation can be set using “pf” |
Windows 7 | Strong | Netsh interface IPv4 set interface “<name>” WeakHostSend=<enabled|disabled> |
Netsh interface IPv4 set interface “<name>” WeakHostReceive=<enabled|disabled> | ||
Windows 10 | Strong | Netsh interface IPv4 set interface “<name>” WeakHostSend=<enabled|disabled> |
Netsh interface IPv4 set interface “<name>” WeakHostReceive=<enabled|disabled> |
Like with the Host Model, also the ARP behaviour can be tweaked on some operating systems. For some I couldn't find an according setting, if you know any, please let me know:
OS | Cross-Interface ARP allowed | Toggle ARP Behaviour |
---|---|---|
Ubuntu Server 18.10 | Yes | sysctl -w net.ipv4.conf.<all|interface>.arp_ignore=<0|1> |
Arch Linux | Yes | sysctl -w net.ipv4.conf.<all|interface>.arp_ignore=<0|1> |
CentOS 7 | Yes | sysctl -w net.ipv4.conf.<all|interface>.arp_ignore=<0|1> |
Solaris 11 | Nod | No setting |
FreeBSD 12.0 | No | No setting |
NetBSD 8.1 | No | No setting |
HardenedBSD 12.1 | No | No setting |
OpenBSD 6.5 | No | No setting |
Windows 7 | No | Netsh interface IPv4 set interface “<name>” WeakHostSend=<enabled|disabled> |
Netsh interface IPv4 set interface “<name>” WeakHostReceive=<enabled|disabled> | ||
Windows 10 | No | Netsh interface IPv4 set interface “<name>” WeakHostSend=<enabled|disabled> |
Netsh interface IPv4 set interface “<name>” WeakHostReceive=<enabled|disabled> |
Conclusion
The host model implementation is a core concept in network stacks of operating systems. Although documentation could be a bit better, it can be safely assumed that most Linux, and *BSD derivates are following the weak host model, while Windows Vista and newer implement the strong host model. From a security perspective it's important to keep in mind that packet filtering is still necessary even if network interfaces seem to be unreachable. For security researchers it's important to keep these principles in mind to enhance network scanning and discovery.
Cheers