Replace Options From AJAX Response in XSS Safe Manner
Asked Answered
E

5

5

From within the succcess method of my AJAX response, my goal is to do the following in an XSS safe manner:

  • remove all existing options within a select box.
  • replace the options from that same select box.

Here is one way to remove and replace the options, but I don't have high confidence that this strategy is entirely XSS safe:

success: function (data) {
    $('#mySelBox').children().remove();
    $.each(data, function (index, value) {
        $('#mySelBox').append('<option value="' + value.id + '">' + value.description + '</option>');
    });
}

Specifically:

  • I'm not sure if value.id is XSS safe in that context.
  • I'm not sure if value.description is safe in that context.

Referencing the OWASP XSS cheatsheet):

[Ensure] that all variables go through validation and are then escaped or sanitized is known as perfect injection resistance.

To that end here are my questions:

  • What is the sure way to escape and sanitize value.id in the above context?
  • What is the sure way to escape and sanitize value.description in the above context?

I also found this XSS prevention article. It made me aware of how complicated XSS prevention can be because there is not one single solution to the problem: the solution depends entirely upon the context.

Em answered 14/2, 2023 at 18:14 Comment(3)
the below answser shows the safe way to do this, but I'll briefly show why your version isn't safe. If a malicious actor can provide their own data then they could ensure the id, for example, is: '"></option><script>someNastyXSSFunction()</script><option value="', which when inserted into your HTMl string results in perfectly valid HTML containing an injected script of their choice.Linell
So, data comes from your server. My question is, why it's not already sanitized server-side? (stripped HTML tags , etc.)Grimaldi
@RokoC.Buljan I'm currently of the opinion that it isn't safe to trust server-side data ever, no matter what. Many examples: inheriting a legacy database, a vulnerable feature went live for a brief time, a new vulnerability was discovered, a disgruntled employee performed sabotage. The defense-in-depth cybersecurity principle would say, yes: sanitize before saving in the database, but also sanitize before displaying data on the front end.Em
P
4

Instead of concatenated HTML strings, use the DOM API to create the <option> element:

$.each(data, function (index, value) {
  var opt = document.createElement("option");
  opt.setAttribute("value", value.id);
  opt.textContent = value.description;
  MY-SELECT-BOX.append(opt);
});

References:

setAttribute is considered secure when updating value: DOM based XSS Prevention - GUIDELINE #3

textContent is considered secure to populate the DOM with untrusted data: DOM based XSS Prevention - RULE #6

Proctology answered 14/2, 2023 at 18:21 Comment(2)
Thank you for the response. I will be creating a bounty here soon to get this question more attention and thus get more validation with this solution. That said: this solution does align with what I've read.Em
For authoritative source backing up this answer: For setAttribute: it is considered secure when updating value: cheatsheetseries.owasp.org/cheatsheets/… . textContent is also considered secure: cheatsheetseries.owasp.org/cheatsheets/…Em
L
4

As data is getting fetched from a backend from an API by making an AJAX call. Ideally this data should be validated while inserting into database/backend.

But still If you are in doubt or not much confident about the data you are getting from backend or any third party library, Then for safer side you can sanitize it at client side by just escaping/replace the blacklist tags/characters.

You can define regex which contains blacklisted HTML tags or any other malicious keyowrds. Like this :

const blacklist = /(<[^>]+>|<[^>]>|<\/[^>]>)|(document\.cookie)|eval|http-equiv/ig

And then replace the blacklisted characters with an empty value if encountered,

function sanitizeString(inputString) {
  return inputString.replace(blacklist, '')
}

Live Demo with a minimal use case just to show the approach :

const blacklist = /(<[^>]+>|<[^>]>|<\/[^>]>)|(document\.cookie)|eval|http-equiv/ig

function sanitizeString(inputString) {
  return inputString.replace(blacklist, '')
}

$.fn.populate = function (data) {
  var $selectbox = $(this);
  if ($selectbox.is("select")) {
    $.each(data, function (i, value) {
      $selectbox.append('<option value="' + value.id + '">' + value.description + '</option>');
    });
  }
};

$.fn.populateSanitized = function (data) {
  var $selectbox = $(this);
  if ($selectbox.is("select")) {
    $.each(data, function (i, value) {
      const id = sanitizeString(value.id);
      const option = sanitizeString(value.description);
      $selectbox.append('<option value="' + id + '">' + option + '</option>');
    });
  }
};

