{
$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";
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=' "._("Login -->")." ' 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
class current_user
{
- var $user;
+ var $user = 0;
var $loginname;
var $username;
var $name;
function login($company, $loginname, $password)
{
global $security_areas, $security_groups, $security_headings, $path_to_root;
-
+
$this->set_company($company);
$this->logged = false;
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);
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);
+ }
+}
//----------------------------------------------------------------------------------------
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");