From 8df5cb39c17392a1aeba0d9e96bc44dda3e9390f Mon Sep 17 00:00:00 2001 From: "nils@teampass.net" Date: Fri, 20 May 2016 22:10:52 +0200 Subject: [PATCH] 2.1.26 Fix for #1295, #1298 --- includes/libraries/csrfp/js/csrfprotector.js | 91 +- .../csrfp/libs/csrf/csrfpJsFileBase.php | 314 ------ .../csrfp/libs/csrf/csrfprotector.php | 954 ++++++++++-------- sources/admin.queries.php | 18 +- sources/identify.php | 2 +- sources/main.functions.php | 23 +- 6 files changed, 591 insertions(+), 811 deletions(-) delete mode 100644 includes/libraries/csrfp/libs/csrf/csrfpJsFileBase.php diff --git a/includes/libraries/csrfp/js/csrfprotector.js b/includes/libraries/csrfp/js/csrfprotector.js index 7616d92d2..395293ca4 100644 --- a/includes/libraries/csrfp/js/csrfprotector.js +++ b/includes/libraries/csrfp/js/csrfprotector.js @@ -11,6 +11,9 @@ * ================================================================= */ +var CSRFP_FIELD_TOKEN_NAME = 'csrfp_hidden_data_token'; +var CSRFP_FIELD_URLS = 'csrfp_hidden_data_urls'; + var CSRFP = { CSRFP_TOKEN: 'c42f1a870f5a58088dbfaea2f9788d9f967ecfa64251de294d', /** @@ -140,6 +143,14 @@ var CSRFP = { * @return void */ _init: function() { + CSRFP.CSRFP_TOKEN = document.getElementById(CSRFP_FIELD_TOKEN_NAME).value; + try { + CSRFP.checkForUrls = JSON.parse(document.getElementById(CSRFP_FIELD_URLS).value); + } catch (err) { + console.error(err); + console.error('[ERROR] [CSRF Protector] unable to parse blacklisted url fields.'); + } + //convert these rules received from php lib to regex objects for (var i = 0; i < CSRFP.checkForUrls.length; i++) { CSRFP.checkForUrls[i] = CSRFP.checkForUrls[i].replace(/\*/g, '(.*)') @@ -247,14 +258,16 @@ function csrfprotector_init() { */ function new_send(data) { if (this.method.toLowerCase() === 'post') { - - if (data !== "") { - data += "&"; + if (data !== null && typeof data === 'object') { + data.append(CSRFP.CSRFP_TOKEN, CSRFP._getAuthKey()); } else { - data = ""; + if (typeof data != "undefined") { + data += "&"; + } else { + data = ""; + } + data += CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); } - - data += CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); } return this.old_send(data); } @@ -276,39 +289,47 @@ function csrfprotector_init() { // Rewrite existing urls ( Attach CSRF token ) // Rules: // Rewrite those urls which matches the regex sent by Server - // Ingore cross origin urls & internal links (one with hashtags) + // Ignore cross origin urls & internal links (one with hashtags) // Append the token to those url already containig GET query parameter(s) // Add the token to those which does not contain GET query parameter(s) //================================================================== for (var i = 0; i < document.links.length; i++) { - document.links[i].addEventListener("mousedown", function(event) { - var urlDisect = event.target.href.split('#'); - var url = urlDisect[0]; - var hash = urlDisect[1]; - - if(CSRFP._getDomain(url).indexOf(document.domain) === -1 - || CSRFP._isValidGetRequest(url)) { - //cross origin or not to be protected by rules -- ignore - return; - } - - if (url.indexOf('?') !== -1) { - if(url.indexOf(CSRFP.CSRFP_TOKEN) === -1) { - url += "&" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); - } else { - url = url.replace(new RegExp(CSRFP.CSRFP_TOKEN +"=.*?(&|$)", 'g'), - CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey() + "$1"); - } - } else { - url += "?" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); - } - - event.target.href = url; - if (typeof hash !== 'undefined') { - event.target.href += '#' +hash; - } - }); + document.links[i].addEventListener("mousedown", function(event) { + var href = event.target.href; + if(typeof href === "string") + { + var urlDisect = href.split('#'); + var url = urlDisect[0]; + var hash = urlDisect[1]; + + if(CSRFP._getDomain(url).indexOf(document.domain) === -1 + || CSRFP._isValidGetRequest(url)) { + //cross origin or not to be protected by rules -- ignore + return; + } + + if (url.indexOf('?') !== -1) { + if(url.indexOf(CSRFP.CSRFP_TOKEN) === -1) { + url += "&" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); + } else { + url = url.replace(new RegExp(CSRFP.CSRFP_TOKEN +"=.*?(&|$)", 'g'), + CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey() + "$1"); + } + } else { + url += "?" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); + } + + event.target.href = url; + if (typeof hash !== 'undefined') { + event.target.href += '#' +hash; + } + } + }); } -} \ No newline at end of file +} + +window.addEventListener("DOMContentLoaded", function() { + csrfprotector_init(); +}, false); diff --git a/includes/libraries/csrfp/libs/csrf/csrfpJsFileBase.php b/includes/libraries/csrfp/libs/csrf/csrfpJsFileBase.php deleted file mode 100644 index 406490e82..000000000 --- a/includes/libraries/csrfp/libs/csrf/csrfpJsFileBase.php +++ /dev/null @@ -1,314 +0,0 @@ -/** - * ================================================================= - * Javascript code for OWASP CSRF Protector - * Task it does: Fetch csrftoken from cookie, and attach it to every - * POST request - * Allowed GET url - * -- XHR - * -- Static Forms - * -- URLS (GET only) - * -- dynamic forms - * ================================================================= - */ - -var CSRFP = { - CSRFP_TOKEN: '$$tokenName$$', - /** - * Array of patterns of url, for which csrftoken need to be added - * In case of GET request also, provided from server - * - * @var string array - */ - checkForUrls: [$$getAllowedUrls$$], - /** - * Function to check if a certain url is allowed to perform the request - * With or without csrf token - * - * @param: string, url - * - * @return: boolean, true if csrftoken is not needed - * false if csrftoken is needed - */ - _isValidGetRequest: function(url) { - for (var i = 0; i < CSRFP.checkForUrls.length; i++) { - var match = CSRFP.checkForUrls[i].exec(url); - if (match !== null && match.length > 0) { - return false; - } - } - return true; - }, - /** - * function to get Auth key from cookie Andreturn it to requesting function - * - * @param: void - * - * @return: string, csrftoken retrieved from cookie - */ - _getAuthKey: function() { - var re = new RegExp(CSRFP.CSRFP_TOKEN +"=([^;]+)(;|$)"); - var RegExpArray = re.exec(document.cookie); - - if (RegExpArray === null) { - return false; - } - return RegExpArray[1]; - }, - /** - * Function to get domain of any url - * - * @param: string, url - * - * @return: string, domain of url - */ - _getDomain: function(url) { - if (url.indexOf("http://") !== 0 - && url.indexOf("https://") !== 0) - return document.domain; - return /http(s)?:\/\/([^\/]+)/.exec(url)[2]; - }, - /** - * Function to create and return a hidden input element - * For stroing the CSRFP_TOKEN - * - * @param void - * - * @return input element - */ - _getInputElt: function() { - var hiddenObj = document.createElement("input"); - hiddenObj.name = CSRFP.CSRFP_TOKEN; - hiddenObj.type = 'hidden'; - hiddenObj.value = CSRFP._getAuthKey(); - return hiddenObj; - }, - /** - * Returns absolute path for relative path - * - * @param base, base url - * @param relative, relative url - * - * @return absolute path (string) - */ - _getAbsolutePath: function(base, relative) { - var stack = base.split("/"); - var parts = relative.split("/"); - // remove current file name (or empty string) - // (omit if "base" is the current folder without trailing slash) - stack.pop(); - - for (var i = 0; i < parts.length; i++) { - if (parts[i] == ".") - continue; - if (parts[i] == "..") - stack.pop(); - else - stack.push(parts[i]); - } - return stack.join("/"); - }, - /** - * Remove jcsrfp-token run fun and then put them back - * - * @param function - * @param reference form obj - * - * @retrun function - */ - _csrfpWrap: function(fun, obj) { - return function(event) { - // Remove CSRf token if exists - if (typeof obj[CSRFP.CSRFP_TOKEN] !== 'undefined') { - var target = obj[CSRFP.CSRFP_TOKEN]; - target.parentNode.removeChild(target); - } - - // Trigger the functions - var result = fun.apply(this, [event]); - - // Now append the csrfp_token back - obj.appendChild(CSRFP._getInputElt()); - - return result; - }; - }, - /** - * Initialises the CSRFProtector js script - * - * @param void - * - * @return void - */ - _init: function() { - //convert these rules received from php lib to regex objects - for (var i = 0; i < CSRFP.checkForUrls.length; i++) { - CSRFP.checkForUrls[i] = CSRFP.checkForUrls[i].replace(/\*/g, '(.*)') - .replace(/\//g, "\\/"); - CSRFP.checkForUrls[i] = new RegExp(CSRFP.checkForUrls[i]); - } - - } - -}; - -//========================================================== -// Adding tokens, wrappers on window onload -//========================================================== - -function csrfprotector_init() { - - // Call the init funcion - CSRFP._init(); - - //================================================================== - // Adding csrftoken to request resulting from
submissions - // Add for each POST, while for mentioned GET request - //================================================================== - for(var i = 0; i < document.forms.length; i++) { - document.forms[i].addEventListener("submit", function(event) { - if (typeof event.target[CSRFP.CSRFP_TOKEN] === 'undefined') { - event.target.appendChild(CSRFP._getInputElt()); - } else { - //modify token to latest value - event.target[CSRFP.CSRFP_TOKEN].value = CSRFP._getAuthKey(); - } - }); - } - - /** - * Add wrapper for HTMLFormElements addEventListener so that any further - * addEventListens won't have trouble with CSRF token - */ - HTMLFormElement.prototype.addEventListener_ = HTMLFormElement.prototype.addEventListener; - HTMLFormElement.prototype.addEventListener = function(eventType, fun, bubble) { - if (eventType === 'submit') { - var wrapped = CSRFP._csrfpWrap(fun, this); - this.addEventListener_(eventType, wrapped, bubble); - } else { - this.addEventListener_(eventType, fun, bubble); - } - } - - /** - * Add wrapper for IE's attachEvent - */ - if (typeof HTMLFormElement.prototype.attachEvent !== 'undefined') { - HTMLFormElement.prototype.attachEvent_ = HTMLFormElement.prototype.attachEvent; - HTMLFormElement.prototype.attachEvent = function(eventType, fun) { - if (eventType === 'submit') { - var wrapped = CSRFP._csrfpWrap(fun, this); - this.attachEvent_(eventType, wrapped); - } else { - this.attachEvent_(eventType, fun); - } - } - } - - - //================================================================== - // Wrapper for XMLHttpRequest & ActiveXObject (for IE 6 & below) - // Set X-No-CSRF to true before sending if request method is - //================================================================== - - /** - * Wrapper to XHR open method - * Add a property method to XMLHttpRequst class - * @param: all parameters to XHR open method - * @return: object returned by default, XHR open method - */ - function new_open(method, url, async, username, password) { - this.method = method; - var isAbsolute = (url.indexOf("./") === -1) ? true : false; - if (!isAbsolute) { - var base = location.protocol +'//' +location.host - + location.pathname; - url = CSRFP._getAbsolutePath(base, url); - } - if (method.toLowerCase() === 'get' - && !CSRFP._isValidGetRequest(url)) { - //modify the url - if (url.indexOf('?') === -1) { - url += "?" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); - } else { - url += "&" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); - } - } - - return this.old_open(method, url, async, username, password); - } - - /** - * Wrapper to XHR send method - * Add query paramter to XHR object - * - * @param: all parameters to XHR send method - * - * @return: object returned by default, XHR send method - */ - function new_send(data) { - if (this.method.toLowerCase() === 'post') { - - if (data !== "") { - data += "&"; - } else { - data = ""; - } - - data += CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); - } - return this.old_send(data); - } - - if (window.XMLHttpRequest) { - // Wrapping - XMLHttpRequest.prototype.old_send = XMLHttpRequest.prototype.send; - XMLHttpRequest.prototype.old_open = XMLHttpRequest.prototype.open; - XMLHttpRequest.prototype.open = new_open; - XMLHttpRequest.prototype.send = new_send; - } - if (typeof ActiveXObject !== 'undefined') { - ActiveXObject.prototype.old_send = ActiveXObject.prototype.send; - ActiveXObject.prototype.old_open = ActiveXObject.prototype.open; - ActiveXObject.prototype.open = new_open; - ActiveXObject.prototype.send = new_send; - } - //================================================================== - // Rewrite existing urls ( Attach CSRF token ) - // Rules: - // Rewrite those urls which matches the regex sent by Server - // Ingore cross origin urls & internal links (one with hashtags) - // Append the token to those url already containig GET query parameter(s) - // Add the token to those which does not contain GET query parameter(s) - //================================================================== - - for (var i = 0; i < document.links.length; i++) { - document.links[i].addEventListener("mousedown", function(event) { - var urlDisect = event.target.href.split('#'); - var url = urlDisect[0]; - var hash = urlDisect[1]; - - if(CSRFP._getDomain(url).indexOf(document.domain) === -1 - || CSRFP._isValidGetRequest(url)) { - //cross origin or not to be protected by rules -- ignore - return; - } - - if (url.indexOf('?') !== -1) { - if(url.indexOf(CSRFP.CSRFP_TOKEN) === -1) { - url += "&" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); - } else { - url = url.replace(new RegExp(CSRFP.CSRFP_TOKEN +"=.*?(&|$)", 'g'), - CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey() + "$1"); - } - } else { - url += "?" +CSRFP.CSRFP_TOKEN +"=" +CSRFP._getAuthKey(); - } - - event.target.href = url; - if (typeof hash !== 'undefined') { - event.target.href += '#' +hash; - } - }); - } - -} \ No newline at end of file diff --git a/includes/libraries/csrfp/libs/csrf/csrfprotector.php b/includes/libraries/csrfp/libs/csrf/csrfprotector.php index 9156d44cc..a9861d26e 100644 --- a/includes/libraries/csrfp/libs/csrf/csrfprotector.php +++ b/includes/libraries/csrfp/libs/csrf/csrfprotector.php @@ -1,446 +1,514 @@ action to be taken in case autherisation fails - * @property #2: logDirectory (string) => directory in which log will be saved - * @property #3: customErrorMessage (string) => custom error message to be sent in case - * of failed authentication - * @property #4: jsFile (string) => location of the CSRFProtector js file - * @property #5: tokenLength (int) => default length of hash - * @property #6: disabledJavascriptMessage (string) => error message if client's js is disabled - */ - public static $config = array(); - - /** - * function to initialise the csrfProtector work flow - * @parameters: variables to override default configuration loaded from file - * - * @param $length - length of CSRF_AUTH_TOKEN to be generated - * @param $action - int array, for different actions to be taken in case of failed validation - * - * @return void - * - * @throw configFileNotFoundException - */ - public static function init($length = null, $action = null) - { - /** - * if mod_csrfp already enabled, no verification, no filtering - * Already done by mod_csrfp - */ - if (getenv('mod_csrfp_enabled')) { - return; - } - - //start session in case its not - if (session_id() == '') { - require_once('sources/sessions.php'); - session_start(); - } - - if (!file_exists(__DIR__ ."/../csrfp.config.php")) { - throw new configFileNotFoundException("configuration file not found for CSRFProtector!"); - } - - //load configuration file and properties - self::$config = include(__DIR__ ."/../csrfp.config.php"); - - //overriding length property if passed in parameters - if ($length !== null) { - self::$config['tokenLength'] = intval($length); - } - - //action that is needed to be taken in case of failed authorisation - if ($action !== null) { - self::$config['failedAuthAction'] = $action; - } - - if (self::$config['CSRFP_TOKEN'] == '') - self::$config['CSRFP_TOKEN'] = CSRFP_TOKEN; - - //authorise the incoming request - self::authorisePost(); - - // Initialize output buffering handler - ob_start('csrfProtector::ob_handler'); - - if (!isset($_COOKIE[self::$config['CSRFP_TOKEN']]) - || !isset($_SESSION[self::$config['CSRFP_TOKEN']])) - self::refreshToken(); - } - - /** - * Function to check weather to use cached version of js - * file or not - * - * @param void - * - * @return, bool -- true if cacheversion can be used - * -- false otherwise - */ - public static function useCachedVersion() - { - $configLastModified = filemtime(__DIR__ ."/../csrfp.config.php"); - if (file_exists(__DIR__ ."/../" .self::$config['jsPath'])) { - $jsFileLastModified = filemtime(__DIR__ ."/../" - .self::$config['jsPath']); - if ($jsFileLastModified < $configLastModified) { - // -- config is more recent than js file - return false; - } - return true; - } else { - return false; - } - - } - - /** - * Function to create new cache version of js - * - * @param void - * - * @return void - * - * @throw baseJSFileNotFoundExceptio - */ - public static function createNewJsCache() - { - if (!file_exists(__DIR__ ."/csrfpJsFileBase.php")) { - throw new baseJSFileNotFoundExceptio("base js file needed to create js file not found at " .__DIR__); - return;; - } - - $jsFile = file_get_contents(__DIR__ ."/csrfpJsFileBase.php"); - $arrayStr = ''; - if (self::$config['verifyGetFor']) { - foreach (self::$config['verifyGetFor'] as $key => $value) { - if ($key !== 0) $arrayStr .= ','; - $arrayStr .= "'". $value ."'"; - } - } - $jsFile = str_replace('$$tokenName$$', self::$config['CSRFP_TOKEN'], $jsFile); - $jsFile = str_replace('$$getAllowedUrls$$', $arrayStr, $jsFile); - file_put_contents(__DIR__ ."/../" .self::$config['jsPath'], $jsFile); - } - - /** - * function to authorise incoming post requests - * @param void - * @return void - * @throw logDirectoryNotFoundException - */ - public static function authorisePost() - { - //#todo this method is valid for same origin request only, - //enable it for cross origin also sometime - //for cross origin the functionality is different - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - - //set request type to POST - self::$requestType = "POST"; - - //currently for same origin only - if (!(isset($_POST[self::$config['CSRFP_TOKEN']]) - && isset($_SESSION[self::$config['CSRFP_TOKEN']]) - && ($_POST[self::$config['CSRFP_TOKEN']] === $_SESSION[self::$config['CSRFP_TOKEN']]) - )) { - - //action in case of failed validation - self::failedValidationAction(); - } else { - self::refreshToken(); //refresh token for successfull validation - } - } else if (!static::isURLallowed()) { - - //currently for same origin only - if (!(isset($_GET[self::$config['CSRFP_TOKEN']]) - && isset($_SESSION[self::$config['CSRFP_TOKEN']]) - && ($_GET[self::$config['CSRFP_TOKEN']] === $_SESSION[self::$config['CSRFP_TOKEN']]) - )) { - - //action in case of failed validation - self::failedValidationAction(); - } else { - self::refreshToken(); //refresh token for successfull validation - } - } - } - - /** - * function to be called in case of failed validation - * performs logging and take appropriate action - * @param: void - * @return: void - */ - private static function failedValidationAction() - { - if (!file_exists(__DIR__ ."/../" .self::$config['logDirectory'])) { - throw new logDirectoryNotFoundException("Log Directory Not Found!"); - } - - //call the logging function - static::logCSRFattack(); - - //#todo: ask mentors if $failedAuthAction is better as an int or string - //default case is case 0 - switch (self::$config['failedAuthAction'][self::$requestType]) { - case 0: - //send 403 header - header('HTTP/1.0 403 Forbidden'); - exit("

