Layer 2 attacks - ARP poisoning

What is ARP?

Address Resolution Protocol (ARP) is a functionality often taken for granted that is built on trust, has no authentication, no state, and no memory of who told it anything. It helps connect the dots between Layer 2 and Layer 3 in the network, but as a naive helper it can be exploited by attackers.

The original ARP, RFC 826, has its own way of beginning to explain how it works.

"The world is a jungle in general, and the networking game contributes many animals." - RFC 826

But in more technical words, it's a protocol made to temporarily store mappings between two protocols whose address spaces are differently sized in bits, namely the 48-bit Ethernet (MAC) addresses and 32-bit IPv4 addresses.

So where does this come in handy? It's incredibly helpful when a host knows the IP address of its target but not the MAC address.
This is how it works:

  1. Requester checks their ARP cache for the mapping
  2. Requester broadcasts an ARP request
  3. Network is flooded by the ARP request and reaches all hosts
  4. Target caches Requester's IP-to-MAC mapping
  5. Target unicasts ARP reply back to Requester
  6. Requester caches Target's IP-to-MAC mapping
  7. Communication proceeds normally until the cache entry dies

ARP cache entries do not last more than a few minutes, and this cache table only exists on end hosts.

This is what an ARP interaction looks like in Wireshark:

No.  Time         Source             Destination        Protocol  Length  Info
 28  3.238293717  aa:aa:aa:aa:aa:aa  Broadcast          ARP           60  Who has 192.168.0.20? Tell 192.168.0.10
  -  -            -                  -                  -              -  -
 33  3.618371892  bb:bb:bb:bb:bb:bb  aa:aa:aa:aa:aa:aa  ARP           60  192.168.0.20 is at bb:bb:bb:bb:bb:bb

You can think of ARP as someone, let's call them Alice, shouts into a crowd of people "Who's Bob?" and Bob walks up to Alice and tells her "Bob is here". Everyone else ignores it.

ARP Poisoning

Now imagine if a third person, Mallory, is in the same crowd of people. She also walks up to Alice after Bob to tell her "Bob is actually here". Alice will be grateful and update her contact list, and Mallory will receive all of Bob's traffic and needs to forward it between Alice and Bob to maintain the facade.

ARP is gullible

The goal of poisoning, or spoofing, the ARP message is to convince a host in the network to link the attacker's MAC address with another host's IPv4 address. Traffic meant for the other host will have the right IPv4 address, but since it's on an internal network with switches, it doesn't matter because the switch only cares about the MAC address and routes it to the attacker. The attacker knows the correct MAC address of the other host, and forwards the traffic to it and back, but only after reading through all the contents.

There has to be built-in mitigation, right?

Reading up on exploitation concepts often makes me think along the lines of "this is too simple to work so why even try" or "surely someone has fixed this and implemented the fix globally so that everyone is safe". And although that might be the case for a lot of concepts, the mitigation and hardening steps are often waiting unfinished after a network technician moves on after everything "just works". And it's worth getting to know a few mitigation techniques to know what might be guarding the gates. At least for troubleshooting when a VM test lab inevitably runs into who-knows what kinds of problems.

ARP works on a last message wins system to overwrite the cache, so to ensure that Mallory actually wins the entry she could use Gratuitous ARP messages. Gratuitous ARP means sending ARP replies containing "Bob is here" without ever being asked who Bob is in the first place. ARP does not maintain state, so a host will not question the statement and will happily overwrite their ARP cache. ARP spoofing tools will send these uncalled-for messages every few seconds to maintain a foothold in the target's ARP cache.

A simple mitigation technique is to not accept ARP replies containing IP addresses not already in the cache, but this can be circumvented by waiting for valid traffic and overwriting those entries instead.

Dynamic ARP Inspection (DAI) with the help of DHCP snooping is a better fix enforced on the switch interfaces instead of on the end hosts. DAI keeps a list of known IP-to-MAC address mappings and will drop packets disagreeing with it. DHCP snooping is a trust configuration added on a switch port to specify where trusted DHCP offers come from. Everything else gets dropped. Setting static addresses, which is often recommended in OT for consistency, would break this.

Setting permanent, static ARP entries is possible, but cannot practically be done at scale due to the headache of a management task it introduces.

Another method is using authentication on the network itself (802.1x) although this is operationally heavy, and not really a task a busy industrial site wants to manage on all their small internal networks.

So this is good news for an attacker, especially for internal OT networks. You don't want to use DAI because you'd rather have static IP addresses, but you also don't want to manage a lot of static IP addresses, meaning a lot of smaller, unprotected networks. And authentication adds another layer of inconvenience.

I work mainly with OT systems at my job, and these mitigations are seldom in place due to the need for availability in a system, assumed appropriate segmentation, and the convenience of dumb devices such as unmanaged switches, PLCs, and RTUs. The most interesting thing for me is the possibilities this opens up for snooping on traffic where it's not practical or possible to place a physical network tap in series with the equipment you want to listen to. Or to test if an attacker can listen in if they are simply a part of the network by getting in remotely.

Practical Lab: Setting up a Man-in-The-Middle

Let's set up a network of two hosts, Alice and Bob, where one sends the other sensitive credentials with an unencrypted protocol to simulate authentication (more common than you'd think). A third host, Mallory, will be connected to the same switch and try to hijack the traffic between the other hosts.

