Weak passwords and brute force attacks

Started by Gavin Sherryabout 19 years ago10 messages
#1Gavin Sherry
swm@linuxworld.com.au
1 attachment(s)

Hi all,

Host-based authentication and the other security mechanisms we have at the
moment provide fairly rigorous security but it seems to me there are two
mechanisms other authentication systems provide which we do not: testing
of the strength of passwords and delaying response after an authentication
failure.

The password strength test is pretty self-explanatory: DBAs often have to
hand out accounts to a range of real people who will be using anything
from reporting apps to testing new applications etc. With strength
testing, a DBA can at least ensure that a certain standard of complexity
is met. The attached patch adds two GUCs called 'test_weak_passwords' and
'min_password_length'. If 'test_weak_passwords', passwords will be
analyzed during CREATE and ALTER ROLE. It's not as simple the password
being greater than min_password_length. I guess the GUC name is a bit
confusing in that respect. Instead, what we do is add up the different
character types (lower, upper, digits, etc) and for each character type
missing, we reduce the hypothetical password length: the theory being that
the longer the password, the harder to guess.

Now, in the presence of encrypted passwords being sent across the wire, we
can't do anything. So, we export the password strength tester to libpq.

The second mechanism is the delay on authentication failure. The problem
here is that a distributed application could attempt to brute force guess
a password for a role. This could be fairly effective on a high speed LAN.
So, the usual approach is to delay sending the failure message to the
client for some period of time (specified in the patch by
auth_failure_delay) to slow the progress of the password guesser.
Naturally, environments where you cannot trust the local network sound
like problem outside out scope. But, I see a lot of systems with sensitive
company information (consider an HR system) which even employees should be
denied access to.

Authentication failure delay can be done with PAM but not everyone will be
abke to use PAM.

Any thoughts on these ideas?

Thanks,

Gavin

Attachments:

sec-3.difftext/plain; charset=US-ASCII; name=sec-3.diffDownload
Index: doc/src/sgml/config.sgml
===================================================================
RCS file: /usr/local/cvsroot/pgsql/doc/src/sgml/config.sgml,v
retrieving revision 1.98
diff -c -p -r1.98 config.sgml
*** doc/src/sgml/config.sgml	30 Nov 2006 20:50:44 -0000	1.98
--- doc/src/sgml/config.sgml	4 Dec 2006 13:46:34 -0000
*************** SET ENABLE_SEQSCAN TO OFF;
*** 587,592 ****
--- 587,627 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry id="guc-test-weak-passwords" xreflabel="password_encryption">
+       <term><varname>test_weak_passwords</varname> (<type>boolean</type>)</term>      <indexterm>
+        <primary><varname>test_weak_passwords</> configuration parameter</primary>
+       </indexterm>
+       <listitem>
+        <para>
+         When a password is specified in <xref
+         linkend="sql-createuser" endterm="sql-createuser-title"> or
+         <xref linkend="sql-alteruser" endterm="sql-alteruser-title">
+         and the password is unencrypted, the password's strength will be
+         tested if this parameter is turned on.   
+        </para>
+ 
+        <para>
+         If the password supplied is marked <literal>ENCRYPTED</> or
+         <literal>password_encryption</> is switched on, the password is not
+         tested.
+        </para>
+       </listitem>
+      </varlistentry> 
+ 
+      <varlistentry id="guc-min-password-length" xreflabel="min_password_length">
+ 	  <term><varname>min_password_length</varname> (<type>integer</type>)</term>
+       <indexterm>
+        <primary><varname>min_password_length</> configuration 
+        paramater</primary>
+      </indexterm>
+       <listitem>
+        <para>
+         Determines the minimum length in bytes for a password to be tested
+         when <literal>test_weak_passwords</> is turned on.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
       <varlistentry id="guc-krb-server-keyfile" xreflabel="krb_server_keyfile">
        <term><varname>krb_server_keyfile</varname> (<type>string</type>)</term>
        <indexterm>
Index: doc/src/sgml/libpq.sgml
===================================================================
RCS file: /usr/local/cvsroot/pgsql/doc/src/sgml/libpq.sgml,v
retrieving revision 1.220
diff -c -p -r1.220 libpq.sgml
*** doc/src/sgml/libpq.sgml	10 Nov 2006 22:15:26 -0000	1.220
--- doc/src/sgml/libpq.sgml	5 Dec 2006 12:40:47 -0000
*************** the result when done with it.
*** 3888,3893 ****
--- 3888,3915 ----
  </para>
  </listitem>
  </varlistentry>
+ 
+ <varlistentry>
+ <term><function>PQpasswordCheck</function><indexterm><primary>PQpasswordCheck</></></term>
+ <listitem>
+ <para>
+ Check whether a password could be considered strong.
+ <synopsis>
+ int PQpasswordCheck(const char *passwd);
+ </synopsis>
+ This function tests whether a password is of at least 6 bytes and
+ has a mix of upper case characters, lower case characters, numbers and 
+ other symbols (such as punctuation marks). The function works as follows:
+ any password of less than 6 bytes is rejected. For each class of character
+ missing from the strong, the password length is hypothetically shortened. That
+ is, the checker assumes that the password is easier to guess. So, to
+ be accepted, a password consisting only of lower case letters would have to
+ be 8 bytes long; one with with upper and lower case letters, 7 bytes; one with
+ upper and lower case letters and numbers, 6 bytes. The function is 
+ designed for <literal>SQL_ASCII</> environments.
+ </para>
+ </listitem>
+ </varlistentry>
  </variablelist>
  
  </sect1>
