prevent user enumeration attacks
authorDietmar Maurer <dietmar@proxmox.com>
Mon, 18 Nov 2013 08:05:04 +0000 (09:05 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Mon, 18 Nov 2013 08:05:04 +0000 (09:05 +0100)
PVE/API2/AccessControl.pm
PVE/AccessControl.pm

index 1679ed4..8daf10c 100644 (file)
@@ -2,6 +2,7 @@ package PVE::API2::AccessControl;
 
 use strict;
 use warnings;
+use Time::HiRes qw(usleep gettimeofday tv_interval);
 
 use PVE::Exception qw(raise raise_perm_exc);
 use PVE::SafeSyslog;
@@ -265,6 +266,8 @@ __PACKAGE__->register_method ({
 
        my $res;
 
+       my $starttime = [gettimeofday];
+
        eval {
            # test if user exists and is enabled
            $rpcenv->check_user_enabled($username);
@@ -279,7 +282,13 @@ __PACKAGE__->register_method ({
        if (my $err = $@) {
            my $clientip = $rpcenv->get_client_ip() || '';
            syslog('err', "authentication failure; rhost=$clientip user=$username msg=$err");
-           die $err;
+           # do not return any info to prevent user enumeration attacks
+           # always try to delay exactly 3 seconds to prevent timing attacks
+           my $elapsed;
+           while (($elapsed = tv_interval($starttime)) < 3) {
+               usleep(int((3 - $elapsed)*1000000));
+           }
+           die "authentication failure\n"; 
        }
 
        $res->{cap} = &$compute_api_permission($rpcenv, $username);
index ab5e52e..236efac 100644 (file)
@@ -306,6 +306,7 @@ sub check_user_enabled {
 }
 
 # password should be utf8 encoded
+# Note: some pluging delay/sleep if auth fails
 sub authenticate_user {
     my ($username, $password) = @_;
 
@@ -317,32 +318,19 @@ sub authenticate_user {
 
     my $usercfg = cfs_read_file('user.cfg');
 
-    eval { check_user_enabled($usercfg, $username); };
-    if (my $err = $@) {
-       sleep(2);
-       die $err;
-    }
+    check_user_enabled($usercfg, $username);
 
     my $ctime = time();
     my $expire = $usercfg->{users}->{$username}->{expire};
 
-    if ($expire && ($expire < $ctime)) {
-       sleep(2);
-       die "account expired\n"
-    }
+    die "account expired\n" if $expire && ($expire < $ctime);
 
     my $domain_cfg = cfs_read_file('domains.cfg');
 
-    eval {
-       my $cfg = $domain_cfg->{ids}->{$realm};
-       die "auth domain '$realm' does not exists\n" if !$cfg;
-       my $plugin = PVE::Auth::Plugin->lookup($cfg->{type});
-       $plugin->authenticate_user($cfg, $realm, $ruid, $password);
-    };
-    if (my $err = $@) {
-       sleep(2); # timeout after failed auth
-       die $err;
-    }
+    my $cfg = $domain_cfg->{ids}->{$realm};
+    die "auth domain '$realm' does not exists\n" if !$cfg;
+    my $plugin = PVE::Auth::Plugin->lookup($cfg->{type});
+    $plugin->authenticate_user($cfg, $realm, $ruid, $password);
 
     return $username;
 }