Cross-Site Scripting Defense

Introduction

Cross-Site Scripting, XSS, is a class of vulnerabilities that allow an attacker to inject malicious scripts, such as javascript, into a web page that gets executed on a victims browser. This can allow the attacker to compromise the browser and begin to attack the victim directly through the browser.

This attack is very easy to initiate for one simple reason: browsers are very trusting. Generally, they trust any content that they receive from a server; however, if the content that the browser receives has been tampered with, the browser is generally defenseless. This means the protection must fall to developers to teach the website what content is fully trusted vs untrusted, and what to do with untrusted content.

The Commerce Cloud recommendation for protecting sites against Cross-Site Scripting is Output Encode, input validate, using our built-in mechanisms. Output Encoding is used when data that lives in the application may contain untrusted data, but once that data has left the application and is placed in a web page destined for a user, the potentially compromised data should be encoded for the context in which it lives – e.g. if the context is an HTML page, the data should encode HTML markup, such as the greater-than and less-than signs (><)

Threats

  • Counteracts Cross Site Scripting Attack

Why is this Best Practice Important?

In Commerce Cloud Storefronts, this attack is one of the most common vectors to attack storefront users. Since the attack uses the server as a staging area and directly attacks users’ browsers, it is nearly impossible to detect when an attack is happening on the server, since the attack happens on client machines.

By setting up defenses inside the application, customer developers can ensure that attackers cannot use the ecommerce site as a staging ground to attack storefront users.

How does this Best Practice protect against Threats?

  • Output Encoding: The preferred method for protection against XSS, output encoding sanitizes data before being sent to browsers. This neutralizes potentially malicious text without affecting how it is processed internally.
  • Input Validation: Another strong method for protecting against XSS is Input Validation. Unlike Output Encoding, this method examines all parameters upon receipt and if any parameter value does not conform to an expected value, the request is rejected. Input Validation is typically harder to accomplish in nonnaive cases. Commerce Cloud recommends Input Validation to examine types of data, rather than contents – e.g. examine the parameter item_number to ensure that it is a number and not a string. Otherwise, it is generally easier to output encode as a rule.

Does this Best Practice not protect against any part of a Threat?

XSS Vectors change frequently. There may be attacks that attack specific functionality of a browser  or a new class of vectors may be discovered. The defenses that Commerce Cloud provides customers for Output Encoding solves significant classes of XSS attacks in most cases. Input Validation can further protect a site from unexpected inputs, limiting attacks. An additional, free, defense is that some browsers implement automatic XSS protection filtering, which further restricts vulnerable inputs.

Code Examples

Example of Bad Practice

This is a typical example of a vulnerable XSS output vector. Here a new developer has decided to copy code without understanding the side effects. Unfortunately for the business, the code was copied from a bad example, where encoding is explicitly turned off:

				
					Welcome Back <isprint value="${user_name}" encoding="off"/>
				
			

Similarly, there is an API, dw.io.PrintWriter, exposed as the variable “out,” that does no checking against the contents before simply outputting to the generated HTML. The developer is attempting to output the contents of a variable, custom_comment. The customer comment should be able to bold certain parts of the review.

				
					<isscript>
    out.println("<div class='comments'>");
    out.println(custom_comment); // allow bolding
    out.println("</div>");
</isscript>
				
			

Finally, a common anti-pattern is to try to output JSON data without encoding. While this looks like a workaround to incorrect encoding, the issue here comes from having a JSON context hide an HTML context:

				
					<isscript>
    var comments = CustomerHelpers.getCommentsForProduct(product_id);
    var json = JSON.stringify(comments);
</isscript>

<script>
    var comment_json = <isprint value="${json}" encoding="off"/>;
    injectComments(comment_json);
</script>
				
			

Example of Best Practice

The first example is a simple fix. The developer can change the encoding option to “on” or remove the encoding attribute entirely, since <isprint /> encodes automatically by default:

				
					Welcome Back <isprint value="${user_name}" encoding="on"/>
...
Welcome Back <isprint value="${user_name}"/>
				
			

To fix the second example, developers should be extremely cautious. The recommended fix is to reject this feature and as it would be rife with potential issues and just encode the full value.

It may be tempting to Input Validate, however validating HTML code is very difficult and not recommended to do by hand. All other attempts at encoding are hacks and not production-ready.

				
					<isscript>
    out.println("<div class='comments'>");
    var clean_custom_comment = dw.util.SecureEncoder.forHTMLContent(custom_comment); // disallow all HTML
    out.println(clean_custom_comment);
    out.println("</div>");
</isscript>
				
			

The final example is a prime example of Double Encoding. In Double Encoding, a piece of data may have to be wrapped in multiple layers of encoding in order to remain safe. This makes the values difficult to handle, but should be treated as a simple stack. First encoding used is the last decoding used.

Since this JSON contains comments to be injected into HTML, the first encoding should be for HTML. The data is then transferred via JS, so JSON stringify encodes the data handily. injectComments would then have to decode the JSON, and insert into HTML, still encoded. This might look something like this:

				
					<isscript>
    var comments = CustomerHelpers.getCommentsForProduct(product_id);
    for(var key in comments) {
        if(comment.hasOwnProperty(key)) {
            comments[key] = dw.util.SecureEncoder.forJSONValue(comments[key]); // Ensure JSON values are encode
        }
    }
    var json = JSON.stringify(comments); // turn JSON into manageable string
</isscript>

<script>
    var comment_json = <isprint value="${json}" encoding="off"/>; // encoding="off" is ok here as the values are definitely encoded
    injectComments(comment_json); // As values are used, they can be inserted directly into HTML safely
</script>
				
			

How to be a Certified Salesforce Commerce Cloud Developer for FREE

Unlock a FREE PDF with SFCC B2C Certification questions from our Udemy Course. Join our newsletter!
Check your email, you’ll receive a copy in a few seconds. If you don’t see it, please check your spam folder.

Do you like cookies? 🍪 We use cookies to ensure you get the best experience on our website. Learn more.