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