Testers wanted for krb5 / gssftpd graylisting changes

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi.

I got tired of people running FTP password attacks on my machine from
China, Korea, Thailand, etc. so I came up with the following change:  the
FTP server remembers when a single session (connection) that had 3 failed
logins, and graylists that address for 60 seconds (configurable timeout,
actually).  If the user tries to reconnect again before that that
timeout expires,
the timeout gets restarted as another 120 seconds, etc. making the timeout
longer and longer until it hits some maximum (such as 2 weeks).

This at a minimum makes it a significantly more time-consuming attack on
a machine (without it, I've seen 30 connections coming into my server
trying 90 passwords per second)...

The changes, since they use an external database, also handles having
multiple simultaneous connections coming in parallel... and quickly
scales up the graylist interval.

I've attached the diffs to apply to the .spec file and in the to put into
the SOURCES directory.  I.e.

* do an "rpm -i" of the .src RPM
* apply the diffs in the SPECS directory
* save the .patch file into the SOURCES directory
* do a normal build with "rpmbuild -bb"
* do an "rpm -U" of the new RPM binaries to install the patched package

That's it.

Please let me know what your experiences are.

Thanks,

-Philip

*** krb5.spec	2005-06-29 20:28:32.000000000 -0600
--- krb5-tweaked.spec	2006-06-11 12:25:31.000000000 -0600
***************
*** 7,13 ****
  Summary: The Kerberos network authentication system.
  Name: krb5
  Version: 1.3.6
! Release: 7
  # Maybe we should explode from the now-available-to-everybody tarball instead?
  # http://web.mit.edu/kerberos/www/dist/krb5/1.3/krb5-1.3.5.tar
  Source0: krb5-%{version}.tar.gz
--- 7,13 ----
  Summary: The Kerberos network authentication system.
  Name: krb5
  Version: 1.3.6
! Release: 8
  # Maybe we should explode from the now-available-to-everybody tarball instead?
  # http://web.mit.edu/kerberos/www/dist/krb5/1.3/krb5-1.3.5.tar
  Source0: krb5-%{version}.tar.gz
***************
*** 66,71 ****
--- 66,73 ----
  Patch38: krb5-1.4-ncurses.patch
  Patch39: krb5-1.4.1-api.patch
  Patch40: krb5-1.4.1-telnet-environ.patch
+ Patch99: gssftpd-graylist-db4.patch
+ 
  License: MIT, freely distributable.
  URL: http://web.mit.edu/kerberos/www/
  Group: System Environment/Libraries
***************
*** 829,834 ****
--- 831,838 ----
  %patch38 -p1 -b .ncurses
  %patch39 -p1 -b .api
  %patch40 -p1 -b .telnet-environ
+ %patch99 -p0 -b .graylist
+ 
  cp src/krb524/README README.krb524
  find . -type f -name "*.info-dir" -exec rm -fv "{}" ";"
  gzip doc/*.ps
***************
*** 847,857 ****
--- 851,863 ----
  %ifarch %{ix86} s390 ppc sparc
  DEFINES="-D_FILE_OFFSET_BITS=64" ; export DEFINES
  %endif
+ DEFINES="$DEFINES -DGRAYLIST"
  CFLAGS="`echo $RPM_OPT_FLAGS $ARCH_OPT_FLAGS $DEFINES $INCLUDES -fPIC`"
  %configure \
  	CC=%{__cc} \
  	CFLAGS="$CFLAGS" \
  	LDFLAGS="-pie" \
+ 	FTPD_LIBS='$(KDB5_LIBS)' \
  	CPPFLAGS="$DEFINES $INCLUDES" \
  	--enable-shared --enable-static \
  	--bindir=%{krb5prefix}/bin \
--- src/appl/gssftp/ftpd/ftpd.c.graylist	2006-06-07 20:13:07.000000000 -0600
+++ src/appl/gssftp/ftpd/ftpd.c	2006-06-07 20:13:08.000000000 -0600
@@ -101,6 +101,10 @@
 #endif
 #include "pathnames.h"
 #include <libpty.h>
+#ifdef GRAYLIST
+#include <db.h>
+#include <limits.h>
+#endif
 
 #ifdef NEED_SETENV
 extern int setenv(char *, char *, int);
@@ -222,6 +226,32 @@
 char	rhost_addra[16];
 char	*rhost_sane;
 
+#ifdef GRAYLIST
+const char *gl_path= "/etc/ftpgraylist.db";
+int gl_initial = 0;
+int gl_scale = 0;
+int gl_max = 0;
+
+typedef struct {
+	time_t		start;
+	unsigned	shutout;
+} graylist_t;
+
+#define RET_NOTFOUND	RET_SPECIAL
+
+#define GL_INIT_MIN	5
+#define GL_INIT_MAX	7200
+
+#define GL_SCALE_MIN	2
+#define GL_SCALE_MAX	100
+#define GL_SCALE_DFLT	2
+
+#define GL_CAP_MIN	300
+#define GL_CAP_MAX	(365 * 24 * 3600)
+
+static unsigned gl_connect(struct in_addr, int);
+#endif
+
 /* Defines for authlevel */
 #define AUTHLEVEL_NONE		0
 #define AUTHLEVEL_AUTHENTICATE	1
