Cross-site scripting (XSS)

Cross-site scripting, or XSS, is a security vulnerability that can be found in web applications. This vulnerability allows attackers to inject malicious code/styles into a web page viewed by users. Extension developers should be aware of these vulnerabilities to avoid introducing them in their code.

There are three main types of XSS vulnerabilities:

Preventing XSS

XSS vulnerabilities can be prevented by validating and sanitizing user input as well as sanitizing dynamic values when rendering the view (HTML, mobile).

Input Processing

Any request data can be manipulated by attackers and can contain malicious values such as:

To combat this, developers must validate any value coming in from requests.

It is better to validate/sanitize values as close as possible to the view context because only then you can be sure of the restrictions you have to impose on dynamic values and you are not risking security requirements for business requirements.

There is no reason, from a business standpoint, to disallow < > symbols in your users' "About me" section. By escaping control symbols when rendering HTML, allowing these characters would not be problematic. "About me" data may be delivered via RESTful API, where {} could cause issues. If sanitized earlier, the user data would be damaged and contain HTML control symbols (< >).

Output Processing

Output processing involves sanitizing strings that may have come from external data sources before using it to render views. It is the main method of protecting your extension from XSS attacks.

The general rule is: Do not trust dynamic values.

PHTML templates

The \Magento\Framework\Escaper class is provided for .phtml templates and PHP classes responsible for generating HTML. It contains HTML sanitization methods for a variety of contexts.

The $escaper local variable is available inside the .phtml templates. See the product listing template as example of $escaper usage in .phtml templates.

See Template guide to read more about templates in Magento.

When using the \Magento\Framework\Escaper or $escaper:

The following code sample illustrates XSS-safe output in templates:

<?= $block->getTitleHtml() ?>
<?= $block->getHtmlTitle() ?>
<?= $escaper->escapeHtml($block->getTitle()) ?>
<?= (int)$block->getId() ?>
<?= count($var); ?>
<?= 'some text' ?>
<?= "some text" ?>
<a href="<?= $escaper->escapeUrl($block->getUrl()) ?>"><?= $block->getAnchorTextHtml() ?></a>

When to use Escaper methods:

Case: JSON inside an HTML attribute

Escaper method: escapeHtmlAttr

<div data-bind='settings: <?= $escaper->escapeHtmlAttr($myJson) ?>'></div>

Case: JSON inside script tag

Escaper method: no sanitization needed

<script>
let settings = <?= $myJson ?>
</script>

Case: HTML tag content that should not contain HTML

Escaper method: escapeHtml

Pass in an optional array of allowed tags that will not be escaped.

If a tag is allowed, the following attributes will not be escaped: id, class, href, style and title. Any other attribute for that allowed tag will be escaped.

embed, iframe, video, source, object, audio, script and img tags are not allowed, regardless of the content of this array.

 <span class="label"><?= $escaper->escapeHtml($block->getLabel()) ?></span>
  // Escaping translation
  <div id='my-element'>
      <?= $escaper->escapeHtml(__('Only registered users can write reviews. Please <a href="%1">Sign in</a> or <a href="%2">create an account</a>', $block->getLoginUrl(), $block->getCreateAccountUrl()), ['a']) ?>
  </div>

Case: URL inside certain HTML attributes

Escaper method: escapeUrl

Certain attributes like a.href accept URIs of various types and must be sanitized.

<a href="<?= $escaper->escapeUrl($myUrl) ?>">Click me</a>
<div attr-js-extracts="<?= $escaper->escapeHtmlAttr($myOtherUrl) ?>"></div>

Case: All JavaScript inside attributes must be escaped by escapeJs before escapeHtmlAttr:

Escaper method: escapeJS

<div
    onclick="<?= $escaper->escapeHtmlAttr('handler("' . $escaper->escapeJs($aString) . '", ' . $escaper->escapeJs($aVar) .')') ?>">
    My DIV
</div>

Case: JavaScript string that must not contain JS/HTML

Escaper method: escapeJS

<script>
let phrase = "Hi, my name is <?= $escaper->escapeJs($myName) ?>";
//Do not use HTMl context methods like escapeUrl
let redirectUrl = "<?= $escaper->escapeJs($myUrl) ?>";
location.href = redirectUrl;
</script>

Case: JavaScript variable that must not contain JS/HTML

Escaper method: escapeJS

<script>
let <?= $escaper->escapeJs($dynamicVariable) ?> = <?= $myJson ?>;
settings.<?= $escaper->escapeJs($myProperty) ?> = true;
</script>

Knockout templates

In knockout templates, you can bind a UI component's property/function as the 'inner HTML' of an element. Such properties may contain dynamic data and must be sanitized within components. See binding syntax to learn more about UI component templates.

In order to notify developers that these properties/function results may contain HTML, the application requires (with the help of a static test) that you name such properties/functions using "UnsanitizedHtml" suffix.

<div data-bind="html: propUnsanitizedHtml"></div>
<p html="returnUnsanitizedHtml()"></p>

Dynamically created DOM elements

When using variables that are not supposed to contain HTML, the safest way to generate DOM elements is to create them programmatically using the appropriate API, instead of using the innerHtml property or jQuery's .html() function.

let newDiv = document.createElement("div");
newDiv.innerText = userName;
newDiv.setAttribute("custom-attribute", myAttribute);
parentElement.appendChild(newDiv);

UI component data providers

UI component data providers pass dynamic (user generated) data to UI components. The data they return is rendered in order to support component dynamic linking. Since user data is supposed to be treated as a literal value which is not refering to any other component, rendering of these properties must be disabled. See UI component data providers to read more about data providers and component linking.

$uiData = ['linkProperty' => '${ $.otherComponent.value }'];
$uiData['customer'] = $customer->getData();
//Customer data will be taken as is, linkProperty will be retrieved from otherComponent and taken as is
$uiData['__disableTmpl'] = ['customer' => true, 'linkProperty' => 1];

return $uiData;

Static Test

To check your .phtml template for XSS vulnerabilities, use the Magento2.Security.XssTemplate sniff from Coding Standard. This sniff finds all echo calls in PHTML-templates and determines if the output is properly escaped. It covers the following cases: