Skip to content

Commit

Permalink
Add CSRF token for Ajax (#14952)
Browse files Browse the repository at this point in the history
* Add CSRF token for Ajax

* Add CSRF token to hathor

* CS

* call jquery first

* Use server->get()

* Loaded once

* Add CSRF to core.js request method

* Use CSRF instead Csrf

* Use scriptOption

* Fix test

* MISC update

* Use $name variable in JS

* Use string as script option directly and fix tests
  • Loading branch information
asika32764 authored and Michael Babker committed Jul 29, 2017
1 parent ac7f0d5 commit 09f5cd5
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

defined('_JEXEC') or die;

JHtml::_('jquery.token');
JHtml::_('script', 'system/sendtestmail.js', array('version' => 'auto', 'relative' => true));

// Load JavaScript message titles
Expand All @@ -25,7 +26,7 @@
JText::script('JLIB_JS_AJAX_ERROR_TIMEOUT');

// Ajax request data.
$ajaxUri = JRoute::_('index.php?option=com_config&task=config.sendtestmail.application&format=json&' . JSession::getFormToken() . '=1');
$ajaxUri = JRoute::_('index.php?option=com_config&task=config.sendtestmail.application&format=json');

$this->name = JText::_('COM_CONFIG_MAIL_SETTINGS');
$this->fieldsname = 'mail';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@

defined('_JEXEC') or die;

JHtml::_('jquery.token');
JHtml::_('script', 'system/sendtestmail.js', array('version' => 'auto', 'relative' => true));

JFactory::getDocument()->addScriptDeclaration('
var sendtestmail_url = "' . addslashes(JUri::base()) . 'index.php?option=com_config&task=config.sendtestmail.application&format=json&' . JSession::getFormToken() . '=1";
var sendtestmail_url = "' . addslashes(JUri::base()) . 'index.php?option=com_config&task=config.sendtestmail.application&format=json";
');
?>
<div class="width-100">
Expand Down
1 change: 1 addition & 0 deletions libraries/cms/html/behavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public static function core()
return;
}

JHtml::_('form.csrf');
JHtml::_('script', 'system/core.js', array('version' => 'auto', 'relative' => true));

// Add core and base uri paths so javascript scripts can use them.
Expand Down
53 changes: 51 additions & 2 deletions libraries/cms/html/form.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,74 @@

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
* Utility class for form elements
*
* @since 1.5
*/
abstract class JHtmlForm
{
/**
* Array containing information for loaded files.
*
* @var array
*
* @since __DEPLOY_VERSION__
*/
protected static $loaded = array();

/**
* Displays a hidden token field to reduce the risk of CSRF exploits
*
* Use in conjunction with JSession::checkToken()
*
* @param array $attribs Input element attributes.
*
* @return string A hidden input field with a token
*
* @see JSession::checkToken()
* @since 1.5
*/
public static function token()
public static function token(array $attribs = array())
{
return '<input type="hidden" name="' . JSession::getFormToken() . '" value="1" />';
$attributes = '';

if ($attribs !== array())
{
$attributes .= ' ' . ArrayHelper::toString($attribs);
}

return '<input type="hidden" name="' . JSession::getFormToken() . '" value="1"' . $attributes . ' />';
}

/**
* Add CSRF form token to Joomla script options that developers can get it by Javascript.
*
* @param string $name The script option key name.
*
* @return void
*
* @since __DEPLOY_VERSION__
*/
public static function csrf($name = 'csrf.token')
{
if (isset(static::$loaded[__METHOD__][$name]))
{
return;
}

/** @var JDocumentHtml $doc */
$doc = JFactory::getDocument();

if (!$doc instanceof JDocumentHtml || $doc->getType() !== 'html')
{
return;
}

$doc->addScriptOptions($name, JSession::getFormToken());

static::$loaded[__METHOD__][$name] = true;
}
}
39 changes: 39 additions & 0 deletions libraries/cms/html/jquery.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,43 @@ public static function ui(array $components = array('core'), $debug = null)

return;
}