@@ -277,6 +307,18 @@
 }
 #endif
 
+#ifdef KRB5_KRB4_COMPAT
+#define KRB5_OPTIONS "s:"
+#else
+#define KRB5_OPTIONS ""
+#endif
+
+#ifdef GRAYLIST
+#define GL_OPTIONS "G:g:S:"
+#else
+#define GL_OPTIONS
+#endif
+
 int stripdomain = 1;
 int maxhostlen = 0;
 int always_ip = 0;
@@ -290,11 +332,11 @@
 	int addrlen, c, on = 1, tos, port = -1;
 	extern char *optarg;
 	extern int optopt;
-#ifdef KRB5_KRB4_COMPAT
-	char *option_string = "AaCcdlp:r:s:T:t:U:u:vw:";
-#else /* !KRB5_KRB4_COMPAT */
-	char *option_string = "AaCcdlp:r:T:t:U:u:vw:";
-#endif /* KRB5_KRB4_COMPAT */
+	char *option_string = "AaCcdlp:r:T:t:U:u:vw:" KRB5_OPTIONS
+		GL_OPTIONS;
+#ifdef GRAYLIST
+	unsigned expire;
+#endif
 	ftpusers = _PATH_FTPUSERS_DEFAULT;
 
 #ifdef KRB5_KRB4_COMPAT
@@ -420,6 +462,29 @@
 			}
 			break;
 		}
+#ifdef GRAYLIST
+		case 'G':
+			gl_max = atoi(optarg);
+			if (gl_max < GL_CAP_MIN || gl_max > GL_CAP_MAX) {
+				fprintf(stderr, "ftpd: bad arg to -G\n");
+				exit(1);
+			}
+			break;
+		case 'g':
+			gl_initial = atoi(optarg);
+			if (gl_initial < GL_INIT_MIN || gl_initial > GL_INIT_MAX) {
+				fprintf(stderr, "ftpd: bad arg to -g\n");
+				exit(1);
+			}
+			break;
+		case 'S':
+			gl_scale = atoi(optarg);
+			if (gl_scale < GL_SCALE_MIN || gl_scale > GL_SCALE_MAX) {
+				fprintf(stderr, "ftpd: bad arg to -S\n");
+				exit(1);
+			}
+			break;
+#endif
 		default:
 			fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
 			     (char)optopt);
@@ -427,6 +492,21 @@
 		}
 	}
 
+#ifdef GRAYLIST
+	if (!gl_scale && gl_initial)
+		gl_scale = GL_SCALE_DFLT;
+	if (gl_max && !gl_initial) {
+		fprintf(stderr, "ftpd: -G must be used with -g\n");
+		exit(1);
+	} else if (gl_initial && !gl_max) {
+		gl_max = gl_initial * (gl_scale * gl_scale * gl_scale *
+			 gl_scale * gl_scale);
+	} else if (gl_initial > gl_max) {
+		fprintf(stderr, "ftpd: -g arg must be less than -G arg\n");
+		exit(1);
+	}
+#endif
+
 	if (port != -1) {
 		struct sockaddr_in sin4;
 		int s, ns, sz;
@@ -538,6 +618,15 @@
 	stru = STRU_F;
 	mode = MODE_S;
 	tmpline[0] = '\0';
+
+#ifdef GRAYLIST
+	expire = gl_connect(his_addr.sin_addr, 1);
+	if (expire != 0) {
+		reply(530, "Client graylisted for %u seconds.", expire);
+		dologout(0);
+	}
+#endif
+
 	(void) gethostname(hostname, sizeof (hostname));
 	reply(220, "%s FTP server (%s) ready.", hostname, version);
 	(void) setjmp(errcatch);
@@ -909,6 +998,212 @@
 	guest = 0;
 }
 
