Javascript - Some quick javascript utility methods for XSS filtering

I often find myself needing to "eval" some content in javascript, where the content happens to be the name of a callback method (sometimes from another iframe on the same web page, e.g. parent.Portal.onWallRendered). You see I create a lot of frameworks that can be hosted by external apps, so those frameworks allow passing in the name of the method to be invoked back when it has rendered on the page (often dynamically on demand) as a query-string parameter.

XSS/CSRF etc. are a continuous threat in such scenarios, where a malicious user can send a manually-crafted (often tiny) URL to another user having dire consequences for my code and framework(s). So, I use a couple of utility methods to filter out attack vectors from the url query-string parameters where the parameters are expected to contain method names or other identifiers (e.g. frame names). Here are those methods:

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }Rahul = {}; Rahul.Xss = {}; Rahul.Xss.isIdentifier = function (str) { if (!Rahul.isString(str)) { return (false); } else { var match = str.match(/[^a-zA-Z0-9_]+/g); if (match && match.length > 0) { return (false); } else { return (true); } } } Imbibe.Xss.isNamespacedIdentifier = function (str) { if (!Rahul.isString(str)) { return (false); } else { var match = str.match(/[^a-zA-Z0-9_.]+/g); if (match && match.length > 0) { return (false); } else { return (true); } } }{/syntaxhighlighter}

The first method, isIdentifier essentially allows only alphabets, digits and underscore in a string. If the string contains anything else (or if the parameter is not a string), it returns false.

The second method, isNamespacedIdentifier additionally allows a dot (.) in a string in addition to alphabets, digits and underscore.

Although javascript allows some additional characters (e.g. dollar ($)) in identifiers, my code does not allow them to keep things straight-forward. If you need to allow additional characters, you can easily add them to the Regexs.

The above methods depend on another utility method isString, whose code is below:

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }Rahul.isString = function(v){ return typeof v === 'string'; }{/syntaxhighlighter}

 

Another set of methods I use for XSS filtering is to check whether the current page has been browsed to from another page on the same domain (or a page on another allowed domain). You see most of the times, when your web app allows some transaction, you expect the user to come to your page from previous page on the same or another domain (that you expect). So if the url referrer for the current page is not from a domain you expect it to be, someone is trying to trick the user with a manually crafted url. The following methods return true if the referrer for the current page is from another allowed domain or from the same domain respectively:

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }Rahul.Xss.isReferrerFromUrl = function (url) { var referrerParts = Rahul.parseUrl(document.referrer); var urlParts = Rahul.parseUrl(url); if (referrerParts.protocol == urlParts.protocol && referrerParts.host == urlParts.host && referrerParts.port == urlParts.port) { return (true); } else { return (false); } } Rahul.Xss.isReferrerFromCurrentUrl = function () { return (Rahul.Xss.isReferrerFromUrl(document.location.href)); }{/syntaxhighlighter}

The first method returns whether the current url is browsed to (i.e. referred) from the same domain whose url is passed as the parameter. The second method returns true if the current url was referred from the same domain itself.

The above methods depend upon another utility method parseUrl, whose code is below:

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }Rahul.parseUrl = function(url) { var protocol = '', host = '', port = null, path = '', query = '', fragment = ''; var remaining = url; var index = remaining.indexOf('//'); if (index != -1) { protocol = remaining.substr(0, index - 1); remaining = remaining.substr(index + 2); index = remaining.indexOf(':'); if (index != -1) { host = remaining.substr(0, index); remaining = remaining.substr(index + 1); index = remaining.indexOfAny(['/', '?', '#']); if (index != -1) { port = remaining.substr(0, index); remaining = remaining.substr(index); } else { protocol = remaining; remaining = ''; } } else { index = remaining.indexOfAny(['/', '?', '#']); if (index != -1) { host = remaining.substr(0, index); remaining = remaining.substr(index); } else { host = remaining; remaining = ''; } } } else { remaining = url; } index = remaining.indexOfAny(['?', '#']); if (index != -1) { path = remaining.substr(0, index); remaining = remaining.substr(index); } else { path = remaining; remaining = ''; } if (path[0] == '/') path = path.substr(1); index = remaining.indexOf('#'); if (index != -1) { query = remaining.substr(0, index); remaining = remaining.substr(index); } else { query = remaining; remaining = ''; } if (query[0] == '?') query = query.substr(1); fragment = remaining; if (fragment[0] == '#') fragment = fragment.substr(1); return ({ protocol: protocol, host: host, port: port, path: path, query: query, //Uncomment line below if you are suing ExtJs //queryParameters: Ext.urlDecode(query), fragment: fragment }); }{/syntaxhighlighter}

The above method takes any url string and return an object containing the url components.

 

Tags: 
Web 2.0: 

Comments

That is some awesome coding here my friend! You are amazing!