/**
* Auto set CSRF token to ajaxSetup so all jQuery ajax call will contains CSRF token.
*
* @param string $name The CSRF meta tag name.
*
* @return void
*
* @throws \InvalidArgumentException
*
* @since __DEPLOY_VERSION__
*/
public static function token($name = 'csrf.token')
{
// Only load once
if (!empty(static::$loaded[__METHOD__][$name]))
{
return;
}

static::framework();
JHtml::_('form.csrf', $name);

$doc = JFactory::getDocument();

$doc->addScriptDeclaration(
<<<JS
;(function ($) {
$.ajaxSetup({
headers: {
'X-CSRF-Token': Joomla.getOptions('$name')
}
});
})(jQuery);
JS
);

static::$loaded[__METHOD__][$name] = true;
}
}
7 changes: 7 additions & 0 deletions libraries/src/Joomla/CMS/Session/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,13 @@ public static function checkToken($method = 'post')
$token = self::getFormToken();
$app = \JFactory::getApplication();

// Check from header first
if ($token === $app->input->server->get('HTTP_X_CSRF_TOKEN', '', 'alnum'))
{
return true;
}

// Then fallback to HTTP query
if (!$app->input->$method->get($token, '', 'alnum'))
{
if (\JFactory::getSession()->isNew())
Expand Down
17 changes: 13 additions & 4 deletions media/system/js/core-uncompressed.js
Original file line number Diff line number Diff line change
Expand Up @@ -839,24 +839,33 @@ Joomla.editors.instances = Joomla.editors.instances || {
}, options);

// Use POST for send the data
options.method = options.data ? 'POST' : options.method;
options.method = options.data ? 'POST' : options.method.toUpperCase();

// Set up XMLHttpRequest instance
try{
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('MSXML2.XMLHTTP.3.0');

xhr.open(options.method, options.url, true);

// Set the headers
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('X-Ajax-Engine', 'Joomla!');

if (options.method === 'POST' && (!options.headers || !options.headers['Content-Type'])) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
if (options.method === 'POST') {
var token = Joomla.getOptions('csrf.token', '');

if (token) {
xhr.setRequestHeader('X-CSRF-Token', token);
}

if (!options.headers || !options.headers['Content-Type']) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
}
}

// Custom headers
if (options.headers){
for (var p in options.headers){
for (var p in options.headers) {
if (options.headers.hasOwnProperty(p)) {
xhr.setRequestHeader(p, options.headers[p]);
}
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/suites/libraries/cms/html/JHtmlFormTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,24 @@ public function testToken()
$this->equalTo('<input type="hidden" name="' . $token . '" value="1" />')
);
}

/**
* Tests the JHtmlForm::csrf method.
*
* @return void
*
* @since __DEPLOY_VERSION__
*/
public function testCsrf()
{
JFactory::$application = $this->getMockCmsApp();
JFactory::$document = new JDocumentHtml;

JHtmlForm::csrf();

$doc = JFactory::getDocument();
$options = $this->getObjectAttribute($doc, 'scriptOptions');

$this->assertEquals(JSession::getFormToken(), $options['csrf.token']);
}
}
19 changes: 19 additions & 0 deletions tests/unit/suites/libraries/cms/html/JHtmlJqueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,23 @@ public function testUi()
'Verify that the jQueryUI sortable script is loaded'
);
}

/**
* Tests the token() method.
*
* @return void
*
* @since __DEPLOY_VERSION__
*/
public function testToken()
{
JHtmlJquery::token();

$doc = JFactory::getDocument();

$script = $doc->_script['text/javascript'];
$expected = ";(function ($) { $.ajaxSetup({ headers: { 'X-CSRF-Token': Joomla.getOptions('csrf.token') } }); })(jQuery);";

self::assertEquals($expected, preg_replace('/\s+/', ' ', $script));
}
}

0 comments on commit 09f5cd5

Please sign in to comment.