[ADDED] Simple CSRF protection tokens

* Adds config options for disabling, timeout lead time, and forms
 * Adds another salt in config that's used in the token
 * Adds protection for login form by default
This commit is contained in:
xisi 2014-01-15 19:32:33 -05:00
parent bae30b2e4f
commit 2d0938b35b
10 changed files with 90 additions and 15 deletions

6
.gitignore vendored
View File

@ -17,6 +17,6 @@ public/include/config/global.inc.sha.php
# IDE Settings # IDE Settings
/.idea/* /.idea/*
/.buildpath/* .buildpath
/.project/* .project
/.settings/* .settings

View File

@ -31,6 +31,9 @@ class Base {
public function setSalt($salt) { public function setSalt($salt) {
$this->salt = $salt; $this->salt = $salt;
} }
public function setSalty($salt) {
$this->salty = $salt;
}
public function setSmarty($smarty) { public function setSmarty($smarty) {
$this->smarty = $smarty; $this->smarty = $smarty;
} }

View File

@ -788,6 +788,27 @@ class User extends Base {
if ($logout == true) $this->logoutUser($_SERVER['REQUEST_URI']); if ($logout == true) $this->logoutUser($_SERVER['REQUEST_URI']);
return false; return false;
} }
/**
* Gets the current CSRF token for this user/type setting and time chunk
* @param string User; for hash seed, if username isn't available use IP
* @param string Type of token; for hash seed, should be unique per page/use
* @return string CSRF token
*/
public function getCSRFToken($user, $type) {
$date = date('m/d/y/H/i/s');
$data = explode('/', $date);
$month = $data[0]; $day = $data[1]; $year = $data[2];
$hour = $data[3]; $minute = $data[4]; $second = $data[5];
$seed = $this->salty;
// X second lead time on each minute
if ($minute == 59 && $second > (60-$this->config['csrf']['options']['leadtime'])) {
$minute = 0;
$fhour = ($hour == 23) ? $hour = 0 : $hour+=1;
}
$seed = $seed.$month.$day.$user.$type.$year.$hour.$minute.$seed;
return $this->getHash($seed);
}
} }
// Make our class available automatically // Make our class available automatically
@ -795,6 +816,7 @@ $user = new User();
$user->setDebug($debug); $user->setDebug($debug);
$user->setMysql($mysqli); $user->setMysql($mysqli);
$user->setSalt(SALT); $user->setSalt(SALT);
$user->setSalty(SALTY);
$user->setSmarty($smarty); $user->setSmarty($smarty);
$user->setConfig($config); $user->setConfig($config);
$user->setMail($mail); $user->setMail($mail);

View File

