The primary vulnerability we need to be careful of in Javascript is Cross Site Scripting (XSS). We’re probably all familiar with the escaping functions we use with PHP in WordPress to avoid that — esc_html()
, esc_attr()
, esc_url()
, etc. Given that, it only seems natural that we would also need to escape HTML in Javascript.
As it turns out out, however, that’s the wrong way to approach Javascript security. To avoid XSS, we want to avoid inserting HTML directly into the document and instead, programmatically create DOM nodes and append them to the DOM. This means avoiding .html()
, .innerHTML
, and other related functions, and instead using .append()
, .prepend()
, .before()
, .after()
, and so on.
Here is an example:
jQuery.ajax({
url: 'http://any-site.com/endpoint.json'
}).done( function( data ) {
var link = '<a href="' + data.url + '">' + data.title + '</a>';
jQuery( '#my-div' ).html( link );
});
This approach is dangerous, because we’re trusting that the response from any-site.com
includes only safe data – something we can not guarantee, even if the site is our own. Who is to say that data.title
doesn’t contain <script>alert( "haxxored");</script>
;?
Instead, the correct approach is to create a new DOM node programmatically, then attach it to the DOM:
jQuery.ajax({
url: 'http://any-site.com/endpoint.json'
}).done( function( data ) {
var a = jQuery( '<a />' );
a.attr( 'href', data.url );
a.text( data.title );
jQuery( '#my-div' ).append( a );
});
Note: It’s technically faster to insert HTML, because the browser is optimized to parse HTML. The best solution is to minimize insertions of DOM nodes by building larger objects in memory then insert it into the DOM all at once, when possible.
By passing the data through either jQuery or the browser’s DOM API’s, we ensure the values are properly sanitized and remove the need to inject insecure HTML snippets into the page.
To ensure the security of your application, use the DOM APIs provided by the browser (or jQuery) for all DOM manipulation.
Escaping Dynamic JavaScript Values
When it comes to sending dynamic data from PHP to JavaScript, care must be taken to ensure values are properly escaped. The core function esc_js()
helps escape JavaScript for us in DOM attributes, while all other values should be encoded with wp_json_encode()
.
From the WP Codex on esc_js():
It is intended to be used for inline JS (in a tag attribute, for example onclick=”…”).
…
If you’re not working with inline JS in HTML event handler attributes, a more suitable function to use is wp_json_encode, which is built-in to WordPress.
This approach is incorrect:
var title = '<?php echo esc_js( $title ); ?>';
var content = '<?php echo esc_js( $content ); ?>';
var comment_count = '<?php echo esc_js( $comment_count ); ?>';
Instead, it’s better to use wp_json_encode()
(note that wp_json_encode()
adds the quotes automatically):
var title = <?php echo wp_json_encode( $title ); ?>;
var content = <?php echo wp_json_encode( $content ); ?>;
var comment_count = <?php echo wp_json_encode( $comment_count ); ?>;
Stripping Tags
It may be tempting to use .html()
followed by .text()
to strip tags – but this approach is still vulnerable to attack:
// Incorrect
var text = jQuery('<div />').html( some_html_string ).text();
jQuery( '.some-div' ).html( text );
Setting the HTML of an element will always trigger things like src
attributes to be executed, which can lead to attacks like this:
// XSS attack waiting to happen
var some_html_string = '<img src="a" onerror="alert(\'haxxored\');" />';
As soon as that string is set as a DOM element’s HTML (even if it’s not currently attached to the DOM!), src
will be loaded, will error out, and the code in the onerror
handler will be executed…all before .text()
is ever called.
The need to strip tags is indicative of bad practices – remember, always use the appropriate API for DOM manipulation.
// Correct
jQuery( '.some-div' ).text( some_html_string );
Other Common XSS Vectors
- Using
eval()
. Never do this.
- Un-whitelisted / un-sanitized data from urls, url fragments, query strings, cookies
- Including un-trusted / un-reviewed 3rd party JS libraries
- Using out-dated / un-patched 3rd party JS libraries
Helpful Resources
You must be logged in to post a comment.