From 504831fe4d5c21b1dafbd7e8cdde7607592c8fb7 Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Wed, 11 Feb 2015 13:47:16 -0600 Subject: [PATCH] login: Require CSRF token to login This patch fixes a vulnerable scenario, where sequential login attempts can be made without an existing session, and without a valid CSRF token. This scenario lends itself well for brute force password attempts, because attackers can avoid using a session and still send requests to determine if a set of credentials are valid. This vector also avoids the authentication lockout mechanism, because it requires an ongoing session to shutdown the requests. This patch addresses the issue by requiring a session and a valid CSRF token generated by the server and placed in the session to be submitted with the credentials. Therefore, an existing session and a Cookie header are required to process a login attempt. Secondly, the CSRF token will be changed on the server after each login processed. Therefore, for each session, a subsequent GET request would be necessary before submitting another login attempt. --- include/class.csrf.php | 11 +++++++---- login.php | 14 ++++++++++++++ scp/login.php | 10 ++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/include/class.csrf.php b/include/class.csrf.php index 94f103cb8..a1c3aed21 100644 --- a/include/class.csrf.php +++ b/include/class.csrf.php @@ -53,12 +53,15 @@ Class CSRF { return $this->name; } - function getToken() { + function rotate() { + $this->csrf['token'] = sha1(session_id().Crypto::random(16).SECRET_SALT); + $this->csrf['time'] = time(); + } - if(!$this->csrf['token'] || $this->isExpired()) { + function getToken() { - $this->csrf['token'] = sha1(session_id().Crypto::random(16).SECRET_SALT); - $this->csrf['time'] = time(); + if (!$this->csrf['token'] || $this->isExpired()) { + $this->rotate(); } else { //Reset the timer $this->csrf['time'] = time(); diff --git a/login.php b/login.php index f79bf8563..a90376ec3 100644 --- a/login.php +++ b/login.php @@ -31,6 +31,20 @@ else $inc = 'login.inc.php'; $suggest_pwreset = false; + +// Check the CSRF token, and ensure that future requests will have to use a +// different CSRF token. This will help ward off both parallel and serial +// brute force attacks, because new tokens will have to be requested for +// each attempt. +if ($_POST) { + // Check CSRF token + if (!$ost->checkCSRFToken()) + Http::response(400, __('Valid CSRF Token Required')); + + // Rotate the CSRF token (original cannot be reused) + $ost->getCSRF()->rotate(); +} + if ($_POST && isset($_POST['luser'])) { if (!$_POST['luser']) $errors['err'] = __('Valid username or email address is required'); diff --git a/scp/login.php b/scp/login.php index ad07a831b..bbf8dc7a3 100644 --- a/scp/login.php +++ b/scp/login.php @@ -31,6 +31,16 @@ $msg = $msg ?: ($content ? $content->getName() : __('Authentication Required')); $dest=($dest && (!strstr($dest,'login.php') && !strstr($dest,'ajax.php')))?$dest:'index.php'; $show_reset = false; if($_POST) { + // Check the CSRF token, and ensure that future requests will have to + // use a different CSRF token. This will help ward off both parallel and + // serial brute force attacks, because new tokens will have to be + // requested for each attempt. + if (!$ost->checkCSRFToken()) + Http::response(400, __('Valid CSRF Token Required')); + + // Rotate the CSRF token (original cannot be reused) + $ost->getCSRF()->rotate(); + // Lookup support backends for this staff $username = trim($_POST['userid']); if ($user = StaffAuthenticationBackend::process($username, -- GitLab