Security

SQL Injection

To prevent SQL injection, all external data must be filtered and then passed into queries via parameters. Table names should be included with curly braces around them (which will automatically apply the correct $CFG->prefix value).

AJAX scripts

AJAX requests require a particular coding style to avoid security issues. Please ensure your AJAX scripts follow the guidelines below:

Script headers

IE7 can be tricked into encoding documents in UTF-7 which can lead to security issues. While IE7 is no longer supported, passing the right headers remains good security practice. Ensure the script passes the correct output headers so it will be encoded as UTF-8 by defining AJAX_SCRIPT before config.php is included, then calling header():

Setting AJAX_SCRIPT will also prevent redirects and ensure the ajax render is used which will give nicely formatted JSON error messages which makes it easier to pass back errors to the calling script.

CSRF token

To prevent other websites from triggering the AJAX request, always include and require the sesskey in all AJAX scripts. This is done by passing the token generated by sesskey() into the script as a parameter and adding the following line near the top of your script:

Require login

If your script should only be accessed by logged in users (most of the time this is true), then make sure you include:

It is important to supply $course and $cm parameters if the ajax request is within the context of a course because they are used for preventing of access to hidden courses and activities. If you have only $context use get_context_info_array() to find out $course and $cm parameters.

Function require_course_login() is used rarely, it allows not-logged-in users to access resources on the frontpage. Most likely you will never use it in Totara code.

Access control

Users can access your script directly and alter the input parameters to attempt to extract data that is not theirs. Make sure you independently validate that the user has access to the data they are requesting. This is typically done via require_capability() or a specific access control method.

If you need multiple parameters (such as course id and course module id) pass in the most specific and calculate the others from that to avoid potential inconsistencies.

Input parameters

Clean all input parameters with normal parameter cleaning. Limit the types of input parameters to PARAM_INT and PARAM_ACTION and calculate any other data you need from those values.

Output

Return JSON generated using json_encode():Ensure any user-supplied data is cleaned if it is going to be output to the page. To make that possible we strongly recommend that you return a data object from your AJAX script, and use templates to render that via Javascript, rather than simply returning HTML from the AJAX request and then embedding it in the page. The reason for that is that it makes it much easier to separate and clean user content from system content. For example, imagine the scenario where an AJAX request returned a mix of user content and results:In this situation the user could potentially embed HTML in the search term. To avoid that you may want to use htmlentities(), but doing so would affect the (safe) HTML returned by the script.

It would be better to return a JSON object:which could be rendered to HTML via a template.

AJAX script template

Use this template as a starting point for new scripts:

define('AJAX_SCRIPT', true);

// Update path to config if required.
require_once(__DIR__ . '/../../config.php');

// Limit input params to INT and ACTION types.
$id = required_param('id', PARAM_INT);
$action = required_param('action', PARAM_ACTION);

// If within a course, get $course and $cm, and use course or module context, otherwise get system context
$context = context_system::instance();

$PAGE->set_context($context);
$PAGE->set_url('/path/to/ajax/script.php');

// Include $course and $cm if relevant (see description of require_login() above)
require_login();
require_sesskey();

// Implement access control checks.
require_capability('somecapability', $context);
$someclass->has_access($USER, $id);

// Output headers
echo $OUTPUT->header();

// Create results object
$data = new stdClass();
$data->success = true;
$data->result = $someclass->get_data();

// Output JSON encoded data
echo json_encode($data);


CSRF

To prevent Cross site request forgery (CSRF), any actions that lead to a permanent change should check for the existence of the CSRF token (for example changing the order of two items, or deleting an item). This can be obtained by calling sesskey() and checked using require_sesskey(). Moodle forms pass and check the CSRF token automatically.

Note that the CSRF is relied upon to ensure a user is who they say they are, so it is important to ensure the token is not leaked to an external system as it could allow the user to be impersonated. Please follow these guidelines for CSRF token use:

  1. Do not include the CSRF in the URL of a regular page that a user could browse to, particularly if it can contain user generated content.
  2. It is best practice to use POST requests when performing actions that lead to a permanent change. This also has the advantage of keeping the CSRF out of the URL.
  3. After checking and performing an action using the CSRF token, make sure you redirect to the next page. Never perform the action then render the rest of the page straight away.