Hi,

At present we have a Drupal JS version of the check_plain() function, but we don't have JS equivalent of filter_xss(). The attached patch implements this functionality. It includes a JS version of filter_xss() called Drupal.filterXSS() along with JS versions of the other functions that filter_xss() utilises.

I've tested this on my own system and it appears to work very well. There may be a more elegant way to do some of this, but it's a good starting point.

Cheers,
Stella

Comments

Status: Needs review » Needs work

Patch failed to apply. More information can be found at http://testing.drupal.org/node/14111. If you need help with creating patches please look at http://drupal.org/patch/create

stella’s picture

Status: Needs work » Needs review
StatusFileSize
new11.24 KB

Re-attaching with patch made from within the drupal directory this time, instead of within the misc directory.

stella’s picture

This new JS filterXSS() function is intended for use by JavaScript files in Drupal which handle user input directly. To use it just do:

safeInput = Drupal.filterXSS(unsafeInput);

Cheers,
Stella

robin monks’s picture

Status: Needs review » Reviewed & tested by the community

I tested it out:
document.write(Drupal.filterXSS('<script>alert("xss!");'));
and this came out:

alert("xss!");

Looks good, and very useful IMHO.

Robin

lilou’s picture

Status: Reviewed & tested by the community » Needs work
stella’s picture

Status: Needs work » Needs review
StatusFileSize
new11.25 KB

Updated. Thanks.

Cheers,
Stella

john morahan’s picture

Status: Needs review » Needs work

Doesn't seem to be working properly. I tried this:
document.write(Drupal.filterXSS("<strong> <script>alert('xss');</scr"+"ipt> foo </strong>"));
It wrongly removed the <strong>, and failed to remove the <script>.

lilou’s picture

Need pass this tests : http://ha.ckers.org/xss.html

stella’s picture

Status: Needs work » Needs review
StatusFileSize
new12.61 KB

Yup, there was a big error in the last patch but I've fixed that and re-rolled the patch again. I've tested it with John's example above, along with most of the ones from the link that lilou provided.

lilou: thanks for that link, it was very helpful!

Cheers,
Stella

lilou’s picture

StatusFileSize
new16.56 KB

@stella : you can test all in one ...

nedjo’s picture

I can imagine why this might be useful, but it's a fair bit of code and we won't be using it yet in core.

Can you expand a bit on the use cases? Why is this needed? When is checkPlain not appropriate? How widespread is the need? (Without I guess naming specific modules that should be doing this but aren't ;)).

stella’s picture

I know of a couple of modules (which shall remain nameless) who need this functionality. These modules have javascript which take input from the user (trusted and/or untrusted users), do something with it and then display it. This user-entered input can validly contain html tags, and this is in fact a feature. Therefore using Drupal.checkPlain() is not an option for these modules as it would break the functionality.

stella’s picture

I've tested all of the various XSS strings in the file lilou provided and as far as I can see all tests passed. I'd appreciate it if someone else could also verify this.

Cheers,
Stella

moshe weitzman’s picture

@stella - these modules (why should they remain nameless?) take untrusted input and show them to different users? I doubt it, because then you could have run filter_xss() on the server side. I don't yet see the point in filtering a user's own input when shown to self?

stella’s picture

I don't want to name them here because I think they're vulnerable. I've been talking to Heine about this issue, but have only just realised that no mail was sent to the security team list, so I've sent one now.

To answer your question, these modules take untrusted input from node body field and displays it to any users who can view the same node, so it's not just shown to self. See my mail to the security list for more details on how this can happen.

Cheers,
Stella

Anonymous’s picture

Status: Needs review » Needs work

The last submitted patch failed testing.

stella’s picture

Status: Needs work » Needs review
StatusFileSize
new11.25 KB

Re-rolled patch against latest HEAD.

markus_petrux’s picture

hmm... couldn't you do this using an AJAX request where ANY input format could be invoked?

filter_xss() is pretty limited because it allows only whitelisting HTML elements, and then it blindly allows any attributes in them, except on* and style, but you can still pass class or id, which can cause harm to the page layout, or otherwise unexpeted results. So, there maybe someone that needs a different kind of filter such as HTML Purifier, htmlLawed, WYSIWYG Filter, etc.

PS: when porting filter_xss(), please take this issue into account.

Status: Needs review » Needs work

The last submitted patch failed testing.

lilou’s picture

Status: Needs work » Needs review

failure was due to HEAD not installing properly

blueyed’s picture

I think Markus has a valid point here:
why not implement it through an AJAX request and use the same functionality as on the server side?
This would provide the same results without duplicating any code.

wrwrwr’s picture

Not bad, but it won't be that easy :) Should be simpler and cleaner using AJAX, but if you want it that way, here are some thoughts.

Firstly, it eats my tags:

Drupal.filterXSS('<em>uuu</em>');
Drupal.filterXSS('<em>uuu</em>', new Array('em'));

both give just: uuu</em>. (Seems it only likes first tags: "a<em>b</em>c<em>d</em>" gives "ab</em>c<em>d</em>".)

Secondly, the "for (var character in replace) ..." loop is risky. According to par. 12.2, ECMA-357, order of properties is implementation dependent. Not sure if any implementation would really reorder that, but, if so, this would at least cause removing of Netscape 4.x JS entites ineffective.

Thirdly, entity decoding doesn't seem to take numerical character entities into account, so:

Drupal.filterXSS('<img src="jav&&#x23;x0D;ascript:alert(0)" />', new Array('img'));

should work in IE6 (that is x0D before or inside a protocol). This is unconfirmed, because it eats my tags ;)

While trying to verify the above I've hit something like this:

Drupal.filterXSS('<img><img src="javascript:alert(0);" /><img src="" /><img src="" /><img src=""  />asdf', new Array('img'));

which surprisingly does work (IE6, a script in an article using document.write). Not sure why. :)

robloach’s picture

Is there anywhere in core that would benefit from this?

stella’s picture

StatusFileSize
new1.89 KB

Sorry for the delay in responding. I've completely reworked the patch based on markus_petrux's suggestion to use AJAX. It's now much simpler and easier, and since it uses Drupal's existing filter_xss(), it shouldn't have any of the limitations that existed in the previous patch.

It defines a system/filter-xss menu callback item, which calls filter_xss() on $_GET['string']. There's a short Drupal.filterXSS() javascript function which uses a synchronous ajax request to retrieve system/filter-xss. Personally I would prefer an asynchronous ajax request, but I don't see how that is possible.

I would also like to extend the patch so it can handle the second param to filter_xss(), which is an array of allowed tags, but haven't decided how best to implement this. So feedback much appreciated.

Cheers,
Stella

markus_petrux’s picture

Being a GET request, it may fail depending on the size of the string to parse -vs- server configuration and/or hardcoded limitations.

See for example: http://httpd.apache.org/docs/2.0/mod/core.html#limitrequestline

Depending on the purpose of this API, it might be interesting to use filter_xss_admin() or check_markup(). For passing arguments, maybe using Drupal.settings?

markus_petrux’s picture

In regards to synchronous/asynchronous dilema... if it was asynchronous, it could accept a callback which would be used to process the response, which probably be used to alter the DOM in some way, I guess. :)

stella’s picture

StatusFileSize
new2.12 KB

Here's a new patch which uses POST instead of GET, and which has support for the optional "allowed_tags" param to filter_xss().

Within your javascript, you use it like:

filtered_string = Drupal.filterXSS(unsafe_string);

or

filtered_string = Drupal.filterXSS(unsafe_string, ['a', 'img']);

If we were to use asynchronous, then rather than returning the safe string directly, we would need to alter the DOM and replace the unsafe string, or alter a particular div/span, etc, with the filtered string. This may not suit all use cases.

Cheers,
Stella

Status: Needs review » Needs work

The last submitted patch failed testing.

stella’s picture

Status: Needs work » Needs review
StatusFileSize
new2.12 KB

Re-roll...

sun’s picture

Issue tags: +JavaScript
kkaefer’s picture

Status: Needs review » Needs work

While using synchronous XMLHttpRequests (the A in AJAX means asynchronous, so this can no longer be named ‘AJAX’) facilitates the implementation of this function, it comes at a high cost: When the server or the user’s connection is slow, such a request COMPLETELY BLOCKS the browser. The user can’t do any action on the site and has to wait until the request finishes.

kkaefer’s picture

I don't think such a function is really necessary in client-side JavaScript. The reason for filtering text for XSS is that an attacker might foist code to another user, stealing their session or performing other malicious actions like forging requests. However, when the user enters text which is subsequently injected into the very same page, the user could only harm themselves. When the input is stored on the server, it has to be filtered anyway *on the server* before it is sent back to the user (or another user).

Drupal.checkPlain() is mainly used for being able to display literally what the user entered and less for security purposes.

ufku’s picture

See #586098: Add a core Drupal.checkMarkup() function like check_markup() which could be more useful for input filtering.

sun’s picture

Version: 7.x-dev » 8.x-dev

No longer possible to do for D7.

stella’s picture

The Lightbox2 module had a SA a few months ago that required a Drupal.filterXSS() type function. The result was we implemented the above patch as a lightbox2 function. The asynchronous approach isn't ideal, but we can't use synchronous as that allows malicious code to be executed. Perhaps if we filtered the title or rel attributes for xss code, then this approach wouldn't be needed.

anjalik’s picture

Status: Needs work » Needs review

js_filter_xss.patch queued for re-testing.

nod_’s picture

Status: Needs review » Needs work

First it needs a reroll,
Second it needs to not use async: false, just like the checkMarkup issue.

stella’s picture

Second it needs to not use async: false, just like the checkMarkup issue.

that would allow inserted javascript to run on the site before the sanitized version was returned, so where's the point in adding this function then??

nod_’s picture

Well no, you have to wait for the callback to insert your sanitized content. You can't just use procedural code in JS, it doesn't work that way.

dsnopek’s picture

Issue summary: View changes

I've experimented with implementing this purely in Javascript, without an AJAX call back to Drupal. You can see my code here:

http://jsfiddle.net/dsnopek/ygwyaw18/

It seems to work, but without a TON more review and testing, I can't really say there isn't a browser/configuration/trick to get around it.

nod_’s picture

With CKEditor we have something similar because of the same security concerns. If we want that it should be looked into. It's using an async ajax call.

nod_’s picture

Status: Needs work » Closed (won't fix)

The use case outline in #12 is a bit thing to add that sort of functionality especially when the kind of people doing that nowdays are probably using react, angular or whatever is popular in 2 weeks.