diff --git a/src/bb-config-sample.php b/src/bb-config-sample.php index 0dac096a6..d1cd37700 100755 --- a/src/bb-config-sample.php +++ b/src/bb-config-sample.php @@ -19,6 +19,9 @@ */ 'url' => 'http://localhost/', + /** + * The URL prefix to access the BB admin area. Ex: '/bb-admin' = https://example.com/bb-admin + */ 'admin_area_prefix' => '/bb-admin', /** diff --git a/src/bb-library/Security/CSRF-Protector-PHP/js/csrfprotector.js b/src/bb-library/Security/CSRF-Protector-PHP/js/csrfprotector.js new file mode 100644 index 000000000..f2bb8d477 --- /dev/null +++ b/src/bb-library/Security/CSRF-Protector-PHP/js/csrfprotector.js @@ -0,0 +1,350 @@ +/** + * ================================================================= + * 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_FIELD_TOKEN_NAME = 'csrfp_hidden_data_token'; +var CSRFP_FIELD_URLS = 'csrfp_hidden_data_urls'; + +var CSRFP = { + CSRFP_TOKEN: 'CSRFP-Token', + /** + * Array of patterns of url, for which csrftoken need to be added + * In case of GET request also, provided from server + * + * @var {Array} + */ + checkForUrls: [], + /** + * Returns true if the get request doesn't need csrf token. + * + * @param {String} url to check. + * @return {Boolean} true if csrftoken is not 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; + }, + /** + * Returns auth key from cookie. + * + * @return {String} auth key from cookie. + */ + _getAuthKey: function () { + var regex = new RegExp((?:^|;\\s*)${CSRFP.CSRFP_TOKEN}=([^;]+)(;|$)); + var regexResult = regex.exec(document.cookie); + if (regexResult === null) { + return null; + } + + return regexResult[1]; + }, + /** + * Returns domain name of a url. + * + * @param {String} url - url to check. + * @return {String} domain of the input url. + */ + _getDomain: function (url) { + // TODO(mebjas): add support for other protocols that web supports. + if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { + return document.domain; + } + return /http(s)?:\/\/([^\/]+)/.exec(url)[2]; + }, + /** + * Creates hidden input element with CSRF_TOKEN in it. + * + * @return {HTMLInputElement} hidden input element. + */ + _createHiddenInputElement: function () { + var inputElement = document.createElement('input'); + inputElement.setAttribute('name', CSRFP.CSRFP_TOKEN); + inputElement.setAttribute('class', CSRFP.CSRFP_TOKEN); + inputElement.type = 'hidden'; + inputElement.value = CSRFP._getAuthKey(); + return inputElement; + }, + /** + * Returns absolute url from the input relative components. + * + * @param {String} basePart - base part of the url. + * @param {String} relativePart - relative part of the url. + * @return {String} absolute url. + */ + _createAbsolutePath: function (basePart, relativePart) { + var stack = basePart.split("/"); + var parts = relativePart.split("/"); + 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("/"); + }, + /** + * Creates a function wrapper around {@param runnableFunction}, removes + * CSRF Token before calling the function and then put it back. + * + * @param {Function} runnableFunction - function to run. + * @param {Object} htmlFormObject - reference form object. + * @return modified wrapped function. + */ + _createCsrfpWrappedFunction: function (runnableFunction, htmlFormObject) { + return function (event) { + // Remove CSRf token if exists + if (typeof htmlFormObject[CSRFP.CSRFP_TOKEN] !== 'undefined') { + var target = htmlFormObject[CSRFP.CSRFP_TOKEN]; + target.parentNode.removeChild(target); + } + + // Trigger the functions + var result = runnableFunction.apply(this, [event]); + + // Now append the CSRFP-Token back + htmlFormObject.appendChild(CSRFP._createHiddenInputElement()); + return result; + }; + }, + /** + * Initialises the CSRFProtector js script. + */ + _init: function () { + this.CSRFP_TOKEN = document.getElementById( + CSRFP_FIELD_TOKEN_NAME).value; + + try { + var csrfFieldElem = document.getElementById(CSRFP_FIELD_URLS); + this.checkForUrls = JSON.parse(csrfFieldElem.value); + } catch (exception) { + console.error(exception); + console.error('[ERROR] [CSRF Protector] unable to parse blacklisted' + + ` url fields. Exception = ${exception}`); + } + + // Convert the rules received from php library to regex objects. + for (var i = 0; i < CSRFP.checkForUrls.length; i++) { + this.checkForUrls[i] + = this.checkForUrls[i].replace(/\*/g, '(.*)') + .replace(/\//g, "\\/"); + this.checkForUrls[i] = new RegExp(CSRFP.checkForUrls[i]); + } + } +} + +//========================================================== +// Adding tokens, wrappers on window onload +//========================================================== + +function csrfprotector_init() { + + // Call the init function + CSRFP._init(); + + // Basic FORM submit event handler to intercept the form request and attach + // a CSRFP TOKEN if it's not already available. + var basicSubmitInterceptor = function (event) { + if (!event.target[CSRFP.CSRFP_TOKEN]) { + event.target.appendChild(CSRFP._createHiddenInputElement()); + } else { + //modify token to latest value + event.target[CSRFP.CSRFP_TOKEN].value = CSRFP._getAuthKey(); + } + }; + + //================================================================== + // Adding csrftoken to request resulting from
submissions + // Add for each POST, while for mentioned GET request + // TODO - check for method + //================================================================== + // run time binding + document.querySelector('body').addEventListener('submit', function (event) { + if (event.target.tagName.toLowerCase() === 'form') { + basicSubmitInterceptor(event); + } + }); + + //================================================================== + // Adding csrftoken to request resulting from direct form.submit() call + // Add for each POST, while for mentioned GET request + // TODO - check for form method + //================================================================== + HTMLFormElement.prototype.submit_real = HTMLFormElement.prototype.submit; + HTMLFormElement.prototype.submit = function () { + // check if the FORM already contains the token element + if (!this.getElementsByClassName(CSRFP.CSRFP_TOKEN).length) { + this.appendChild(CSRFP._createHiddenInputElement()); + } + this.submit_real(); + }; + + /** + * Add wrapper for HTMLFormElements addEventListener so that any further + * addEventListens won't have trouble with CSRF token + * todo - check for method + */ + HTMLFormElement.prototype.addEventListener_real + = HTMLFormElement.prototype.addEventListener; + HTMLFormElement.prototype.addEventListener = function ( + eventType, func, bubble) { + if (eventType === 'submit') { + var wrappedFunc = CSRFP._createCsrfpWrappedFunction(func, this); + this.addEventListener_real(eventType, wrappedFunc, bubble); + } else { + this.addEventListener_real(eventType, func, bubble); + } + }; + + /** + * Add wrapper for IE's attachEvent + * todo - check for method + * todo - typeof is now obsolete for IE 11, use some other method. + */ + if (HTMLFormElement.prototype.attachEvent) { + HTMLFormElement.prototype.attachEvent_real + = HTMLFormElement.prototype.attachEvent; + HTMLFormElement.prototype.attachEvent = function (eventType, func) { + if (eventType === 'onsubmit') { + var wrappedFunc = CSRFP._createCsrfpWrappedFunction(func, this); + this.attachEvent_real(eventType, wrappedFunc); + } else { + this.attachEvent_real(eventType, func); + } + } + } + + //================================================================== + // 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 XMLHttpRequest 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; + if (!isAbsolute) { + var base = location.protocol + '//' + location.host + + location.pathname; + url = CSRFP._createAbsolutePath(base, url); + } + + if (method.toLowerCase() === 'get' && !CSRFP._isValidGetRequest(url)) { + var token = CSRFP._getAuthKey(); + if (url.indexOf('?') === -1) { + url += `?${CSRFP.CSRFP_TOKEN}=${token}` + } else { + url += `&${CSRFP.CSRFP_TOKEN}=${token}`; + } + } + + return this.old_open(method, url, async, username, password); + } + + /** + * Wrapper to XHR send method + * Add query parameter 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') { + // attach the token in request header + this.setRequestHeader(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 + // Ignore cross origin urls & internal links (one with hashtags) + // Append the token to those url already containing 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 href = event.target.href; + if (typeof href !== "string") { + return; + } + var urlParts = href.split('#'); + var url = urlParts[0]; + var hash = urlParts[1]; + + if (CSRFP._getDomain(url).indexOf(document.domain) === -1 + || CSRFP._isValidGetRequest(url)) { + //cross origin or not to be protected by rules -- ignore + return; + } + + var token = CSRFP._getAuthKey(); + if (url.indexOf('?') !== -1) { + if (url.indexOf(CSRFP.CSRFP_TOKEN) === -1) { + url += `&${CSRFP.CSRFP_TOKEN}=${token}`; + } else { + var replacementString = `${CSRFP.CSRFP_TOKEN}=${token}$1`; + url = url.replace( + new RegExp(CSRFP.CSRFP_TOKEN + "=.*?(&|$)", 'g'), + replacementString); + } + } else { + url += `?${CSRFP.CSRFP_TOKEN}=${token}`; + } + + event.target.href = url; + if (hash) { + event.target.href += `#${hash}`; + } + }); + } +} + +window.addEventListener("DOMContentLoaded", function () { + csrfprotector_init(); + + // Dispatch an event so clients know the library has initialized + var postCsrfProtectorInit = new Event('postCsrfProtectorInit'); + window.dispatchEvent(postCsrfProtectorInit); +}, false); diff --git a/src/bb-library/Security/CSRF-Protector-PHP/js/index.php b/src/bb-library/Security/CSRF-Protector-PHP/js/index.php new file mode 100644 index 000000000..6fab721c9 --- /dev/null +++ b/src/bb-library/Security/CSRF-Protector-PHP/js/index.php @@ -0,0 +1,7 @@ +path = $cfg['path']; + } + + if (isset($cfg['domain'])) { + $this->domain = $cfg['domain']; + } + + if (isset($cfg['secure'])) { + $this->secure = (bool) $cfg['secure']; + } + + if (isset($cfg['expire']) && $cfg['expire']) { + $this->expire = (int)$cfg['expire']; + } + } + } + } +} diff --git a/src/bb-library/Security/CSRF-Protector-PHP/libs/csrf/csrfpDefaultLogger.php b/src/bb-library/Security/CSRF-Protector-PHP/libs/csrf/csrfpDefaultLogger.php new file mode 100644 index 000000000..dffb667bd --- /dev/null +++ b/src/bb-library/Security/CSRF-Protector-PHP/libs/csrf/csrfpDefaultLogger.php @@ -0,0 +1,40 @@ + + */ + private static $cookieConfig = null; + + /** + * Variable: $logger + * Logger class object + * @var LoggerInterface + */ + private static $logger = null; + + /** + * Variable: $tokenHeaderKey + * Key value in header array, which contain the token + * @var string + */ + private static $tokenHeaderKey = null; + + /* + * Variable: $requestType + * Variable to store whether 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: #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. + * + * TODO(mebjas): this field should be private + */ + public static $config = array(); + + /* + * Variable: $requiredConfigurations + * Contains list of those parameters that are required to be there + * in config file for csrfp to work + * + * TODO(mebjas): this field should be private + */ + public static $requiredConfigurations = array( + 'failedAuthAction', 'jsUrl', 'tokenLength'); + + /* + * Function: function to initialise the csrfProtector work flow + * + * Parameters: + * $length - (int) length of CSRF_AUTH_TOKEN to be generated. + * $action - (int array), for different actions to be taken in case of + * failed validation. + * $logger - (LoggerInterface) custom logger class object. + * + * 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, $logger = null) + { + // Check if init has already been called. + if (count(self::$config) > 0) { + throw new alreadyInitializedException("OWASP CSRFProtector: library was already initialized."); + } + + // If mod_csrfp already enabled, no extra verification needed. + if (getenv('mod_csrfp_enabled')) { + return; + } + + // Start session in case its not, and unit test is not going on + if (session_id() == '' && !defined('__CSRFP_UNIT_TEST__')) { + 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 = BB_PATH_ROOT ."/csrfp-config.php"; + + if (file_exists($standard_config_location)) { + self::$config = include($standard_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; + } + + self::$tokenHeaderKey = 'HTTP_' .strtoupper(self::$config['CSRFP_TOKEN']); + self::$tokenHeaderKey = str_replace('-', '_', self::$tokenHeaderKey); + + // Load parameters for setcookie method + if (!isset(self::$config['cookieConfig'])) { + self::$config['cookieConfig'] = array(); + } + + self::$cookieConfig = new csrfpCookieConfig(self::$config['cookieConfig']); + + // Validate the config if everything is filled out + $missingConfiguration = []; + foreach (self::$requiredConfigurations as $value) { + if (!isset(self::$config[$value]) || self::$config[$value] === '') { + $missingConfiguration[] = $value; + } + } + + if ($missingConfiguration) { + throw new incompleteConfigurationException( + 'OWASP CSRFProtector: Incomplete configuration file: missing ' . + implode(', ', $missingConfiguration) . ' value(s)'); + } + + // Initialize the logger class + if ($logger !== null) { + self::$logger = $logger; + } else { + self::$logger = new csrfpDefaultLogger(); + } + + // Authorise the incoming request + self::authorizePost(); + + // Initialize output buffering handler + if (!defined('__TESTING_CSRFP__')) { + 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(); + } + } + + /* + * Function: authorizePost + * function to authorise incoming post requests + * + * Parameters: + * void + * + * Returns: + * void + * + * TODO(mebjas): this method should be private. + */ + public static function authorizePost() + { + // TODO(mebjas): 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"; + + // Look for token in payload else from header + $token = self::getTokenFromRequest(); + + // Currently for same origin only + if (!($token && isset($_SESSION[self::$config['CSRFP_TOKEN']]) + && (self::isValidToken($token)))) { + + // Action in case of failed validation + self::failedValidationAction(); + } else { + self::refreshToken(); //refresh token for successful 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 successful validation + } + } + } + + /* + * Function: getTokenFromRequest + * function to get token in case of POST request + * + * Parameters: + * void + * + * Returns: + * any (string / bool) - token retrieved from header or form payload + */ + private static function getTokenFromRequest() + { + // Look for in $_POST, then header + if (isset($_POST[self::$config['CSRFP_TOKEN']])) { + return $_POST[self::$config['CSRFP_TOKEN']]; + } + + if (function_exists('getallheaders')) { + $requestHeaders = getallheaders(); + if (isset($requestHeaders[self::$config['CSRFP_TOKEN']])) { + return $requestHeaders[self::$config['CSRFP_TOKEN']]; + } + } + + if (self::$tokenHeaderKey === null) { + return false; + } + + if (isset($_SERVER[self::$tokenHeaderKey])) { + return $_SERVER[self::$tokenHeaderKey]; + } + + return false; + } + + /* + * 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() + { + //call the logging function + static::logCSRFattack(); + + // TODO(mebjas): ask mentors if $failedAuthAction is better as an int or string + // default case is case 0 + switch (self::$config['failedAuthAction'][self::$requestType]) { + case csrfpAction::ForbiddenResponseAction: + // Send 403 header + header('HTTP/1.0 403 Forbidden'); + exit("

403 Access Forbidden by CSRFProtector!

"); + case csrfpAction::ClearParametersAction: + // Unset the query parameters and forward + if (self::$requestType === 'GET') { + $_GET = array(); + } else { + $_POST = array(); + } + break; + case csrfpAction::RedirectAction: + // Redirect to custom error page + $location = self::$config['errorRedirectionPage']; + header("location: $location"); + exit(self::$config['customErrorMessage']); + case csrfpAction::CustomErrorMessageAction: + // Send custom error message + exit(self::$config['customErrorMessage']); + case csrfpAction::InternalServerErrorResponseAction: + // Send 500 header -- internal server error + header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); + exit("

500 Internal Server Error!

"); + 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 + if (self::$cookieConfig === null) { + if (!isset(self::$config['cookieConfig'])) + self::$config['cookieConfig'] = array(); + self::$cookieConfig = new csrfpCookieConfig(self::$config['cookieConfig']); + } + + setcookie( + self::$config['CSRFP_TOKEN'], + $token, + time() + self::$cookieConfig->expire, + self::$cookieConfig->path, + self::$cookieConfig->domain, + (bool) self::$cookieConfig->secure); + } + + /* + * 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() + { + // TODO(mebjas): Make this a member method / configurable + $randLength = 64; + + // If config tokenLength value is 0 or some non int + if (intval(self::$config['tokenLength']) == 0) { + self::$config['tokenLength'] = 32; //set as default + } + + // TODO(mebjas): if $length > 128 throw exception + + if (function_exists("random_bytes")) { + $token = bin2hex(random_bytes($randLength)); + } elseif (function_exists("openssl_random_pseudo_bytes")) { + $token = bin2hex(openssl_random_pseudo_bytes($randLength)); + } 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); + + if (self::$config['jsUrl']) { + // Implant the CSRFGuard js file to outgoing script + $script = ''; + $buffer = str_ireplace('', $script . PHP_EOL . '', $buffer, $count); + + // Add the script to the end if the body tag was not closed + if (!$count) { + $buffer .= $script; + } + } + + return $buffer; + } + + /* + * Function: logCSRFattack + * Function to log CSRF Attack + * + * Parameters: + * void + * + * Returns: + * void + * + * Throws: + * logFileWriteError - if unable to log an attack + */ + protected static function logCSRFattack() + { + //miniature version of the log + $context = array(); + $context['HOST'] = $_SERVER['HTTP_HOST']; + $context['REQUEST_URI'] = $_SERVER['REQUEST_URI']; + $context['requestType'] = self::$requestType; + $context['cookie'] = $_COOKIE; + self::$logger->log( + "OWASP CSRF PROTECTOR VALIDATION FAILURE", $context); + } + + /* + * 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 matches 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/src/bb-library/Security/CSRF-Protector-PHP/libs/csrf/index.php b/src/bb-library/Security/CSRF-Protector-PHP/libs/csrf/index.php new file mode 100644 index 000000000..6fab721c9 --- /dev/null +++ b/src/bb-library/Security/CSRF-Protector-PHP/libs/csrf/index.php @@ -0,0 +1,7 @@ + "", + "failedAuthAction" => array( + "GET" => 0, + "POST" => 0), + "errorRedirectionPage" => "", + "customErrorMessage" => "", + "jsUrl" => $config['url'] .'/bb-library/Security/CSRF-Protector-PHP/js/csrfprotector.js', + "tokenLength" => 10, + "cookieConfig" => array( + "path" => '', + "domain" => '', + "secure" => false, + "expire" => '', + ), + "disabledJavascriptMessage" => "This site attempts to protect users against + Cross-Site Request Forgeries attacks. In order to do so, you must have JavaScript enabled in your web browser otherwise this site will fail to work correctly for you. + See details of your web browser for how to enable JavaScript.", + "verifyGetFor" => array() +);