The computers in this setup are Virtual Machines (VMs) run from the same VirtualBox instance. VirtualBox allows you to create a host-only adapter (virtual switch) where the VMs can communicate with each other without exposing them to your actual Internet-facing networks. If you're cloning a VM to save time on setup, remember to generate new MAC addresses for all interfaces in the cloning settings.

Each VM is a Debian instance with Python3 to run the scripts below, and the attacker in addition has dsniff (for arpspoof) and wireshark.

Simulating traffic

Alice will periodically send credentials in cleartext to Bob. You can imagine Alice as a management device sending authenticated orders to a downstream actuator device.

The following script alice.py will send random credentials to a specified IPv4 address on port 12345.

import socket, time, random

SECRETS = [
    "user=admin password=admin",
    "session_token=eyJhbGc.payload.sig",
    "api_key=sk_live_4eC3fKqLikyDocjtT1zdp7dc",
]
TARGET = ("192.168.0.20", 12345)

while True:
    try:
        s = socket.socket()
        s.settimeout(3)
        s.connect(TARGET)
        msg = random.choice(SECRETS)
        s.send(msg.encode())
        s.close()
        print(f"sent: {msg}")
    except Exception:
        print(".", end="", flush=True)
    time.sleep(5)

Bob is the downstream device and will gladly do whatever it's told if the right credentials are sent with the message.

The following script bob.py will listen for incoming messages on port 12345.

import socket, time

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", 12345))
s.listen(5)
print("listening on :12345")

while True:
    try:
        conn, addr = s.accept()
        data = conn.recv(1024)
        print(f"recv from {addr[0]}: {data.decode()}")
        conn.close()
    except Exception:
        print(".", end="", flush=True)
        time.sleep(2)

This is how that traffic looks before poisoning, with Alice and Bob talking directly:

Alice and Bob normal communication

Setting up the attacker

Before setting up the attacker, check out the ARP cache on any of the other hosts using ip neigh show to first see that it's correct.

On Alice, before spoofing starts, the neighbor table has the expected mapping:

Alice ARP cache before poisoning

Okay, now let's focus on the attacker VM, Mallory. Enable IP forwarding to allow Mallory to route traffic.

sudo sysctl net.ipv4.ip_forward=1

I will use arpspoof from the dsniff toolkit.

sudo arpspoof -i enp0s3 -t 192.168.0.10 192.168.0.20

Then open a second terminal and poison the reverse direction too, so the traffic can keep flowing both ways through Mallory.

sudo arpspoof -i enp0s3 -t 192.168.0.20 192.168.0.10

This is what the spoofing loop looks like when it starts replying continuously:

Starting arpspoof from dsniff

This should start working pretty fast. If it doesn't, check if the attacker is able to route traffic or the switch or end hosts have some kind of mitigation in place.

Confirm it's working

Alice's neighbor table should show an entry containing Bob's IP address with Mallory's MAC address.

ip neigh show

Alice ARP cache after poisoning

PERFECT! It works!

Notice how Bob's IPv4 now maps to Mallory's MAC. Mallory's IP address is also in the neighbour table, but in a red team operation one would not want to let the other hosts know the attacker's address.

Mallory should now be able to see the traffic going from Alice to Bob.

Now run Wireshark on Mallory with the filter tcp.port == 12345.

sudo wireshark

From Alice's perspective, packets still target Bob's IP at Layer 3:

Alice to Bob flow seen on Mallory

The selected packet in the list shows Bob's IP, the yellow-highlighted shows Mallory's MAC, and the data reveals the credentials!

The next packet reroutes to Bob's MAC address (highlighted in yellow):

Layer 2 destination rewritten to Mallory MAC

Amongst us Middlemen

Now it's time to sit back, enjoy a coffee, and listen in on sensitive data being transferred right to you like a KGB officer in someone's attic.

There are of course some very obvious caveats about this topic for actual usage. Like, you need access to the internal network you're wiretapping, and if you do, why not just connect a physical tap instead? ARP poisoning is not a big deadly attack on its own, but is rather used as a step for other attacks, like session hijacking or credential relay attacks.

But as a pentester who's seen a few internal industrial networks I can confidently say that a silly amount of critical infrastructure would rather use FTP and HTTP, which send credentials in cleartext, than their encrypted counterparts, SSH and HTTPS, because it just came like that or is easy. Sometimes hooking up Wireshark and waiting for cleartext credentials is enough for a critical, or at least a high-severity, finding.

People often say you should cut out the middleman, but I'd like to stay a little while, thanks.

Edge-y Cases

A few honest questions that come up the moment you try this on something other than a lab.

What if the traffic is encrypted?
You still see the conversation pattern, who talks to whom, when, and how much, but the payload is just ciphertext.

What if the switch has DAI configured?
Your spoofed replies get dropped at the port and aggressive spoofing can make the port go into err-disable mode. You'll also show up loudly in logs.

What about static or sticky ARP entries on the hosts?
The cache is locked, so the "Bob is here" updates from Mallory just get ignored.

How do I keep Mallory's IP out of the other hosts' neighbour tables?
Don't let Mallory introduce herself. Drop inbound ARP requests and ICMP echoes with iptables/nftables, and don't source any traffic from her real IP while the spoofing is running.

Both Bob and Mallory answer the ARP request, who wins?
Last reply wins on basically every common stack, so as long as Mallory keeps spraying Gratuitous ARP every few seconds she keeps the entry.

Does this work across VLANs or subnets?
No. ARP is link-local, so you only reach hosts in the same broadcast domain.

References