Index: src/backend/commands/user.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/commands/user.c,v
retrieving revision 1.174
diff -c -p -r1.174 user.c
*** src/backend/commands/user.c	4 Oct 2006 00:29:51 -0000	1.174
--- src/backend/commands/user.c	4 Dec 2006 13:47:01 -0000
***************
*** 29,38 ****
--- 29,40 ----
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/passwd_chk.h"
  #include "utils/syscache.h"
  
  
  extern bool Password_encryption;
+ extern bool test_weak_passwords;
  
  static List *roleNamesToIds(List *memberNames);
  static void AddRoleMems(const char *rolename, Oid roleid,
*************** CreateRole(CreateRoleStmt *stmt)
*** 309,314 ****
--- 311,324 ----
  
  	if (password)
  	{
+ 		/* test the strength of the password, if we can */
+ 		if(!isMD5(password) && test_weak_passwords)
+ 		{
+ 			if(pg_password_weak(password))
+ 				elog(ERROR, "password is too weak");
+ 
+ 		}
+ 
  		if (!encrypt_password || isMD5(password))
  			new_record[Anum_pg_authid_rolpassword - 1] =
  				DirectFunctionCall1(textin, CStringGetDatum(password));
*************** AlterRole(AlterRoleStmt *stmt)
*** 637,642 ****
--- 647,677 ----
  	/* password */
  	if (password)
  	{
+         if(!isMD5(password) && test_weak_passwords)
+         {
+ 			/* get the old tuple */
+ 			bool isnull;
+ 			Datum datum;
+ 			const char *oldpasswd;
+ 
+ 			datum = heap_getattr(tuple, Anum_pg_authid_rolpassword, 
+ 								 pg_authid_dsc, &isnull);
+ 			if(isnull)
+ 				oldpasswd = "";
+ 			else
+ 				oldpasswd = DatumGetCString(DirectFunctionCall1(textout, datum));
+             if(!isMD5(oldpasswd))
+ 			{
+ 				if(!pg_password_chg_aprv(oldpasswd, password))
+ 	                elog(ERROR, "password is too weak");
+ 			}
+ 			else
+ 			{
+ 				if(pg_password_weak(password))
+ 					elog(ERROR, "password is too weak");
+ 			}
+         }
+ 
  		if (!encrypt_password || isMD5(password))
  			new_record[Anum_pg_authid_rolpassword - 1] =
  				DirectFunctionCall1(textin, CStringGetDatum(password));
Index: src/backend/libpq/auth.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/libpq/auth.c,v
retrieving revision 1.146
diff -c -p -r1.146 auth.c
*** src/backend/libpq/auth.c	6 Nov 2006 01:27:52 -0000	1.146
--- src/backend/libpq/auth.c	5 Dec 2006 13:06:00 -0000
*************** char	   *pg_krb_server_keyfile;
*** 41,46 ****
--- 41,47 ----
  char	   *pg_krb_srvnam;
  bool		pg_krb_caseins_users;
  char	   *pg_krb_server_hostname = NULL;
+ extern int auth_failure_delay;
  
  #ifdef USE_PAM
  #ifdef HAVE_PAM_PAM_APPL_H
*************** pg_krb5_recvauth(Port *port)
*** 216,221 ****
--- 217,225 ----
  	krb5_ticket *ticket;
  	char	   *kusername;
  
+ 	if (get_role_line(port->user_name) == NULL)
+ 		return STATUS_ERROR;
+ 	
  	ret = pg_krb5_init();
  	if (ret != STATUS_OK)
  		return ret;
*************** auth_failed(Port *port, int status)
*** 357,362 ****
--- 361,369 ----
  			break;
  	}
  
+ 	if(auth_failure_delay)
+ 		pg_usleep(auth_failure_delay * 1000L);
+ 
  	ereport(FATAL,
  			(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
  			 errmsg(errstr, port->user_name)));
Index: src/backend/utils/misc/Makefile
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/utils/misc/Makefile,v
retrieving revision 1.26
diff -c -p -r1.26 Makefile
*** src/backend/utils/misc/Makefile	25 Jul 2006 03:51:21 -0000	1.26
--- src/backend/utils/misc/Makefile	5 Dec 2006 12:58:25 -0000
*************** include $(top_builddir)/src/Makefile.glo
*** 14,20 ****
  
  override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
  
! OBJS = guc.o help_config.o pg_rusage.o ps_status.o superuser.o tzparser.o
  
  # This location might depend on the installation directories. Therefore
  # we can't subsitute it into pg_config.h.
--- 14,21 ----
  
  override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
  
! OBJS = guc.o help_config.o passwd_chk.o pg_rusage.o ps_status.o superuser.o \
! 			 tzparser.o
  
  # This location might depend on the installation directories. Therefore
  # we can't subsitute it into pg_config.h.
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/utils/misc/guc.c,v
retrieving revision 1.360
diff -c -p -r1.360 guc.c
*** src/backend/utils/misc/guc.c	29 Nov 2006 14:50:07 -0000	1.360
--- src/backend/utils/misc/guc.c	5 Dec 2006 12:46:14 -0000
***************
*** 56,61 ****
--- 56,62 ----
  #include "utils/builtins.h"
  #include "utils/guc_tables.h"
  #include "utils/memutils.h"
+ #include "utils/passwd_chk.h"
  #include "utils/pg_locale.h"
  #include "utils/ps_status.h"
  #include "utils/tzparser.h"
*************** bool		default_with_oids = false;
*** 175,180 ****
--- 176,184 ----
  bool		SQL_inheritance = true;
  
  bool		Password_encryption = true;
+ bool 		test_weak_passwords = false;
+ int 		min_password_length = PASSWD_MIN_LEN;
+ int 		auth_failure_delay = 0;
  
  int			log_min_error_statement = ERROR;
  int			log_min_messages = NOTICE;
*************** static struct config_bool ConfigureNames
*** 827,832 ****
--- 831,846 ----
  		true, NULL, NULL
  	},
  	{
+ 		{"test_weak_passwords", PGC_SUSET, CONN_AUTH_SECURITY,
+ 			gettext_noop("Test for weak passwords."),
+ 			gettext_noop("When turned on, unencrypted passwords supplied to "
+ 				"the CREATE/ALTER ROLE/USER commands will be tested for "
+ 				"strength.")
+ 		},
+ 		&test_weak_passwords,
+ 		false, NULL, NULL
+ 	}, 
+ 	{
  		{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
  			gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
  			gettext_noop("When turned on, expressions of the form expr = NULL "
*************** static struct config_int ConfigureNamesI
*** 1658,1664 ****
  		&server_version_num,
  		PG_VERSION_NUM, PG_VERSION_NUM, PG_VERSION_NUM, NULL, NULL
  	},
! 
  	/* End-of-list marker */
  	{
  		{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
--- 1672,1699 ----
  		&server_version_num,
  		PG_VERSION_NUM, PG_VERSION_NUM, PG_VERSION_NUM, NULL, NULL
  	},
! 	{
! 		{"min_password_length", PGC_SUSET, CONN_AUTH_SECURITY,
! 			gettext_noop("Minimum password length."),
! 			gettext_noop("When unencrypted passwords are sent to "
! 				"CREATE/ALTER ROLE/USER, check that they are at least "
! 				"this long. Only used if test_weak_passwords is on.")
! 		},
! 		&min_password_length,
! 		PASSWD_MIN_LEN, 0, NAMEDATALEN - 1, NULL, NULL
! 	},
! 	{
! 		{"auth_failure_delay", PGC_SIGHUP, CONN_AUTH_SECURITY,
! 			gettext_noop("Time to sleep after an authentication failure."),
! 			gettext_noop("When authentication of a user fails, sleep for "
! 				"a period, measured in milliseconds, before notifying the "
! 				"client that the failure has occured. This can assist with "
! 				"mitigating systematic attacks which try and guess passwords."),
! 			GUC_UNIT_MS
! 		},
! 		&auth_failure_delay,
! 		0, 0, INT_MAX / 1000, NULL, NULL
! 	}, 
  	/* End-of-list marker */
  	{
  		{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
Index: src/backend/utils/misc/passwd_chk.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/utils/misc/Attic/passwd_chk.c,v
retrieving revision 1.1.2.1
diff -c -p -r1.1.2.1 passwd_chk.c
*** src/backend/utils/misc/passwd_chk.c	2 Nov 2006 21:51:11 -0000	1.1.2.1
--- src/backend/utils/misc/passwd_chk.c	27 Oct 2006 05:36:13 -0000
***************
*** 0 ****
--- 1,217 ----
+ /*-------------------------------------------------------------------------
+  *
+  * passwd_check.c
+  *	  Functions to check the strength of passwords
+  *
+  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * IDENTIFICATION
+  *	  $PostgreSQL$
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+ 
+ #include <ctype.h>
+ 
+ #include "utils/passwd_chk.h"
+ 
+ #ifndef FRONTEND
+ extern bool test_weak_passwords;
+ extern int min_password_length;
+ #endif
+ 
+ 
+ /*
+  * Check if the password is too simple. To do this, we check that characters
+  * -- be they digits, upper case alpha, lower case alpha or other -- meet
+  * the thresholds defined in passwd_chk.h.
+  *
+  * This is very much specific to C locale settings -- i.e., doesn't play
+  * address multibyte issues. For example, six bytes can be taken up by
+  * two Thai utf-8 characters, which is hardly a strong password. Hmmm...
+  * what to do?
+  *
+  * There are a few different things we could do:
+  * - have a password_min_bytes GUC (seems kinda lame)
+  * - have a password_min_len GUC and measure characters basic on current
+  * 		encoding
+  * - do the isupper, islower, etc, but with iswupper, etc. But, I don't know
+  *      if all languages have a concept of upper and lower case...
+  * 
+  * Returns true if password is simple, otherwise false.
+  */
+ 
+ static bool
+ passwd_smpl(const char *passwd)
+ {
+ 	int i = 0;
+ 	int o = 0;
+ 	int d = 0;
+ 	int u = 0;
+ 	int l = 0;
+ 
+ 	while(passwd[i] != '\0')
+ 	{
+ 		int c = (int)passwd[i];
+ 		if(isdigit(c))
+ 			d++;
+ 		else if(isupper(c))
+ 			u++;
+ 		else if(islower(c))
+ 			l++;
+ 		else
+ 			o++;
+ 		i++;
+ 	}
+ 
+ 	/*
+ 	 * For every missing class of character, we're going to subtract 1 byte
+ 	 * from the measured length of the password. If we're too short after
+ 	 * this, complain that the password is too simple.
+ 	 */
+ 	i -= d > 0 ? 0 : 1;
+ 	i -= o > 0 ? 0 : 1;
+ 	i -= u > 0 ? 0 : 1;
+ 	i -= l > 0 ? 0 : 1;
+ 
+ #ifdef FRONTEND
+ 	return i < PASSWD_MIN_LEN;
+ #else
+ 	return i < min_password_length;
+ #endif 
+ }
+ 
+ #ifndef FRONTEND
+ 
+ /*
+  * Sometimes users change their passwords in silly ways: such as
+  * 'foo' -> 'oof' or 'foo' to 'FOO'. See if that's the case.
+  *
+  * If the passwords are sufficiently different, return true, otherwise
+  * false.
+  */
+ static bool
+ passwd_diff(const char *old, const char *new)
+ {
+ 	int ol;
+ 	int nl;
+ 	int i;
+ 	bool found = false;
+ 	char oldl[NAMEDATALEN];
+ 	char newl[NAMEDATALEN];
+ 
+ 	/* we want to ignore case, so fold each to lower case */
+ 	ol = strlen(old);
+ 	nl = strlen(new);
+ 
+ 	for(i = 0; i < ol; i++)
+ 	{
+ 		oldl[i] = tolower(old[i]);
+ 	}
+ 	oldl[i] = '\0';
+ 
+ 	for(i = 0; i < nl; i++)
+     {
+         newl[i] = tolower(new[i]);
+     }
+     newl[i] = '\0';
+ 
+ 	/* are they the same? */
+ 	if(strcmp(oldl, newl) == 0)
+ 		return false;
+ 
+ 	/* is the string just reversed? */
+ 	if(ol == nl)
+ 	{
+ 		for(i = 0; i < ol && i < nl; i++)
+ 		{
+ 			/* they might have changed case too */
+ 			if(oldl[i] != newl[nl - i - 1])
+ 			{
+ 				found = true;
+ 				break;
+ 			}
+ 		}
+ 		if(!found)
+ 			return false;
+ 	}
+ 
+ 	/* is one password a sub/super set of the other? */
+ 	if(strstr(oldl, newl))
+ 		return false;
+ 	if(strstr(newl, oldl))
+ 		return false;
+ 
+ 	return true;
+ }
+ 
+ #endif
+ 
+ /*
+  * public functions
+  */
+ 
+ bool
+ pg_password_weak(const char *passwd)
+ {
+ #ifndef FRONTEND
+ 	if(!test_weak_passwords)
+ 		return false;
+ #endif
+ 	return passwd_smpl(passwd);
+ }
+ 
+ /* We have no use for testing password changes in the front end */
+ #ifndef FRONTEND
+ bool
+ pg_password_chg_aprv(const char *old, const char *new)
+ {
+ 	if(!test_weak_passwords)
+ 		return true;
+ 
+ 	if(pg_password_weak(new))
+ 		return false;
+ 	if(!passwd_diff(old, new))
+ 		return false;
+ 	return true;
+ }
+ 
+ #endif /* !FRONTEND */
+ 
+ #ifdef UNIT_TEST
+ 
+ #include <stdio.h>
+ 
+ #define MYA(condition) \
+ 	if(!(condition)) \
+ 		fprintf(stderr, "Test '%s' failed at %i\n", #condition, __LINE__); 
+ 
+ int
+ main(void)
+ {
+ 
+ 	MYA(passwd_smpl("foo") == true);
+ 	MYA(passwd_smpl("Foo6") == true);
+ 	MYA(passwd_smpl("gavins") == true);
+ 
+ 	MYA(passwd_diff("foo", "foo") == false);
+ 	MYA(passwd_diff("foo", "oof") == false);
+ 	MYA(passwd_diff("foofoo", "foo") == false);
+ 	MYA(passwd_diff("FOO", "foo") == false);
+ 	MYA(passwd_diff("dsiifm0239u", "5sd|[+09s2") == true);
+ 
+ #ifndef FRONTEND
+ 	MYA(pg_password_chg_aprv("oldold", "ddd") == false);
+ 	MYA(pg_password_chg_aprv("newview", "sdfi0238j") == true);
+ #endif
+ 
+ 	return 0;
+ }
+ #endif /* UNIT_TEST */
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.199
diff -c -p -r1.199 postgresql.conf.sample
*** src/backend/utils/misc/postgresql.conf.sample	21 Nov 2006 01:23:37 -0000	1.199
--- src/backend/utils/misc/postgresql.conf.sample	5 Dec 2006 13:04:33 -0000
***************
*** 72,77 ****
--- 72,81 ----
  #authentication_timeout = 1min		# 1s-600s
  #ssl = off				# (change requires restart)
  #password_encryption = on
+ #auth_failure_delay = 0ms     # sleep this long after an authentication failure
+ #test_weak_passwords = off    # whether to test for weak passwords
+ #min_password_length = 0      # minimum length of passwords, in bytes.
+ 						      # only used if test_weak_passwords is on.
  #db_user_namespace = off
  
  # Kerberos
Index: src/include/utils/passwd_chk.h
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/include/utils/Attic/passwd_chk.h,v
retrieving revision 1.1.2.1
diff -c -p -r1.1.2.1 passwd_chk.h
*** src/include/utils/passwd_chk.h	2 Nov 2006 21:52:14 -0000	1.1.2.1
--- src/include/utils/passwd_chk.h	27 Oct 2006 05:12:32 -0000
***************
*** 0 ****
--- 1,24 ----
+ /*-------------------------------------------------------------------------
+  *
+  * passwd_chk.h
+  * 		Routines to check password strength
+  *
+  * See passwd_chk.c for details.
+  *
+  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * $PostgreSQL$
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #ifndef PASSWD_CHK_H
+ #define PASSWD_CHK_H
+ 
+ #define PASSWD_MIN_LEN 6
+ 
+ extern bool pg_password_weak(const char *passwd);
+ extern bool pg_password_chg_aprv(const char *old, const char *new);
+ 
+ #endif
Index: src/interfaces/libpq/Makefile
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/interfaces/libpq/Makefile,v
retrieving revision 1.149
diff -c -p -r1.149 Makefile
*** src/interfaces/libpq/Makefile	27 Sep 2006 21:29:17 -0000	1.149
--- src/interfaces/libpq/Makefile	5 Dec 2006 12:42:39 -0000
*************** LIBS := $(LIBS:-lpgport=)
*** 34,39 ****
--- 34,40 ----
  OBJS=	fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
  	fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
  	md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o \
+ 	passwd_chk.o \
  	$(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o, $(LIBOBJS))
  
  ifeq ($(PORTNAME), cygwin)
*************** md5.c ip.c: % : $(backend_src)/libpq/%
*** 89,94 ****
--- 90,98 ----
  encnames.c wchar.c : % : $(backend_src)/utils/mb/%
  	rm -f $@ && $(LN_S) $< .
  
+ passwd_chk.c: % : $(backend_src)/utils/misc/%
+ 	rm -f $@ && $(LN_S) $< . 
+ 
  
  # We need several not-quite-identical variants of .DEF files to build libpq
  # DLLs for Windows.  These are made from the single source file exports.txt.
Index: src/interfaces/libpq/fe-auth.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v
retrieving revision 1.121
diff -c -p -r1.121 fe-auth.c
*** src/interfaces/libpq/fe-auth.c	4 Oct 2006 00:30:12 -0000	1.121
--- src/interfaces/libpq/fe-auth.c	5 Dec 2006 12:43:24 -0000
***************
*** 49,54 ****
--- 49,55 ----
  
  #include "libpq-fe.h"
  #include "fe-auth.h"
+ #include "utils/passwd_chk.h"
  #include "libpq/md5.h"
  
  
*************** pg_fe_getauthname(char *PQerrormsg)
*** 581,586 ****
--- 582,605 ----
  	return authn;
  }
  
+ /*
+  * PQpasswordCheck() - test the strength of a password
+  *
+  * We can do this in the backend when a role is created or altered and a
+  * password is supplied. In fact, pg_password_weak() is exported from the
+  * backend code. The reason we provide it here is that passwords can be
+  * sent to the backend encrypted (see PQencryptPassword() above). Obvious
+  * we cannot test the strength of a password encrypted with md5 so we
+  * allow users to do it in the front end.
+  *
+  * Returns true if the password is sufficiently strong, otherwise false.
+  */
+ 
+ int
+ PQpasswordCheck(const char *passwd)
+ {
+ 	return !pg_password_weak(passwd);
+ }
  
  /*
   * PQencryptPassword -- exported routine to encrypt a password
Index: src/interfaces/libpq/libpq-fe.h
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v
retrieving revision 1.134
diff -c -p -r1.134 libpq-fe.h
*** src/interfaces/libpq/libpq-fe.h	4 Oct 2006 00:30:13 -0000	1.134
--- src/interfaces/libpq/libpq-fe.h	5 Dec 2006 12:43:46 -0000
*************** extern int	PQenv2encoding(void);
*** 506,511 ****
--- 506,512 ----
  
  /* === in fe-auth.c === */
  
+ extern int PQpasswordCheck(const char *passwd);
  extern char *PQencryptPassword(const char *passwd, const char *user);
  
  #ifdef __cplusplus
#2Andrew Dunstan
andrew@dunslane.net
In reply to: Gavin Sherry (#1)
Re: Weak passwords and brute force attacks

Gavin Sherry wrote:

Hi all,

Host-based authentication and the other security mechanisms we have at the
moment provide fairly rigorous security but it seems to me there are two
mechanisms other authentication systems provide which we do not: testing
of the strength of passwords and delaying response after an authentication
failure.

Frankly, if we're going to build this stuff in we need to expose the API
for password checking at least. Many apps run as a given postgres user
and set up application users and passwords as application level artifacts.

The password strength test is pretty self-explanatory: DBAs often have to
hand out accounts to a range of real people who will be using anything
from reporting apps to testing new applications etc. With strength
testing, a DBA can at least ensure that a certain standard of complexity
is met. The attached patch adds two GUCs called 'test_weak_passwords' and
'min_password_length'. If 'test_weak_passwords', passwords will be
analyzed during CREATE and ALTER ROLE. It's not as simple the password
being greater than min_password_length. I guess the GUC name is a bit
confusing in that respect. Instead, what we do is add up the different
character types (lower, upper, digits, etc) and for each character type
missing, we reduce the hypothetical password length: the theory being that
the longer the password, the harder to guess.

Now, in the presence of encrypted passwords being sent across the wire, we
can't do anything. So, we export the password strength tester to libpq.

That assumes you trust the client. So then you are protecting against
stupidity, but not malice. But you are afraid that stupidity will open
the door to malice ...

The second mechanism is the delay on authentication failure. The problem
here is that a distributed application could attempt to brute force guess
a password for a role. This could be fairly effective on a high speed LAN.
So, the usual approach is to delay sending the failure message to the
client for some period of time (specified in the patch by
auth_failure_delay) to slow the progress of the password guesser.
Naturally, environments where you cannot trust the local network sound
like problem outside out scope. But, I see a lot of systems with sensitive
company information (consider an HR system) which even employees should be
denied access to.

Arguably such systems should not be using standard password auth at all.
SSL with client certs is probably the way to go. Relying on password
strength checking and delay in such a case would be, to use David
Fetter's recent phrase, putting lipstick on the md5 pig.

Authentication failure delay can be done with PAM but not everyone will be
abke to use PAM.

Well, pam_cracklib will do an outstanding job on all these issues for you.

I'm not opposed to providing some of this stuff, although some does seem
to be reinventing the wheel. But we should be careful about how much
security we think we are really providing.

cheers

andrew

#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Gavin Sherry (#1)
Re: Weak passwords and brute force attacks

Gavin Sherry <swm@linuxworld.com.au> writes:

.... Instead, what we do is add up the different
character types (lower, upper, digits, etc) and for each character type
missing, we reduce the hypothetical password length: the theory being that
the longer the password, the harder to guess.

Where did you get this design for a password strength checker?
It doesn't sound like it has much to do with the algorithms commonly
used for such things.

Now, in the presence of encrypted passwords being sent across the wire, we
can't do anything. So, we export the password strength tester to libpq.

As already noted, that seems approximately useless.

The second mechanism is the delay on authentication failure. The problem
here is that a distributed application could attempt to brute force guess
a password for a role. This could be fairly effective on a high speed LAN.
So, the usual approach is to delay sending the failure message to the
client for some period of time (specified in the patch by
auth_failure_delay) to slow the progress of the password guesser.

This is a waste of effort, unless you propose to put the delay into both
the success and failure paths, which hardly seems acceptable. Otherwise
a guesser need only abandon the connection attempt after X microseconds
and try another password.

regards, tom lane

#4Noname
mark@mark.mielke.cc
In reply to: Tom Lane (#3)
Re: Weak passwords and brute force attacks

On Tue, Dec 05, 2006 at 11:32:40AM -0500, Tom Lane wrote:

Gavin Sherry <swm@linuxworld.com.au> writes:

Now, in the presence of encrypted passwords being sent across the wire, we
can't do anything. So, we export the password strength tester to libpq.

As already noted, that seems approximately useless.

Telling the users they are being stupid isn't useless - but yeah, it sounds
like a domain that should be covered by a tool other than PostgreSQL.

PAM sounds about right, and PAM already has this functionality.

The second mechanism is the delay on authentication failure. The problem
here is that a distributed application could attempt to brute force guess
a password for a role. This could be fairly effective on a high speed LAN.
So, the usual approach is to delay sending the failure message to the
client for some period of time (specified in the patch by
auth_failure_delay) to slow the progress of the password guesser.

This is a waste of effort, unless you propose to put the delay into both
the success and failure paths, which hardly seems acceptable. Otherwise
a guesser need only abandon the connection attempt after X microseconds
and try another password.

Not a waste unless the caller isn't going to allow for missing a valid
password after X+1 microseconds that was late due to network latency,
and CPU scheduling. This isn't a real time system. Even halfing the
number of password attempts doubles the time required to search a
particular pattern space. Double is good. It's a tried and true method
used by security systems around the world. Another tried and true
approach is to deny a client for a period (10 seconds?) if they enter
the same wrong password three times in a row. It really puts a wrench
in the gears for any brute force strategy.

But again, I would still prefer to avoid username/password entirely if
security is a real concern. SSL certificates, Kerberos tokens, or
operating system credentials (passed over UNIX sockets) appeal to me
much more.

If the patch is really small, and really portable, I would suggest it
be adopted. For example, adding a sleep(1) after invalid passwords doesn't
significantly add to the maintenance cost for the code, and it has a
positive effect. Getting too complicated, however, would be a problem
domain that PostgreSQL shouldn't be trying to address.

Another perspective... :-)

Cheers,
mark

--
mark@mielke.cc / markm@ncf.ca / markm@nortel.com __________________________
. . _ ._ . . .__ . . ._. .__ . . . .__ | Neighbourhood Coder
|\/| |_| |_| |/ |_ |\/| | |_ | |/ |_ |
| | | | | \ | \ |__ . | | .|. |__ |__ | \ |__ | Ottawa, Ontario, Canada

One ring to rule them all, one ring to find them, one ring to bring them all
and in the darkness bind them...

http://mark.mielke.cc/

#5Stephen Frost
sfrost@snowman.net
In reply to: Noname (#4)
Re: Weak passwords and brute force attacks

* mark@mark.mielke.cc (mark@mark.mielke.cc) wrote:

On Tue, Dec 05, 2006 at 11:32:40AM -0500, Tom Lane wrote:

Gavin Sherry <swm@linuxworld.com.au> writes:

Now, in the presence of encrypted passwords being sent across the wire, we
can't do anything. So, we export the password strength tester to libpq.

As already noted, that seems approximately useless.

Telling the users they are being stupid isn't useless - but yeah, it sounds
like a domain that should be covered by a tool other than PostgreSQL.

PAM sounds about right, and PAM already has this functionality.

PAM is simply not always an option, unless you want to figure out a way
to use PAM modules without using /etc/passwd and company. Currently the
only way to use PAM w/ password-changing done in PG is to chown all the
various files and whatnot over to being owned by Postgres, which is a royal
pain and a very ugly mess. I suppose another option would be to
convince PG to run as root but that's not exactly an encouraged setup
either.

I wouldn't be against using cracklib (outside of PAM) tho.

This is a waste of effort, unless you propose to put the delay into both
the success and failure paths, which hardly seems acceptable. Otherwise
a guesser need only abandon the connection attempt after X microseconds
and try another password.

Not a waste unless the caller isn't going to allow for missing a valid
password after X+1 microseconds that was late due to network latency,
and CPU scheduling. This isn't a real time system. Even halfing the
number of password attempts doubles the time required to search a
particular pattern space. Double is good. It's a tried and true method
used by security systems around the world. Another tried and true
approach is to deny a client for a period (10 seconds?) if they enter
the same wrong password three times in a row. It really puts a wrench
in the gears for any brute force strategy.

I fully agree with this.

But again, I would still prefer to avoid username/password entirely if
security is a real concern. SSL certificates, Kerberos tokens, or
operating system credentials (passed over UNIX sockets) appeal to me
much more.

Sure, but I don't feel that means we should rule out adding in this
basic functionality for the username/password system that I expect we'll
support indefinitely.

Thanks,

Stephen

#6Andrew Dunstan
andrew@dunslane.net
In reply to: Stephen Frost (#5)
Re: Weak passwords and brute force attacks

Stephen Frost wrote:

PAM is simply not always an option, unless you want to figure out a way
to use PAM modules without using /etc/passwd and company. Currently the
only way to use PAM w/ password-changing done in PG is to chown all the
various files and whatnot over to being owned by Postgres, which is a royal
pain and a very ugly mess. I suppose another option would be to
convince PG to run as root but that's not exactly an encouraged setup
either.

That assumes that you are using system auth. PAM+LDAP for example has no
such problems.

cheers

andrew

#7Gavin Sherry
swm@linuxworld.com.au
In reply to: Andrew Dunstan (#2)
Re: Weak passwords and brute force attacks

On Tue, 5 Dec 2006, Andrew Dunstan wrote:

The second mechanism is the delay on authentication failure. The problem
here is that a distributed application could attempt to brute force guess
a password for a role. This could be fairly effective on a high speed LAN.
So, the usual approach is to delay sending the failure message to the
client for some period of time (specified in the patch by
auth_failure_delay) to slow the progress of the password guesser.
Naturally, environments where you cannot trust the local network sound
like problem outside out scope. But, I see a lot of systems with sensitive
company information (consider an HR system) which even employees should be
denied access to.

Arguably such systems should not be using standard password auth at all.
SSL with client certs is probably the way to go. Relying on password
strength checking and delay in such a case would be, to use David
Fetter's recent phrase, putting lipstick on the md5 pig.

I agree with what they should do. However, what usually happens is that a
senior employee wants to plug their tool (reporting, or what ever) into
the database. Because we aren't supported like, say, Oracle is they have
to connect via ODBC. What seems to happen then is, they're given a
username and password. It's those accounts you have to worry about.

Authentication failure delay can be done with PAM but not everyone will be
abke to use PAM.

Well, pam_cracklib will do an outstanding job on all these issues for you.

I'm not opposed to providing some of this stuff, although some does seem
to be reinventing the wheel. But we should be careful about how much
security we think we are really providing.

Right, I think PAM does a great job but it isn't available on, say,
Windows.

Thanks,

Gavin

#8Gavin Sherry
swm@linuxworld.com.au
In reply to: Tom Lane (#3)
Re: Weak passwords and brute force attacks

On Tue, 5 Dec 2006, Tom Lane wrote:

Gavin Sherry <swm@linuxworld.com.au> writes:

.... Instead, what we do is add up the different
character types (lower, upper, digits, etc) and for each character type
missing, we reduce the hypothetical password length: the theory being that
the longer the password, the harder to guess.

Where did you get this design for a password strength checker?
It doesn't sound like it has much to do with the algorithms commonly
used for such things.

I'm not sure what's commonly done but I read it on some slides about PAM.
I looked at what PAM does just now. It does do something close to what I
did -- but with more configuration capabilities -- and checks for
palindromes and similarity when changing a password.

Now, in the presence of encrypted passwords being sent across the wire, we
can't do anything. So, we export the password strength tester to libpq.

As already noted, that seems approximately useless.

I figured it might be useful for applications like pgadmin which wanted to
send the password encrypted on the wire but wanted to test of its
strength.

The second mechanism is the delay on authentication failure. The problem
here is that a distributed application could attempt to brute force guess
a password for a role. This could be fairly effective on a high speed LAN.
So, the usual approach is to delay sending the failure message to the
client for some period of time (specified in the patch by
auth_failure_delay) to slow the progress of the password guesser.

This is a waste of effort, unless you propose to put the delay into both
the success and failure paths, which hardly seems acceptable. Otherwise
a guesser need only abandon the connection attempt after X microseconds
and try another password.

That doesn't seem to be what PAM does, at leasts in the default config.
What they do do is to sleep for a random period between no sleep and the
threshold, so that the attacker cannot guess the appropriate time to wait
before hanging up.

Thanks,

Gavin

#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Gavin Sherry (#8)
Re: Weak passwords and brute force attacks

Gavin Sherry <swm@linuxworld.com.au> writes:

On Tue, 5 Dec 2006, Tom Lane wrote:

Gavin Sherry <swm@linuxworld.com.au> writes:

The second mechanism is the delay on authentication failure.

This is a waste of effort, unless you propose to put the delay into both
the success and failure paths, which hardly seems acceptable. Otherwise
a guesser need only abandon the connection attempt after X microseconds
and try another password.

That doesn't seem to be what PAM does, at leasts in the default config.
What they do do is to sleep for a random period between no sleep and the
threshold, so that the attacker cannot guess the appropriate time to wait
before hanging up.

No, you missed my point: the attacker doesn't need to guess what the
failure delay is. As long as he has a pretty good idea what the
*success* response time ought to be, he can give up as soon as a bit
more than that has elapsed. Yup, it's probabilistic because there's
some uncertainty about the backend launch time, but what does he
care? Brute-force password attacks are always probabilistic.

A delay in the failure case is only helpful if you have some active way
to prevent the attacker from making another try before the delay has
elapsed. Which is something we don't have, at least not without
introducing a lot more complexity/fragility into the postmaster than
seems wise to me.

regards, tom lane

#10Gavin Sherry
swm@linuxworld.com.au
In reply to: Tom Lane (#9)
Re: Weak passwords and brute force attacks

On Fri, 8 Dec 2006, Tom Lane wrote:

Gavin Sherry <swm@linuxworld.com.au> writes:

On Tue, 5 Dec 2006, Tom Lane wrote:

Gavin Sherry <swm@linuxworld.com.au> writes:

The second mechanism is the delay on authentication failure.

This is a waste of effort, unless you propose to put the delay into both
the success and failure paths, which hardly seems acceptable. Otherwise
a guesser need only abandon the connection attempt after X microseconds
and try another password.

That doesn't seem to be what PAM does, at leasts in the default config.
What they do do is to sleep for a random period between no sleep and the
threshold, so that the attacker cannot guess the appropriate time to wait
before hanging up.

No, you missed my point: the attacker doesn't need to guess what the
failure delay is. As long as he has a pretty good idea what the
*success* response time ought to be, he can give up as soon as a bit
more than that has elapsed. Yup, it's probabilistic because there's
some uncertainty about the backend launch time, but what does he
care? Brute-force password attacks are always probabilistic.

I agree that your method makes sense but it's not what PAM seems to do.
I'm no security expert though. The OpenSSH daemon /seems/ to do the
same thing.

Thinking about it, I'm comparing apples and oranges. The systems PAM is
often being used for keep the connection open in the presence of an
authentication failure. They assume a human is at the other end and made a
typo. In our case, we basically terminate the connection.

Hmmm :-(.

A delay in the failure case is only helpful if you have some active way
to prevent the attacker from making another try before the delay has
elapsed. Which is something we don't have, at least not without
introducing a lot more complexity/fragility into the postmaster than
seems wise to me.

I agree. I had a think about what is involved there and as you say it
would make the code a lot more complex.

Thanks,

Gavin