+#ifdef GRAYLIST
+static void
+gl_badlogin(addr)
+struct in_addr addr;
+{
+	DB *db;
+	DBT key, content;
+	graylist_t rec;
+	time_t now;
+	int status;
+
+	/* feature is disabled */
+	if (!gl_initial)
+		return;
+
+	syslog(LOG_DEBUG, "graylist: badlogin %s", inet_ntoa(addr));
+
+	db = dbopen(gl_path, O_CREAT | O_RDWR, 0600, DB_HASH, NULL);
+	if (!db) {
+		syslog(LOG_ERR, "graylist: can't open database (errno %d)",
+		       errno);
+		return;
+	}
+
+	key.data = &addr;
+	key.size = sizeof(addr);
+	status = db->get(db, &key, &content, 0);
+
+	time(&now);
+
+	switch (status) {
+	case RET_SUCCESS:
+		memcpy(&rec, content.data, content.size);
+		/* if the graylist hasn't expired, then increment it. */
+		if (now < rec.start + gl_max) {
+			rec.shutout *= gl_scale;
+			if (rec.shutout > gl_max)
+				rec.shutout = gl_max;
+			break;
+		}
+		/* FALLTHRU */
+	case RET_NOTFOUND:
+		rec.shutout = gl_initial;
+		break;
+	case RET_ERROR:
+		/* for now, failure isn't bad because we're exiting
+		 * anyway... but we might want to fail more dramatically
+		 * so that the system administrator takes notice.
+		 */
+		syslog(LOG_ERR, "graylist: get (%s) failed (errno %d)",
+		       inet_ntoa(addr), errno);
+		(void)db->close(db);
+		return;
+	}
+
+	/* reset the timer... */
+	rec.start = now;
+
+	syslog(LOG_INFO, "graylisting %s for %u seconds", inet_ntoa(addr),
+	       rec.shutout);
+
+	content.data = &rec;
+	content.size = sizeof(rec);
+	status = db->put(db, &key, &content, 0);
+
+	switch (status) {
+	case RET_SUCCESS:
+		break;
+	case RET_ERROR:
+		syslog(LOG_ERR, "graylist: put (%s) failed (errno %d)",
+		       inet_ntoa(addr), errno);
+		break;
+	}
+
+	(void)db->close(db);
+}
+
+static unsigned
+gl_connect(addr, punish)
+struct in_addr addr;
+int punish;
+{
+	DB *db;
+	DBT key, content;
+	graylist_t rec;
+	time_t now, expire;
+	unsigned interval;
+	int status;
+
+	/* feature is disabled */
+	if (!gl_initial)
+		return 0;
+
+	syslog(LOG_DEBUG, "graylist: connect %s, %d", inet_ntoa(addr), punish);
+
+	db = dbopen(gl_path, O_CREAT | O_RDWR, 0600, DB_HASH, NULL);
+	if (!db) {
+		syslog(LOG_ERR, "graylist: can't open database (errno %d)",
+		       errno);
+		return UINT_MAX;
+	}
+
+	key.data = &addr;
+	key.size = sizeof(addr);
+	status = db->get(db, &key, &content, 0);
+
+	time(&now);
+
+	switch (status) {
+	case RET_SUCCESS:
+		/* handled outside switch */
+		break;
+	case RET_NOTFOUND:
+ok:		db->close(db);
+		return 0;
+	case RET_ERROR:
+		syslog(LOG_ERR, "graylist: get (%s) failed (errno %d)",
+		       inet_ntoa(addr), errno);
+		(void)db->close(db);
+		return UINT_MAX;
+	}
+
+	memcpy(&rec, content.data, content.size);
+
+	expire = rec.start + rec.shutout;
+
+	/* we're past the expiry... but we don't delete the record; it
+	 * can only get expunged by a successful login
+	 */
+	if (expire <= now)
+		goto ok;
+
+	/* do we spank them for connecting before the graylist expires? */
+	if (punish) {
+		rec.start = now;
+		rec.shutout *= gl_scale;
+		if (rec.shutout > gl_max)
+			rec.shutout = gl_max;
+
+		content.data = &rec;
+		content.size = sizeof(rec);
+		status = db->put(db, &key, &content, 0);
+
+		switch (status) {
+		case RET_SUCCESS:
+			break;
+		case RET_ERROR:
+			syslog(LOG_ERR, "graylist: put (%s) failed (errno %d)",
+			       inet_ntoa(addr), errno);
+			db->close(db);
+			return UINT_MAX;
+		}
+		/* update expiry to reflect spanking */
+		expire = rec.start + rec.shutout;
+	}
+
+	syslog(LOG_INFO, "graylisted host %s %shas %u seconds remaining",
+	       inet_ntoa(addr), (punish ? "now " : ""), expire - now);
+
+	(void)db->close(db);
+
+	/* how long before they can retry */
+	return (expire - now);
+}
+
+static void
+gl_unblock(addr)
+struct in_addr addr;
+{
+	DB *db;
+	DBT key;
+	time_t now, expire;
+	int status;
+
+	/* feature is disabled */
+	if (!gl_initial)
+		return;
+
+	syslog(LOG_DEBUG, "graylist: authenticated %s", inet_ntoa(addr));
+
+	db = dbopen(gl_path, O_CREAT | O_RDWR, 0600, DB_HASH, NULL);
+	if (!db) {
+		syslog(LOG_ERR, "graylist: can't open database (errno %d)",
+		       errno);
+		return;
+	}
+
+	key.data = &addr;
+	key.size = sizeof(addr);
+	status = db->del(db, &key, 0);
+
+	switch (status) {
+	case RET_SUCCESS:
+	case RET_NOTFOUND:
+		break;
+	case RET_ERROR:
+		syslog(LOG_ERR, "graylist: del (%s) failed (errno %d)",
+		       inet_ntoa(addr), errno);
+		(void)db->close(db);
+		return;
+	}
+
+	(void)db->close(db);
+}
+#endif
+
 static int
 kpass(name, passwd)
 char *name, *passwd;
@@ -1076,6 +1371,9 @@
 				syslog(LOG_NOTICE,
 				       "repeated login failures from %s (%s)",
 				       rhost_addra, remotehost);
+#ifdef GRAYLIST
+				gl_badlogin(his_addr.sin_addr);
+#endif
 				dologout(0);
 			}
 			reply(530, "Login incorrect.");
@@ -1084,6 +1382,10 @@
 	}
 	login_attempts = 0;		/* this time successful */
 
+#ifdef GRAYLIST
+	gl_unblock(his_addr.sin_addr);
+#endif
+
 	login(passwd, 0);
 	return;
 }
--- src/appl/gssftp/ftpd/ftpd.M.graylist	2006-06-01 15:58:00.000000000 -0600
+++ src/appl/gssftp/ftpd/ftpd.M	2006-06-01 16:25:33.000000000 -0600
@@ -38,6 +38,7 @@
 .B ftpd
 [\fB\-A \fP|\fB -a\fP] [\fB\-C\fP] [\fB\-c\fP] [\fB\-d\fP] [\fB\-l\fP]
 [\fB\-v\fP] [\fB\-T\fP \fImaxtimeout\fP] [\fB\-t\fP \fItimeout\fP]
+[\fB\-g\fP \fIinitialgraylist \fP [\fB-G\fP \fImaxgraylist\fP] [\fB-S\fP \fIscalefactor\fP]
 [\fB\-p\fP \fIport\fP] [\fB\-U\fP \fIftpusers-file\fP] [\fB\-u\fP \fIumask\fP]
 [\fB\-r\fP \fIrealm-file\fP] [\fB\-s\fP \fIsrvtab\fP]
 [\fB\-w\fP{\fBip\fP|\fImaxhostlen\fP[\fB,\fP{\fBstriplocal\fP|\fBnostriplocal\fP}]}]
@@ -116,6 +117,25 @@
 file to use.  The default value is normally
 .IR /etc/ftpusers .
 .TP
+\fB\-g\fP \fIinitialgraylist\fP
+Sets the initial time to graylist a user following 3 successive failed
+logins on the same connection.  Should be in the range of 5..300.
+.TP
+\fB-G\fP \fImaxgraylist\fP
+Sets the maximum time to graylist a user should he attempt to reconnect
+before the graylist expires, or fail to login correctly 3 times on the same
+connection before the maximum time has expired (but after the current
+graylist interval expired).  Should be in the range of 300..31536000.
+If unspecified, it defaults to the \fIinitialgraylist\fP multipled by the
+scalefactor to the fifth power.
+.TP
+\fB-S\fP \fIscalefactor\fP
+Sets the scaling factor to increase the graylist period by should the
+user attempt to connect before the graylist expires.  That is, if the
+user connects before the current graylist expires, then the
+current graylist will be increased by \fIscalefactor\fP times.  Should
+be in the range of 2..100.  If unspecified, defaults to 2.
+.TP
 \fB\-u\fP \fIumask\fP
 Sets the umask for the ftpd process.  The default value is normally 027.
 .TP

[Index of Archives]     [Current Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Yosemite News]     [Yosemite Photos]     [KDE Users]     [Fedora Tools]     [Fedora Docs]

  Powered by Linux