Skip to content

Commit

Permalink
Implement XSRF checking for AJAX handlers
Browse files Browse the repository at this point in the history
Refs #4699
Refs #4701
  • Loading branch information
daftspunk committed Oct 24, 2019
1 parent df65861 commit f542ca8
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 8 deletions.
55 changes: 47 additions & 8 deletions modules/cms/classes/Controller.php
Expand Up @@ -6,12 +6,14 @@
use View;
use Lang;
use Flash;
use Crypt;
use Config;
use Session;
use Request;
use Response;
use Exception;
use BackendAuth;
use Carbon\Carbon;
use Twig\Environment as TwigEnvironment;
use Twig\Cache\FilesystemCache as TwigCacheFilesystem;
use Cms\Twig\Loader as TwigLoader;
Expand All @@ -25,6 +27,7 @@
use October\Rain\Exception\AjaxException;
use October\Rain\Exception\ValidationException;
use October\Rain\Parse\Bracket as TextParser;
use Symfony\Component\HttpFoundation\Cookie;
use Illuminate\Http\RedirectResponse;

/**
Expand Down Expand Up @@ -147,6 +150,13 @@ public function run($url = '/')
$url = '/';
}

/*
* Check security token.
*/
if (!$this->verifyCsrfToken()) {
return Response::make(Lang::get('system::lang.page.invalid_token.label'), 403);
}

/*
* Hidden page
*/
Expand Down Expand Up @@ -256,7 +266,13 @@ public function run($url = '/')
return $result;
}

return Response::make($result, $this->statusCode);
$response = Response::make($result, $this->statusCode);

if (Config::get('cms.enableCsrfProtection')) {
$this->addCsrfCookie($response);
}

return $response;
}

