Added prevention against brute force atacks on login page.
authorJanusz Dobrowolski <janusz@frontaccouting.eu>
Fri, 14 Sep 2012 21:49:49 +0000 (23:49 +0200)
committerJanusz Dobrowolski <janusz@frontaccouting.eu>
Fri, 14 Sep 2012 21:49:49 +0000 (23:49 +0200)
access/login.php
config.default.php
includes/current_user.inc
includes/session.inc

index 24a74a1c0ac079287f29c59eb9fa12dd42c53210..fe43ff4ff5ba5f29e88a3919575208beddcd6a30 100644 (file)
@@ -30,6 +30,16 @@ function defaultCompany()
        {
                $demo_text = _("Please login here");
        }
+
+       if (check_faillog())
+       {
+               $blocked_msg = '<span class=redfg>'._('Too many failed login attempts.<br>Please wait a while or try later.').'</span>';
+
+           $js .= "<script>setTimeout(function() {
+               document.getElementsByName('SubmitUser')[0].disabled=0;
+               document.getElementById('log_msg').innerHTML='$demo_text'}, 1000*$login_delay);</script>";
+           $demo_text = $blocked_msg;
+       }
        if (!isset($def_coy))
                $def_coy = 0;
        $def_theme = "default";
@@ -100,12 +110,12 @@ function defaultCompany()
                        text_row(_("Company"), "company_login_nickname", "", 20, 30);
                }
                start_row();
-               label_cell($demo_text, "colspan=2 align='center'");
+               label_cell($demo_text, "colspan=2 align='center' id='log_msg'");
                end_row();
        }; 
        end_table(1);
        echo "<center><input type='submit' value='&nbsp;&nbsp;"._("Login -->")."&nbsp;&nbsp;' name='SubmitUser'"
-               .($login_timeout ? '':" onclick='set_fullmode();'")." /></center>\n";
+               .($login_timeout ? '':" onclick='set_fullmode();'").(isset($blocked_msg) ? " disabled" : '')." /></center>\n";
 
        foreach($_SESSION['timeout']['post'] as $p => $val) {
                // add all request variables to be resend together with login data
index aed9cd9e5ba7a3ad373c57f92b0dd2ad6cd8a4a2..ae6668ef12399e099acac4a53d80146192f657de 100644 (file)
@@ -253,4 +253,10 @@ $text_company_selection  = false;
 
        $hide_inaccessible_menu_items = 0;
 
-?>
\ No newline at end of file
+/*
+       Brute force prevention.
+       $login_delay seconds delay is required between login attempts after $login_max_attemps failed logins.
+       Set $login_delay to 0 to disable the feature (not recommended)
+*/
+$login_delay = 30;
+$login_max_attempts = 10;
index 4c2aca3077ed356bb69f3d3e9742dce3b30e15dc..512d09f02de223d4486d623a5306f442d6bbcf58 100644 (file)
@@ -18,7 +18,7 @@ if (!defined('TB_PREF')) {
 
 class current_user
 {
-       var $user;
+       var $user = 0;
        var $loginname;
        var $username;
        var $name;
@@ -59,7 +59,7 @@ class current_user
        function login($company, $loginname, $password)
        {
                global $security_areas, $security_groups, $security_headings, $path_to_root;
-               
+
                $this->set_company($company);
            $this->logged = false;
 
@@ -73,6 +73,8 @@ class current_user
                if (!isset($Auth_Result))       // if not used: standard method
                        $Auth_Result = get_user_auth($loginname, md5($password));
 
+               write_login_filelog($loginname, $Auth_Result);
+
                if ($Auth_Result)
                {
                        $myrow = get_user_by_login($loginname);
index 81e743c8fbc0757a621139c729837b9c5bfe5b8b..982616c46c7d9f161e61edd86de2c5fb8a57316d 100644 (file)
@@ -140,6 +140,66 @@ function login_fail()
        kill_login();
        die();
 }
+//----------------------------------------------------------------------------------------
+// set to reasonable values if not set in config file (pre-2.3.12 installations)
+
+if (!isset($login_delay))
+{
+       $login_delay = 10;
+       $login_max_attempts = 3;
+}
+
+function check_faillog()
+{
+       global $login_delay, $login_faillog, $login_max_attempts;
+
+       $user = $_SESSION["wa_current_user"]->user;
+
+       if (@$login_delay && ($login_faillog[$user][$_SERVER['REMOTE_ADDR']] >= @$login_max_attempts) && (time() < $login_faillog[$user]['last'] + $login_delay))
+               return true;
+
+       return false;
+}
+/*
+       Simple brute force attack detection is performed before connection to company database is open. Therefore access counters have to be stored in file.
+       Login attempts counter is created for every new user IP, which partialy prevent DOS attacks.
+*/
+function write_login_filelog($login, $result)
+{
+       global $login_faillog, $login_max_attempts, $path_to_root;
+
+       $user = $_SESSION["wa_current_user"]->user;
+
+       $ip = $_SERVER['REMOTE_ADDR'];
+
+       if (!isset($login_faillog[$user][$ip]) || $result) // init or reset on successfull login
+               $login_faillog[$user] = array($ip => 0, 'last' => '');
+
+       if (!$result)
+       {
+               if ($login_faillog[$user][$ip] < @$login_max_attempts) {
+
+                       $login_faillog[$user][$ip]++;
+               } else {
+                       $login_faillog[$user][$ip] = 0; // comment out to restart counter only after successfull login.
+                       error_log(sprintf(_("Brute force attack on account '%s' detected. Access for non-logged users temporarily blocked."     ), $login));
+               }
+               $login_faillog[$user]['last'] = time();
+       }
+
+       $msg = "<?php\n";
+       $msg .= "/*\n";
+       $msg .= "Login attempts info.\n";
+       $msg .= "*/\n";
+       $msg .= "\$login_faillog = " .var_export($login_faillog, true). ";\n";
+
+       $filename = $path_to_root."/faillog.php";
+
+       if ((!file_exists($filename) && is_writable($path_to_root)) || is_writable($filename))
+       {
+               file_put_contents($filename, $msg);
+       }
+}
 
 //----------------------------------------------------------------------------------------
 
@@ -271,6 +331,7 @@ include_once($path_to_root . "/frontaccounting.php");
 include_once($path_to_root . "/admin/db/security_db.inc");
 include_once($path_to_root . "/includes/lang/language.php");
 include_once($path_to_root . "/config_db.php");
+@include_once($path_to_root . "/faillog.php");
 include_once($path_to_root . "/includes/ajax.inc");
 include_once($path_to_root . "/includes/ui/ui_msgs.inc");
 include_once($path_to_root . "/includes/prefs/sysprefs.inc");