403 Access Forbidden by CSRFProtector!

"); - break; - case 1: - //unset the query parameters and forward - if (self::$requestType === 'GET') { - $_GET = array(); - } else { - $_POST = array(); - } - break; - case 2: - //redirect to custom error page - $location = self::$config['errorRedirectionPage']; - header("location: $location"); - case 3: - //send custom error message - exit(self::$config['customErrorMessage']); - break; - case 4: - //send 500 header -- internal server error - header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); - exit("

500 Internal Server Error!

"); - break; - default: - //unset the query parameters and forward - if (self::$requestType === 'GET') { - $_GET = array(); - } else { - $_POST = array(); - } - break; - } - } - - /** - * function to set auth cookie - * @param: void - * @return void - */ - public static function refreshToken() - { - $token = self::generateAuthToken(); - - //set token to session for server side validation - $_SESSION[self::$config['CSRFP_TOKEN']] = $token; - - //set token to cookie for client side processing - setcookie(self::$config['CSRFP_TOKEN'], - $token, - time() + self::$cookieExpiryTime); - } - - /** - * function to generate random hash of length as given in parameter - * max length = 128 - * @param: length to hash required, int - * @return string - */ - public static function generateAuthToken() - { - //if config tokenLength value is 0 or some non int - if (intval(self::$config['tokenLength']) === 0) { - self::$config['tokenLength'] = 32; //set as default - } - - //if $length > 128 throw exception #todo - - if (function_exists("hash_algos") && in_array("sha512", hash_algos())) { - $token = hash("sha512", mt_rand(0, mt_getrandmax())); - } else { - $token = ''; - for ($i = 0; $i < 128; ++$i) { - $r = mt_rand(0, 35); - if ($r < 26) { - $c = chr(ord('a') + $r); - } else { - $c = chr(ord('0') + $r - 26); - } - $token .= $c; - } - } - return substr($token, 0, self::$config['tokenLength']); - } - - /** - * Rewrites on the fly to add CSRF tokens to them. This can also - * inject our JavaScript library. - * @param: $buffer, output buffer to which all output are stored - * @param: flag - * @return string, complete output buffer - */ - public static function ob_handler($buffer, $flags) - { - // Even though the user told us to rewrite, we should do a quick heuristic - // to check if the page is *actually* HTML. We don't begin rewriting until - // we hit the first message to outgoing HTML output, - //informing the user to enable js for CSRFProtector to work - //best section to add, after tag - $buffer = preg_replace("/]*>/", "$0 ", $buffer); - - $arrayStr = ''; - if (!self::useCachedVersion()) { - try { - self::createNewJsCache(); - } catch (exception $ex) { - if (self::$config['verifyGetFor']) { - foreach (self::$config['verifyGetFor'] as $key => $value) { - if ($key !== 0) $arrayStr .= ','; - $arrayStr .= "'". $value ."'"; - } - } - } - } +if (!defined('__CSRF_PROTECTOR__')) { + define('__CSRF_PROTECTOR__', true); // to avoid multiple declaration errors + + // name of HTTP POST variable for authentication + define("CSRFP_TOKEN","csrfp_token"); + + // We insert token name and list of url patterns for which + // GET requests are validated against CSRF as hidden input fields + // these are the names of the input fields + define("CSRFP_FIELD_TOKEN_NAME", "csrfp_hidden_data_token"); + define("CSRFP_FIELD_URLS", "csrfp_hidden_data_urls"); + + /** + * child exception classes + */ + class configFileNotFoundException extends \exception {}; + class logDirectoryNotFoundException extends \exception {}; + class jsFileNotFoundException extends \exception {}; + class logFileWriteError extends \exception {}; + class baseJSFileNotFoundExceptio extends \exception {}; + class incompleteConfigurationException extends \exception {}; + + class csrfProtector + { + /* + * Variable: $cookieExpiryTime + * expiry time for cookie + * @var int + */ + public static $cookieExpiryTime = 1800; //30 minutes + + /* + * Variable: $isSameOrigin + * flag for cross origin/same origin request + * @var bool + */ + private static $isSameOrigin = true; + + /* + * Variable: $isValidHTML + * flag to check if output file is a valid HTML or not + * @var bool + */ + private static $isValidHTML = false; + + /* + * Variable: $requestType + * Varaible to store weather request type is post or get + * @var string + */ + protected static $requestType = "GET"; + + /* + * Variable: $config + * config file for CSRFProtector + * @var int Array, length = 6 + * Property: #1: failedAuthAction (int) => action to be taken in case autherisation fails + * Property: #2: logDirectory (string) => directory in which log will be saved + * Property: #3: customErrorMessage (string) => custom error message to be sent in case + * of failed authentication + * Property: #4: jsFile (string) => location of the CSRFProtector js file + * Property: #5: tokenLength (int) => default length of hash + * Property: #6: disabledJavascriptMessage (string) => error message if client's js is disabled + */ + public static $config = array(); + + /* + * Variable: $requiredConfigurations + * Contains list of those parameters that are required to be there + * in config file for csrfp to work + */ + public static $requiredConfigurations = array('logDirectory', 'failedAuthAction', 'jsPath', 'jsUrl', 'tokenLength'); - $script = '' .PHP_EOL; - - $script .= '' .PHP_EOL; - - //implant the CSRFGuard js file to outgoing script - $buffer = str_ireplace('', $script . '', $buffer, $count); - if (!$count) { - $buffer .= $script; - } - - return $buffer; - } - - /** - * Functio to log CSRF Attack - * @param: void - * @retrun: void - * @throw: logFileWriteError - */ - private static function logCSRFattack() - { - //if file doesnot exist for, create it - $logFile = fopen(__DIR__ ."/../" .self::$config['logDirectory'] - ."/" .date("m-20y") .".log", "a+"); - - //throw exception if above fopen fails - if (!$logFile) { - throw new logFileWriteError("Unable to write to the log file"); - } - - //miniature version of the log - $log = array(); - $log['timestamp'] = time(); - $log['HOST'] = $_SERVER['HTTP_HOST']; - $log['REQUEST_URI'] = $_SERVER['REQUEST_URI']; - $log['requestType'] = self::$requestType; - - if (self::$requestType === "GET") { - $log['query'] = $_GET; - } else { - $log['query'] = $_POST; - } - - $log['cookie'] = $_COOKIE; - - //convert log array to JSON format to be logged - $log = json_encode($log) .PHP_EOL; - - //append log to the file - fwrite($logFile, $log); - - //close the file handler - fclose($logFile); - } - - /** - * Function to return current url of executing page - * @param: void - * @return: string, current url - */ - private static function getCurrentUrl() - { - return $_SERVER['REQUEST_SCHEME'] .'://' - .$_SERVER['HTTP_HOST'] .$_SERVER['PHP_SELF']; - } - - /** - * Function to check if current url mataches for any urls - * Listed in config file - * @param: void - * @return: boolean, true is url need no validation, false if validation needed - */ - public static function isURLallowed() { - foreach (self::$config['verifyGetFor'] as $key => $value) { - $value = str_replace(array('/','*'), array('\/','(.*)'), $value); - preg_match('/' .$value .'/', self::getCurrentUrl(), $output); - if (count($output) > 0) { - return false; - } - } - return true; - } -}; + /* + * Function: init + * + * function to initialise the csrfProtector work flow + * + * Parameters: + * $length - length of CSRF_AUTH_TOKEN to be generated + * $action - int array, for different actions to be taken in case of failed validation + * + * Returns: + * void + * + * Throws: + * configFileNotFoundException - when configuration file is not found + * incompleteConfigurationException - when all required fields in config + * file are not available + * + */ + public static function init($length = null, $action = null) + { + /* + * if mod_csrfp already enabled, no verification, no filtering + * Already done by mod_csrfp + */ + if (getenv('mod_csrfp_enabled')) + return; + + //start session in case its not + if (session_id() == '') + require_once __DIR__ ."/../../../../../sources/sessions.php"; + session_start(); + + /* + * load configuration file and properties + * Check locally for a config.php then check for + * a config/csrf_config.php file in the root folder + * for composer installations + */ + $standard_config_location = __DIR__ ."/../csrfp.config.php"; + $composer_config_location = __DIR__ ."/../../../../../config/csrf_config.php"; + + if (file_exists($standard_config_location)) { + self::$config = include($standard_config_location); + } elseif(file_exists($composer_config_location)) { + self::$config = include($composer_config_location); + } else { + throw new configFileNotFoundException("OWASP CSRFProtector: configuration file not found for CSRFProtector!"); + } + + //overriding length property if passed in parameters + if ($length != null) + self::$config['tokenLength'] = intval($length); + + //action that is needed to be taken in case of failed authorisation + if ($action != null) + self::$config['failedAuthAction'] = $action; + + if (self::$config['CSRFP_TOKEN'] == '') + self::$config['CSRFP_TOKEN'] = CSRFP_TOKEN; + + // Validate the config if everythings filled out + foreach (self::$requiredConfigurations as $value) { + if (!isset(self::$config[$value]) || self::$config[$value] == '') { + throw new incompleteConfigurationException("OWASP CSRFProtector: Incomplete configuration file!"); + exit; + } + } + + // Authorise the incoming request + self::authorizePost(); + + // Initialize output buffering handler + ob_start('csrfProtector::ob_handler'); + + if (!isset($_COOKIE[self::$config['CSRFP_TOKEN']]) + || !isset($_SESSION[self::$config['CSRFP_TOKEN']]) + || !is_array($_SESSION[self::$config['CSRFP_TOKEN']]) + || !in_array($_COOKIE[self::$config['CSRFP_TOKEN']], + $_SESSION[self::$config['CSRFP_TOKEN']])) + self::refreshToken(); + + // Set protected by CSRF Protector header + header('X-CSRF-Protection: OWASP CSRFP 1.0.0'); + } + + /* + * Function: authorizePost + * function to authorise incoming post requests + * + * Parameters: + * void + * + * Returns: + * void + * + * Throws: + * logDirectoryNotFoundException - if log directory is not found + */ + public static function authorizePost() + { + //#todo this method is valid for same origin request only, + //enable it for cross origin also sometime + //for cross origin the functionality is different + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + //set request type to POST + self::$requestType = "POST"; + + //currently for same origin only + if (!(isset($_POST[self::$config['CSRFP_TOKEN']]) + && isset($_SESSION[self::$config['CSRFP_TOKEN']]) + && (self::isValidToken($_POST[self::$config['CSRFP_TOKEN']])) + )) { + + //action in case of failed validation + self::failedValidationAction(); + } else { + self::refreshToken(); //refresh token for successfull validation + } + } else if (!static::isURLallowed()) { + + //currently for same origin only + if (!(isset($_GET[self::$config['CSRFP_TOKEN']]) + && isset($_SESSION[self::$config['CSRFP_TOKEN']]) + && (self::isValidToken($_GET[self::$config['CSRFP_TOKEN']])) + )) { + + //action in case of failed validation + self::failedValidationAction(); + } else { + self::refreshToken(); //refresh token for successfull validation + } + } + } + + /* + * Function: isValidToken + * function to check the validity of token in session array + * Function also clears all tokens older than latest one + * + * Parameters: + * $token - the token sent with GET or POST payload + * + * Returns: + * bool - true if its valid else false + */ + private static function isValidToken($token) { + if (!isset($_SESSION[self::$config['CSRFP_TOKEN']])) return false; + if (!is_array($_SESSION[self::$config['CSRFP_TOKEN']])) return false; + foreach ($_SESSION[self::$config['CSRFP_TOKEN']] as $key => $value) { + if ($value == $token) { + + // Clear all older tokens assuming they have been consumed + foreach ($_SESSION[self::$config['CSRFP_TOKEN']] as $_key => $_value) { + if ($_value == $token) break; + array_shift($_SESSION[self::$config['CSRFP_TOKEN']]); + } + return true; + } + } + + return false; + } + + /* + * Function: failedValidationAction + * function to be called in case of failed validation + * performs logging and take appropriate action + * + * Parameters: + * void + * + * Returns: + * void + */ + private static function failedValidationAction() + { + if (!file_exists(__DIR__ ."/../" .self::$config['logDirectory'])) + throw new logDirectoryNotFoundException("OWASP CSRFProtector: Log Directory Not Found!"); + + //call the logging function + static::logCSRFattack(); + + //#todo: ask mentors if $failedAuthAction is better as an int or string + //default case is case 0 + switch (self::$config['failedAuthAction'][self::$requestType]) { + case 0: + //send 403 header + header('HTTP/1.0 403 Forbidden'); + exit("