@ -26,6 +26,7 @@ define('DEBUG', 0);
// SALT used to hash passwords // SALT used to hash passwords
define('SALT', 'PLEASEMAKEMESOMETHINGRANDOM'); define('SALT', 'PLEASEMAKEMESOMETHINGRANDOM');
define('SALTY', 'THISSHOULDALSOBERRAANNDDOOM');
/** /**
* Underlying coin algorithm that you are mining on. Set this to whatever your coin needs: * Underlying coin algorithm that you are mining on. Set this to whatever your coin needs:
@ -123,6 +124,28 @@ $config['twofactor']['options']['details'] = true;
$config['twofactor']['options']['withdraw'] = true; $config['twofactor']['options']['withdraw'] = true;
$config['twofactor']['options']['changepw'] = true; $config['twofactor']['options']['changepw'] = true;
/**
* CSRF protection config
*
* Explanation:
* To help protect against CSRF, we can generate a hash that changes every minute
* and is unique for each user/IP and page or use, and check against that when
* the form is submitted.
*
* Options:
* enabled = Whether or not we will generate/check for valid CSRF tokens
* leadtime = 1 minute + leadtime seconds for reseeding tokens
* login = Use and check CSRF tokens for the login forms
*
* Default:
* enabled = true
* leadtime = 3
* login = true
*/
$config['csrf']['enabled'] = true;
$config['csrf']['options']['leadtime'] = 3;
$config['csrf']['forms']['login'] = true;
/** /**
* Lock account after maximum failed logins * Lock account after maximum failed logins
* *

View File

@ -4,9 +4,8 @@
if (!defined('SECURITY')) if (!defined('SECURITY'))
die('Hacking attempt'); die('Hacking attempt');
// twofactor stuff
$cp_editable = $wf_editable = $ea_editable = $wf_sent = $ea_sent = $cp_sent = 0; $cp_editable = $wf_editable = $ea_editable = $wf_sent = $ea_sent = $cp_sent = 0;
// stupid hack to fix input when an error happened with a valid token
$ea_token = (!isset($_POST['ea_token'])) ? '' : $_POST['ea_token']; $ea_token = (!isset($_POST['ea_token'])) ? '' : $_POST['ea_token'];
$cp_token = (!isset($_POST['cp_token'])) ? '' : $_POST['cp_token']; $cp_token = (!isset($_POST['cp_token'])) ? '' : $_POST['cp_token'];
$wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token']; $wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token'];
@ -80,7 +79,7 @@ if ($user->isAuthenticated()) {
} }
} }
} else { } else {
// back to get, was only post to fix for stupid hack // back to get, was only post to fix for old token
$ea_token = (!isset($_GET['ea_token'])) ? '' : $_GET['ea_token']; $ea_token = (!isset($_GET['ea_token'])) ? '' : $_GET['ea_token'];
$cp_token = (!isset($_GET['cp_token'])) ? '' : $_GET['cp_token']; $cp_token = (!isset($_GET['cp_token'])) ? '' : $_GET['cp_token'];
$wf_token = (!isset($_GET['wf_token'])) ? '' : $_GET['wf_token']; $wf_token = (!isset($_GET['wf_token'])) ? '' : $_GET['wf_token'];
@ -137,7 +136,6 @@ if ($user->isAuthenticated()) {
} }
// 2fa - one last time so we can sync with changes we made during this page // 2fa - one last time so we can sync with changes we made during this page
if ($user->isAuthenticated() && $config['twofactor']['enabled']) { if ($user->isAuthenticated() && $config['twofactor']['enabled']) {
// stupid hack part deux
// set the token to be the old token so we still have it if it errors out // set the token to be the old token so we still have it if it errors out
if ($old_token_type == 5) { if ($old_token_type == 5) {
$ea_token = $old_token; $ea_token = $old_token;

View File

@ -22,7 +22,12 @@ if (!$smarty->isCached('master.tpl', $smarty_cache_key)) {
} else { } else {
$debug->append('Using cached page', 3); $debug->append('Using cached page', 3);
} }
// csrf token - update if it's enabled
$token = '';
if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) {
$token = $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login');
}
// Load news entries for Desktop site and unauthenticated users // Load news entries for Desktop site and unauthenticated users
$smarty->assign("CONTENT", "default.tpl"); $smarty->assign("CONTENT", "default.tpl");
$smarty->assign('CTOKEN', $token);
?> ?>

View File

@ -5,16 +5,37 @@ if (!defined('SECURITY')) die('Hacking attempt');
if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserId($_POST['username']))) { if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserId($_POST['username']))) {
$_SESSION['POPUP'][] = array('CONTENT' => 'You are not allowed to login during maintenace.', 'TYPE' => 'info'); $_SESSION['POPUP'][] = array('CONTENT' => 'You are not allowed to login during maintenace.', 'TYPE' => 'info');
} else if ($user->checkLogin(@$_POST['username'], @$_POST['password']) ) { } else if (isset($_POST['username']) && isset($_POST['password'])) {
empty($_POST['to']) ? $to = $_SERVER['SCRIPT_NAME'] : $to = $_POST['to']; $nocsrf = 1;
$port = ($_SERVER["SERVER_PORT"] == "80" or $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) {
$location = @$_SERVER['HTTPS'] === true ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $to : 'http://' . $_SERVER['SERVER_NAME'] . $port . $to; if ((isset($_POST['ctoken']) && $_POST['ctoken'] !== $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login')) || (!isset($_POST['ctoken']))) {
if (!headers_sent()) header('Location: ' . $location); // csrf protection is on and this token is invalid, error out -> time expired
exit('<meta http-equiv="refresh" content="0; url=' . htmlspecialchars($location) . '"/>'); $nocsrf = 0;
}
}
if ($nocsrf == 1 || (!$config['csrf']['enabled'] || !$config['csrf']['forms']['login'])) {
$checklogin = $user->checkLogin($_POST['username'], $_POST['password']);
if ($checklogin) {
empty($_POST['to']) ? $to = $_SERVER['SCRIPT_NAME'] : $to = $_POST['to'];
$port = ($_SERVER["SERVER_PORT"] == "80" or $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]);
$location = @$_SERVER['HTTPS'] === true ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $to : 'http://' . $_SERVER['SERVER_NAME'] . $port . $to;
if (!headers_sent()) header('Location: ' . $location);
exit('<meta http-equiv="refresh" content="0; url=' . htmlspecialchars($location) . '"/>');
} else {
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: '. $user->getError(), 'TYPE' => 'errormsg');
}
} else {
$_SESSION['POPUP'][] = array('CONTENT' => 'Login token expired'. $user->getError(), 'TYPE' => 'errormsg');
}
} else if (@$_POST['username'] && @$_POST['password']) { } else if (@$_POST['username'] && @$_POST['password']) {
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: '. $user->getError(), 'TYPE' => 'errormsg'); $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: '. $user->getError(), 'TYPE' => 'errormsg');
} }
// csrf token - update if it's enabled
$token = '';
if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) {
$token = $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login');
}
// Load login template // Load login template
$smarty->assign('CONTENT', 'default.tpl'); $smarty->assign('CONTENT', 'default.tpl');
$smarty->assign('CTOKEN', $token);
?> ?>

View File

@ -65,6 +65,7 @@ $aGlobal = array(
'reward' => $config['reward'], 'reward' => $config['reward'],
'price' => $setting->getValue('price'), 'price' => $setting->getValue('price'),
'twofactor' => $config['twofactor'], 'twofactor' => $config['twofactor'],
'csrf' => $config['csrf'],
'config' => array( 'config' => array(
'disable_navbar' => $setting->getValue('disable_navbar'), 'disable_navbar' => $setting->getValue('disable_navbar'),
'disable_navbar_api' => $setting->getValue('disable_navbar_api'), 'disable_navbar_api' => $setting->getValue('disable_navbar_api'),

View File

@ -1,6 +1,7 @@
<article class="module width_half"> <article class="module width_half">
<form action="{$smarty.server.SCRIPT_NAME}?page=login" method="post" id="loginForm"> <form action="{$smarty.server.SCRIPT_NAME}?page=login" method="post" id="loginForm">
<input type="hidden" name="to" value="{($smarty.request.to|default:"{$smarty.server.SCRIPT_NAME}?page=dashboard")|escape}" /> <input type="hidden" name="to" value="{($smarty.request.to|default:"{$smarty.server.SCRIPT_NAME}?page=dashboard")|escape}" />
{if $GLOBAL.csrf.enabled && $GLOBAL.csrf.forms.login}<input type="hidden" name="ctoken" value="{$CTOKEN}" />{/if}
<header><h3>Login with existing account</h3></header> <header><h3>Login with existing account</h3></header>
<div class="module_content"> <div class="module_content">
<fieldset> <fieldset>

View File

@ -2,6 +2,7 @@
<div class="login_small"> <div class="login_small">
<form action="{$smarty.server.SCRIPT_NAME}" method="post" id="loginForm"> <form action="{$smarty.server.SCRIPT_NAME}" method="post" id="loginForm">
<input type="hidden" name="page" value="login" /> <input type="hidden" name="page" value="login" />
{if $GLOBAL.csrf.enabled && $GLOBAL.csrf.forms.login}<input type="hidden" name="ctoken" value="{$CTOKEN}" />{/if}
<input type="hidden" name="to" value="{$smarty.server.SCRIPT_NAME}?page=dashboard" /> <input type="hidden" name="to" value="{$smarty.server.SCRIPT_NAME}?page=dashboard" />
<fieldset2 class="small"> <fieldset2 class="small">
<label>Username</label> <label>Username</label>