Let’s talk about how to hijack HTTP traffic on your home subnet using ARP and iptables. It’s an easy and fun way to harass your friends, family, or flatmates while exploring the networking protocols.
Please don’t experiment with this outside of a subnet under your control — it’s against the law and it might be hard to get things back to their normal state.
Significant other comes home from work. SO pulls out laptop and tries to catch up on social media like every night. SO instead sees awesome personalized web page proposing marriage:
How do we accomplish this?
The key player is ARP, the “Address Resolution Protocol” responsible for associating Internet Layer addresses with Link Layer addresses. This usually means determining the MAC address corresponding to a given IP address.
ARP comes into play when you, for example, head over to a friend’s house, pull out your laptop, and try to use the wireless to surf the web. One of the first things that probably needs to happen is determining the MAC address of the gateway (probably your friend’s router), so that the Ethernet packets containing all those IP[TCP[HTTP]] requests you want to send out to the Internet know how to get to their first hop, the gateway.
Your laptop finds out the MAC address of the gateway by asking. It broadcasts an ARP request for “Who has IP address
192.168.1.1“, and the gateway broadcasts an ARP response saying “I have
192.168.1.1, and my MAC address is
xx:xx:xx:xx:xx:xx“. Your laptop, armed with the MAC address of the gateway, can then craft Ethernet packets that will go to the gateway and get routed out to the Internet.
But the gateway didn’t really have to prove who it was. It just asserted who it was, and everyone listened. Anyone else can send an ARP response claiming to have IP address
192.168.1.1. And that’s the ticket: if you can pretend to be the gateway, you can control all the packets that get routed through the gateway and the content returned to clients.
Step 1: The layout
I did this at home. The three machines involved were:
- real gateway router: IP address
192.168.1.1, MAC address
- fake gateway: a desktop called
kid-charlemagne, IP address
192.168.1.200, MAC address
- test machine getting duped: a laptop on wireless called
pixeleen, IP address
192.168.1.111, MAC address
The gateway router, like most modern routers, is bridging between the wireless and wired domains, so ARP packets get broadcast to both domains.
Step 2: Enable IPv4 forwarding
kid-charlemagne wants to be receiving packets that aren’t destined for it (eg the web traffic). Unless IP forwarding is enabled, the networking subsystem is going to ignore packets that aren’t destined for us. So step 1 is to enable IP forwarding. All that takes is a non-zero value in
root@kid-charlemagne:~# echo 1 > /proc/sys/net/ipv4/ip_forward
Step 3: Set routing rules so packets going through the gateway get routed to you
kid-charlemagne is going to act like a little NAT. For HTTP packets heading out to the Internet,
kid-charlemagne is going to rewrite the destination address in the IP packet headers to be its own IP address, so it becomes final destination for the web traffic:
For HTTP packets heading back from
kid-charlemagne to the client, it’ll rewrite the source address to be that of the original destination out on the Internet.
We can set up this routing rule with the following iptables command:
jesstess@kid-charlemagne:~$ sudo iptables -t nat -A PREROUTING \
> -p tcp --dport 80 -j NETMAP --to 192.168.1.200
iptables command has 3 components:
- When to apply a rule (
- What packets get that rule (
-p tcp --dport 80)
- The actual rule (
-t nat …
-j NETMAP --to 192.168.1.200)
-t says we’re specifying a table. The
nat table is where a lookup happens on packets that create new connections. The
nat table comes with 3 built-in chains:
POSTROUTING. We want to add a rule in the
PREROUTING chain, which will alter packets right as they come in, before routing rules have been applied.
PREROUTING rule is going to apply to TCP packets destined for port 80 (
-p tcp --dport 80), aka HTTP traffic. For packets that match this filter, jump (
-j) to the following action:
If we receive a packet heading for some destination, rewrite the destination in the IP header to be
NETMAP --to 192.168.1.200). Have the
nat table keep a mapping between the original destination and rewritten destination. When a packet is returning through us to its source, rewrite the source in the IP header to be the original destination.
In summary: “If you’re a TCP packet destined for port 80 (HTTP traffic), actually make my address,
192.168.1.200, the destination, NATting both ways so this is transparent to the source.”
One last thing:
The networking subsystem will not allow you to ARP for a random IP address on an interface — it has to be an IP address actually assigned to that interface, or you’ll get a
bind error along the lines of
"Cannot assign requested address". We can handle this by adding an ip entry on the interface that is going to send packets to
pixeleen, the test client.
kid-charlemagne is wired, so it’ll be
jesstess@kid-charlemagne:~$ sudo ip addr add 192.168.1.1/24 dev eth0
We can check our work by listing all our interfaces’ addresses and noting that we now have two IP addresses for
eth0, the original IP address
192.168.1.200, and the gateway address
jesstess@kid-charlemagne:~$ ip addr
3: eth0: mtu 1500 qdisc noqueue state UNKNOWN
link/ether 00:30:1b:47:f2:74 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.200/24 brd 192.168.1.255 scope global eth0
inet 192.168.1.1/24 scope global secondary eth0
inet6 fe80::230:1bff:fe47:f274/64 scope link
valid_lft forever preferred_lft forever
Step 4: Set yourself up to respond to HTTP requests
kid-charlemagne happens to have Apache set up. You could run any minimalist web server that would, given a request for an arbitrary resource, do something interesting.
Step 5: Test pretending to be the gateway
At this point,
kid-charlemagne is ready to pretend to be the gateway. The trouble is convincing
pixeleen that the MAC address for the gateway has changed, to that of
kid-charlemagne. We can do this by sending a Gratuitous ARP, which is basically a packet that says “I know nobody asked, but I have the MAC address for
192.168.1.1”. Machines that hear that Gratuitous ARP will replace an existing mapping from
192.168.1.1 to a MAC address in their ARP caches with the mapping advertised in that Gratuitous ARP.
We can look at the ARP cache on
pixeleen before and after sending the Gratuitous ARP to verify that the Gratuitious ARP is working.
pixeleen’s ARP cache before the Gratuitous ARP:
jesstess@pixleen$ arp -a
? (192.168.1.1) at 68:7f:74:9a:f4:ca on en1 ifscope [ethernet]
? (192.168.1.200) at 0:30:1b:47:f2:74 on en1 ifscope [ethernet]
68:7f:74:9a:f4:ca is the MAC address of the real gateway router.
There are lots of command line utilities and bindings in various programming language that make it easy to issue ARP packets. I used the arping tool:
jesstess@kid-charlemagne:~$ sudo arping -c 3 -A -I eth0 192.168.1.1
We’ll send a Gratuitous ARP reply (
-A), three times (
-c -3), on the
eth0 interface (
-l eth0) for IP address
As soon as we generate the Gratuitous ARPs, if we check
pixeleen’s ARP cache:
jesstess@pixeleen$ arp -a
? (192.168.1.1) at 0:30:1b:47:f2:74 on en1 ifscope [ethernet]
? (192.168.1.200) at 0:30:1b:47:f2:74 on en1 ifscope [ethernet]
pixeleen now thinks the MAC address for IP address
0:30:1b:47:f2:74, which is
If I try to browse the web on
pixeleen, I am served the resource matching the rules in
kid-charlemagne’s web server.
We can watch this whole exchange in Wireshark:
First, the Gratuitous ARPs generated by
The only traffic getting its headers rewritten so that
kid-charlemagne is the destination is HTTP traffic: TCP traffic on port 80. That means all of the non-HTTP traffic associated with viewing a web page still happens as normal. In particular, when
kid-charlemagne gets the DNS resolution requests for
lycos.com, the test site I visited, it will follow its routing rules and forward them to the real router, which will send them out to the Internet:
The HTTP traffic gets served by
Note that the HTTP request has a source IP of
pixeleen, and a destination IP of
dig -x 18.104.22.168 +short tells us is
search-core1.bo3.lycos.com. The HTTP response has a source IP of
22.214.171.124 and a destination IP of
192.168.1.111. The fact that
kid-charlemagne has rerouted and served the request is totally transparent to the client at the IP layer.
Step 6: Deploy against friends and family
I trust you to get creative with this.
Step 7: Reset everything to the normal state
To get the normal gateway back in control, delete the IP address from the interface on
kid-charlemagne and delete the
iptables routing rule:
jesstess@kid-charlemagne:~$ sudo ip addr delete 192.168.1.1/24 dev eth0
jesstess@kid-charlemagne:~$ sudo iptables -t nat -D PREROUTING -p tcp --dport 80 -j NETMAP --to 192.168.1.200
To get the client machines to believe the router is the real gateway, you might have to clear the gateway entry from the ARP cache with
arp -d 192.168.1.1, or bring your interfaces down and back up. I can verify that my TiVo corrected itself quickly without any intervention, but I won’t make any promises about your networked devices.
That was a lot of explanatory text, but the steps required to hijack the HTTP traffic on your home subnet can be boiled down to:
- enabled IP forwarding:
echo 1 > /proc/sys/net/ipv4/ip_forward
- set your routing rule:
iptables -t nat -A PREROUTING -p tcp --dport 80 -j NETMAP --to 192.168.1.200
- add the gateway IP address to the appropriate interface:
ip addr add 192.168.1.1/24 dev eth0
- ARP for the gateway MAC address:
arping -c 3 -A -I eth0 192.168.1.1
substituting the appropriate IP address and interface information and tearing down when you’re done.
And that’s all there is to it!
This has been tested as working in a few environments, but it might not work in yours. I’d love to hear the details on if this works, works with modifications, or doesn’t work (because the devices are being too clever about Gratuitous ARPs, or otherwise) in the comments.
–> Huge thank-you to fellow experimenter adamf. <—