403 Access Forbidden by CSRFProtector!

"); + break; + case 1: + //unset the query parameters and forward + if (self::$requestType === 'GET') { + $_GET = array(); + } else { + $_POST = array(); + } + break; + case 2: + //redirect to custom error page + $location = self::$config['errorRedirectionPage']; + header("location: $location"); + case 3: + //send custom error message + exit(self::$config['customErrorMessage']); + break; + case 4: + //send 500 header -- internal server error + header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); + exit("

500 Internal Server Error!

"); + break; + default: + //unset the query parameters and forward + if (self::$requestType === 'GET') { + $_GET = array(); + } else { + $_POST = array(); + } + break; + } + } + + /* + * Function: refreshToken + * Function to set auth cookie + * + * Parameters: + * void + * + * Returns: + * void + */ + public static function refreshToken() + { + $token = self::generateAuthToken(); + + if (!isset($_SESSION[self::$config['CSRFP_TOKEN']]) + || !is_array($_SESSION[self::$config['CSRFP_TOKEN']])) + $_SESSION[self::$config['CSRFP_TOKEN']] = array(); + + //set token to session for server side validation + array_push($_SESSION[self::$config['CSRFP_TOKEN']], $token); + + //set token to cookie for client side processing + setcookie(self::$config['CSRFP_TOKEN'], + $token, + time() + self::$cookieExpiryTime); + } + + /* + * Function: generateAuthToken + * function to generate random hash of length as given in parameter + * max length = 128 + * + * Parameters: + * length to hash required, int + * + * Returns: + * string, token + */ + public static function generateAuthToken() + { + //if config tokenLength value is 0 or some non int + if (intval(self::$config['tokenLength']) == 0) { + self::$config['tokenLength'] = 32; //set as default + } + + //#todo - if $length > 128 throw exception + + if (function_exists("hash_algos") && in_array("sha512", hash_algos())) { + $token = hash("sha512", mt_rand(0, mt_getrandmax())); + } else { + $token = ''; + for ($i = 0; $i < 128; ++$i) { + $r = mt_rand(0, 35); + if ($r < 26) { + $c = chr(ord('a') + $r); + } else { + $c = chr(ord('0') + $r - 26); + } + $token .= $c; + } + } + return substr($token, 0, self::$config['tokenLength']); + } + + /* + * Function: ob_handler + * Rewrites on the fly to add CSRF tokens to them. This can also + * inject our JavaScript library. + * + * Parameters: + * $buffer - output buffer to which all output are stored + * $flag - INT + * + * Return: + * string, complete output buffer + */ + public static function ob_handler($buffer, $flags) + { + // Even though the user told us to rewrite, we should do a quick heuristic + // to check if the page is *actually* HTML. We don't begin rewriting until + // we hit the first message to outgoing HTML output, + //informing the user to enable js for CSRFProtector to work + //best section to add, after tag + $buffer = preg_replace("/]*>/", "$0 ", $buffer); + + $hiddenInput = '' .PHP_EOL; + + $hiddenInput .= ''; + + //implant hidden fields with check url information for reading in javascript + $buffer = str_ireplace('', $hiddenInput . '', $buffer); + + //implant the CSRFGuard js file to outgoing script + $script = '' . PHP_EOL; + $buffer = str_ireplace('', $script . '', $buffer, $count); + + if (!$count) + $buffer .= $script; + + return $buffer; + } + + /* + * Function: logCSRFattack + * Functio to log CSRF Attack + * + * Parameters: + * void + * + * Retruns: + * void + * + * Throws: + * logFileWriteError - if unable to log an attack + */ + private static function logCSRFattack() + { + //if file doesnot exist for, create it + $logFile = fopen(__DIR__ ."/../" .self::$config['logDirectory'] + ."/" .date("m-20y") .".log", "a+"); + + //throw exception if above fopen fails + if (!$logFile) + throw new logFileWriteError("OWASP CSRFProtector: Unable to write to the log file"); + + //miniature version of the log + $log = array(); + $log['timestamp'] = time(); + $log['HOST'] = $_SERVER['HTTP_HOST']; + $log['REQUEST_URI'] = $_SERVER['REQUEST_URI']; + $log['requestType'] = self::$requestType; + + if (self::$requestType === "GET") + $log['query'] = $_GET; + else + $log['query'] = $_POST; + + $log['cookie'] = $_COOKIE; + + //convert log array to JSON format to be logged + $log = json_encode($log) .PHP_EOL; + + //append log to the file + fwrite($logFile, $log); + + //close the file handler + fclose($logFile); + } + + /* + * Function: getCurrentUrl + * Function to return current url of executing page + * + * Parameters: + * void + * + * Returns: + * string - current url + */ + private static function getCurrentUrl() + { + $request_scheme = 'https'; + + if (isset($_SERVER['REQUEST_SCHEME'])) { + $request_scheme = $_SERVER['REQUEST_SCHEME']; + } else { + if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') { + $request_scheme = 'https'; + } else { + $request_scheme = 'http'; + } + } + + return $request_scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']; + } + + /* + * Function: isURLallowed + * Function to check if a url mataches for any urls + * Listed in config file + * + * Parameters: + * void + * + * Returns: + * boolean - true is url need no validation, false if validation needed + */ + public static function isURLallowed() { + foreach (self::$config['verifyGetFor'] as $key => $value) { + $value = str_replace(array('/','*'), array('\/','(.*)'), $value); + preg_match('/' .$value .'/', self::getCurrentUrl(), $output); + if (count($output) > 0) + return false; + } + return true; + } + }; +} diff --git a/sources/admin.queries.php b/sources/admin.queries.php index be8f281ac..448de8fb9 100644 --- a/sources/admin.queries.php +++ b/sources/admin.queries.php @@ -291,12 +291,8 @@ } if (!empty($return)) { - // load passwordLib library - $pwdlib = new SplClassLoader('PasswordLib', '../includes/libraries'); - $pwdlib->register(); - $pwdlib = new PasswordLib\PasswordLib(); - // generate key - $token = $pwdlib->getRandomToken(20); + // get a token + $token = GenerateCryptKey(20); //save file $filename = time().'-'.$token.'.sql'; @@ -312,15 +308,7 @@ fclose($handle); //generate 2d key - $pwgen = new SplClassLoader('Encryption\PwGen', '../includes/libraries'); - $pwgen->register(); - $pwgen = new Encryption\PwGen\pwgen(); - $pwgen->setLength(20); - $pwgen->setSecure(true); - $pwgen->setSymbols(false); - $pwgen->setCapitalize(true); - $pwgen->setNumerals(true); - $_SESSION['key_tmp'] = $pwgen->generate(); + $_SESSION['key_tmp'] = GenerateCryptKey(20); //update LOG logEvents('admin_action', 'dataBase backup', $_SESSION['user_id'], $_SESSION['login']); diff --git a/sources/identify.php b/sources/identify.php index 458f9ad8e..e46ee4503 100644 --- a/sources/identify.php +++ b/sources/identify.php @@ -524,7 +524,7 @@ function identifyUser($sentData) $_SESSION['autoriser'] = true; // Generate a ramdom ID - $key = $pwdlib->getRandomToken(50); + $key = GenerateCryptKey(50); if ($debugDuo == 1) { fputs( diff --git a/sources/main.functions.php b/sources/main.functions.php index 0a3016643..1fcb4263d 100644 --- a/sources/main.functions.php +++ b/sources/main.functions.php @@ -1189,13 +1189,30 @@ function prefix_table($table) } /* - * Creates a KEY using Crypt + * Creates a KEY using PasswordLib */ -function GenerateCryptKey($size) +function GenerateCryptKey($size="", $secure="", $numerals="", $capitalize="", $ambiguous="", $symbols="") { - return PHP_Crypt::createKey(PHP_Crypt::RAND, $size); + // load library + $pwgen = new SplClassLoader('Encryption\PwGen', '../includes/libraries'); + $pwgen->register(); + $pwgen = new Encryption\PwGen\pwgen(); + + // init + if(!empty($size)) $pwgen->setLength($size); + if(!empty($secure)) $pwgen->setSecure($secure); + if(!empty($numerals)) $pwgen->setNumerals($numerals); + if(!empty($capitalize)) $pwgen->setCapitalize($capitalize); + if(!empty($ambiguous)) $pwgen->setAmbiguous($ambiguous); + if(!empty($symbols)) $pwgen->setSymbols($symbols); + + // generate and send back + return $pwgen->generate(); } +/* +* Send sysLOG message +*/ function send_syslog($message, $component = "teampass", $program = "php", $host , $port) { $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);