$(document).ready(function () {
  var data = [{
    id: '1',
    description: 'One'
  }, {
    id: '2',
    description: '<\script>Two<\/script>'
  }, {
    id: '3',
    description: 'Three'
  }, {
    id: '4',
    description: 'Four'
  }];
  
  $('#myselect').populate(data);
  $('#sanitizedSelect').populateSanitized(data);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<label>Without Sanitizing : <select id="myselect"></select></label>
<br><br>
<label>After Sanitizing : <select id="sanitizedSelect"></select></label>

enter image description here

Lamppost answered 22/2, 2023 at 13:33 Comment(2)
Within your code example, backticks should also be included in the blacklist, correct?Em
@Em blacklist is just a variable name. You can use any name. And yes, if you want to add any further unwanted characters or patterns, You can add those in the blacklist variable by using OR (|) symbol.Wanda
H
2

If you are using Jquery you can use text and html function:

Create a function to sanitize the text content:

function sanitize(text) {
  return $('<div>').text(text).html();
}

Call sanitize function each time you need to inject content into the DOM:

success: function (data) {
    $('#mySelBox').children().remove();
    $.each(data, function (index, value) {
        $('#mySelBox').append('<option value="' + sanitize(value.id) + '">' + sanitize(value.description) + '</option>');
    });
}

Working example

Also you can take a look at HTML Sanitizer API (Experimental)

Hospital answered 21/2, 2023 at 19:38 Comment(3)
Is this ultimately injecting a div tag into an option tag? If so, it feels kind of odd. As in: I'm unsure if div tags are intended to be used in this way.Em
There is not a div tag within option element, did you inspect the working example?Hospital
You are correct, no div tag is added. I simply read the code wrong.Em
N
0

HTML ESCAPING

When dealing with potentially dangerous input, you need to escape HTML before updating the DOM, as in this example malicious string input value:

"<script>alert('hacked');</script>"

If you render it directly within an options element, the malicious script executes:

<select id='mySelBox'>
   <option value='firstId'>first value</option>
   <option value='secondId'><script>alert('hacked');</script></option>
</select>

By using escaping, you ensure that the malicious value above is handled as text and not executable code. To write the escaping code yourself, see this answer, whose code I'm repeating below:

function escapeHtml(unsafe)
{
    return unsafe
         .replace(/&/g, "&amp;")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;")
         .replace(/"/g, "&quot;")
         .replace(/'/g, "&#039;");
 }

TEMPLATING LIBRARIES

In my opinion a cleaner way to do this is via a small templating library. One example that shows an approach for updating the DOM safely is mustache.js.

const myViewModel = {
    myOptions: [
        {
            myId: 'firstId',
            myValue: 'first value',
        },
        {
            myId: 'secondId',
            myValue: "<script>alert('hacked');</script>",
        }
    ]
};


const myHtmlTemplate = `
    {{#myOptions}}
        <option id='{{myId}}'>{{myValue}}</option>
    {{/myOptions}}
`;


const safeHtml = mustache.render(myHtmlTemplate, myViewModel);
const selBox = document.querySelector('#mySelBox');
if (selBox) {
    selBox.innerHTML = safeHtml;
}

The select box would then be rendered safely, like this:

enter image description here

WEB FRAMEWORKS

Web frameworks such as React and Angular also handle HTML safely, in a similar way. Whatever your preferences, using some kind of respected library or framework is usually recommended, due to the best practice guidance that comes with it.

Nl answered 19/2, 2023 at 10:18 Comment(2)
Within your escapeHtml function above: backticks are also considered a malicious value, correct? So backticks should also be html encoded?Em
I think it is just those 5, that are interpreted by HTML, whereas backticks are used in Javascript. The OWASP XSS article from your question has some info on this.Nl
K
0

As you already have read some reports and tried some stuff, and as all answers posted some potential ways, it's important to note that there is no one-size-fits-all solution to preventing XSS attacks, as the appropriate approach will depend on the context in which the data is being utilized.

However, by following some techniques and comprehending the risks associated with XSS attacks, you can help ensure that your web applications are secure and resistant to malicious attacks.

I would try my best to put out some of my opinions-

1. Using jquery text() method-

This will automatically escape any special characters that could be used in an XSS attack.

$('#mySelBox').append($('<option>', {
  value: value.id,
  text: value.description
}));

Problem- Does not perform any HTML encoding or escaping, which makes it vulnerable to XSS attacks.

2. Using encodeURIComponent method-

That method can encode any special characters.

$('#mySelBox').append($('<option>', {
  value: encodeURIComponent(value.id),
  text: value.description
}));

Problem- Not a certain solution, as it can still be possible to craft malicious input that can evade this encoding.

3. Using jquery HTML method-

This will automatically escape any HTML entities.

$('#mySelBox').append($('<option>', {
  value: value.id,
  html: value.description
}));

Problem- For user-generated content, this approach can lead to allowing the injection of malicious HTML code.

4. Manual functionality-

Own encoding functions can also be created like this-

function htmlEncode(str){
  return String(str).replace(/[^\w. ]/gi, function(c){
    return '&#'+c.charCodeAt(0)+';';
  });
}

Problem- Own encoding methods cannot cover all aspects.

A few other ways

Above All methods provide a different level of security but do not guarantee to prevent of an XSS attack completely. If read from some sources regarding XSS attacks, it is always a recommended way to do XSS validation both on the server side and the client side.

For example, Using the jQuery html() method with the DOMPurify library provides a higher level of security by removing any potentially malicious tags or attributes. This library provides automatic input validation and sanitization. According to its documentation-

DOMPurify sanitizes HTML and prevents XSS attacks. You can feed DOMPurify with a string full of dirty HTML and it will return a string (unless configured otherwise) with clean HTML. DOMPurify will strip out everything that contains dangerous HTML and thereby prevent XSS attacks and other nastiness. It's also damn bloody fast. We use the technologies the browser provides and turn them into an XSS filter. The faster your browser, the faster DOMPurify will be.

If you will look into the size of this package at bundlephobia, its minified size is only 22.1KB.

There must be another library as well as DomPurify is quite popular, so I pointed it out.


Again, as there is no full-proof solution to prevent XSS completely, a few of the rules always include in any approach-

  1. Untrusted data should not be inserted.
  2. HTML should be escaped before inserting any untrusted data.
  3. The attribute should be escaped before inserting the untrusted data, etc.

Here is a good article where you can read more about XSS attacks.

Kuroshio answered 27/3, 2023 at 13:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.