/**
Expand Down Expand Up @@ -802,13 +818,6 @@ protected function execAjaxHandlers()
*/
protected function runAjaxHandler($handler)
{
/*
* Check security token.
*/
if (!$this->verifyCsrfToken()) {
return Response::make(Lang::get('system::lang.page.invalid_token.label'), 403);
}

/**
* @event cms.ajax.beforeRunHandler
* Provides an opportunity to modify an AJAX request
Expand Down Expand Up @@ -1583,6 +1592,32 @@ protected function setComponentPropertiesFromParams($component, $parameters = []
// Security
//

/**
* Adds anti-CSRF cookie.
* Adds a cookie with a token for CSRF checks to the response.
* @return void
*/
protected function addCsrfCookie(\Illuminate\Http\Response $response)
{
$config = Config::get('session');

$response->headers->setCookie(
new Cookie(
'XSRF-TOKEN',
Session::token(),
Carbon::now()->addSeconds(60 * $config['lifetime'])->getTimestamp(),
$config['path'],
$config['domain'],
$config['secure'],
false,
false,
$config['same_site'] ?? null
)
);

return $response;
}

/**
* Checks the request data / headers for a valid CSRF token.
* Returns false if a valid token is not found. Override this
Expand All @@ -1601,6 +1636,10 @@ protected function verifyCsrfToken()

$token = Request::input('_token') ?: Request::header('X-CSRF-TOKEN');

if (!$token && $header = Request::header('X-XSRF-TOKEN')) {
$token = Crypt::decrypt($header);
}

if (!strlen($token) || !strlen(Session::token())) {
return false;
}
Expand Down
8 changes: 8 additions & 0 deletions modules/system/assets/js/framework-min.js
Expand Up @@ -14,6 +14,8 @@ useFiles=false}
if($.type(loading)=='string'){loading=$(loading)}
var requestHeaders={'X-OCTOBER-REQUEST-HANDLER':handler,'X-OCTOBER-REQUEST-PARTIALS':this.extractPartials(options.update)}
if(useFlash){requestHeaders['X-OCTOBER-REQUEST-FLASH']=1}
var csrfToken=getCSRFToken()
if(csrfToken){requestHeaders['X-XSRF-TOKEN']=csrfToken}
var requestData,inputName,data={}
$.each($el.parents('[data-request-data]').toArray().reverse(),function extendRequest(){$.extend(data,paramToObj('data-request-data',$(this).data('request-data')))})
if($el.is(':input')&&!$form.length){inputName=$el.attr('name')
Expand Down Expand Up @@ -112,6 +114,12 @@ function paramToObj(name,value){if(value===undefined)value=''
if(typeof value=='object')return value
try{return ocJSON("{"+value+"}")}
catch(e){throw new Error('Error parsing the '+name+' attribute value. '+e)}}
function getCSRFToken(){var cookieValue=null
if(document.cookie&&document.cookie!=''){var cookies=document.cookie.split(';')
for(var i=0;i<cookies.length;i++){var cookie=jQuery.trim(cookies[i])
if(cookie.substring(0,11)==('XSRF-TOKEN'+'=')){cookieValue=decodeURIComponent(cookie.substring(11))
break}}}
return cookieValue}
$(document).on('change','select[data-request], input[type=radio][data-request], input[type=checkbox][data-request], input[type=file][data-request]',function documentOnChange(){$(this).request()})
$(document).on('click','a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]',function documentOnClick(e){e.preventDefault()
$(this).request()
Expand Down
8 changes: 8 additions & 0 deletions modules/system/assets/js/framework.combined-min.js
Expand Up @@ -14,6 +14,8 @@ useFiles=false}
if($.type(loading)=='string'){loading=$(loading)}
var requestHeaders={'X-OCTOBER-REQUEST-HANDLER':handler,'X-OCTOBER-REQUEST-PARTIALS':this.extractPartials(options.update)}
if(useFlash){requestHeaders['X-OCTOBER-REQUEST-FLASH']=1}
var csrfToken=getCSRFToken()
if(csrfToken){requestHeaders['X-XSRF-TOKEN']=csrfToken}
var requestData,inputName,data={}
$.each($el.parents('[data-request-data]').toArray().reverse(),function extendRequest(){$.extend(data,paramToObj('data-request-data',$(this).data('request-data')))})
if($el.is(':input')&&!$form.length){inputName=$el.attr('name')
Expand Down Expand Up @@ -112,6 +114,12 @@ function paramToObj(name,value){if(value===undefined)value=''
if(typeof value=='object')return value
try{return ocJSON("{"+value+"}")}
catch(e){throw new Error('Error parsing the '+name+' attribute value. '+e)}}
function getCSRFToken(){var cookieValue=null
if(document.cookie&&document.cookie!=''){var cookies=document.cookie.split(';')
for(var i=0;i<cookies.length;i++){var cookie=jQuery.trim(cookies[i])
if(cookie.substring(0,11)==('XSRF-TOKEN'+'=')){cookieValue=decodeURIComponent(cookie.substring(11))
break}}}
return cookieValue}
$(document).on('change','select[data-request], input[type=radio][data-request], input[type=checkbox][data-request], input[type=file][data-request]',function documentOnChange(){$(this).request()})
$(document).on('click','a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]',function documentOnClick(e){e.preventDefault()
$(this).request()
Expand Down
20 changes: 20 additions & 0 deletions modules/system/assets/js/framework.js
Expand Up @@ -68,6 +68,11 @@ if (window.jQuery.request !== undefined) {
requestHeaders['X-OCTOBER-REQUEST-FLASH'] = 1
}

var csrfToken = getCSRFToken()
if (csrfToken) {
requestHeaders['X-XSRF-TOKEN'] = csrfToken
}

/*
* Request data
*/
Expand Down Expand Up @@ -465,6 +470,21 @@ if (window.jQuery.request !== undefined) {
}
}

function getCSRFToken() {
var cookieValue = null
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';')
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i])
if (cookie.substring(0, 11) == ('XSRF-TOKEN' + '=')) {
cookieValue = decodeURIComponent(cookie.substring(11))
break
}
}
}
return cookieValue
}

$(document).on('change', 'select[data-request], input[type=radio][data-request], input[type=checkbox][data-request], input[type=file][data-request]', function documentOnChange() {
$(this).request()
})
Expand Down

0 comments on commit f542ca8

Please sign in to comment.