Skip to content

Instantly share code, notes, and snippets.

@tecywiz121
Created August 24, 2012 17:52
Show Gist options
  • Save tecywiz121/3453443 to your computer and use it in GitHub Desktop.
Save tecywiz121/3453443 to your computer and use it in GitHub Desktop.
Mitigate XSS client side
/*
* A proof of concept client-side XSS mitigation library with minimal server side requirements.
*
* Installation:
*
* Insert this script immediately following the opening tag of the body element.
*
* Strip every instance of </noscript> and </iframe> before the output leaves
* your server.
*/
(function () {
var userAgent = navigator.userAgent.toLowerCase();
// Figure out what browser is being used
var browser = {
version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
safari: /webkit/.test(userAgent),
opera: /opera/.test(userAgent),
msie: (/msie/.test(userAgent)) && (!/opera/.test( userAgent )),
mozilla: (/mozilla/.test(userAgent)) && (!/(compatible|webkit)/.test(userAgent))
};
// Remove the body from the DOM so scripts don't execute as they load
var parent = document.body.parentNode,
body = document.body,
wrapper;
// Write a tag to stop the browser from even parsing the rest of
// the document.
if (browser.msie) {
// Internet Explorer doesn't support reading the contents of noscript
// tags, so we use an iframe with javascript disabled instead.
document.write('<iframe sandbox security="restricted">')
wrapper = 'iframe';
} else {
// Write the wrapper tag!
document.write('<noscript>');
wrapper = 'noscript';
// Pull the body tag out of the dom just in case
// (IE doesn't allow this kind of modification)
body = document.body.parentNode.removeChild(document.body);
}
var shouldRemove = function(name, value) {
name = name.toLowerCase();
value = value.toLowerCase();
// TODO: Whitelist url schemes to only a handfull, like http, https, ftp, mailto, etc
return name === 'style' || name.substr(0, 2) === 'on' || value.substr(0, 11) === 'javascript:';
};
var callback = function (e) {
var noscript = body.getElementsByTagName(wrapper),
elements,
scripts,
attributes,
ii,
jj,
old_body = body;
if (noscript.length === 0) {
setTimeout(callback, 0);
return;
}
noscript = noscript[0];
// Build the new body element from the contents of the noscript tag
if (browser.msie) {
body = document.body;
} else {
body = document.createElement('body');
}
if (typeof(noscript.textContent) !== 'undefined') {
body.innerHTML = noscript.textContent;
} else if(typeof(noscript.innerText) != 'undefined') {
body.innerHTML = noscript.innerHTML;
} else {
alert('Unable to unescape contents, sorry...');
return;
}
// Remove events inside the body
elements = body.querySelectorAll('*');
for (ii = 0; ii < elements.length; ii += 1) {
attributes = elements[ii].attributes;
for (jj = 0; jj < attributes.length; jj += 1) {
if (shouldRemove(attributes[jj].nodeName, attributes[jj].value)) {
elements[ii].removeAttribute(attributes[jj].nodeName);
}
}
}
// Remove script tags inside the body.
scripts = body.querySelectorAll('script, object, embed, style, link');
for (ii = scripts.length-1; ii >= 0; ii -= 1) {
scripts[ii].parentNode.removeChild(scripts[ii]);
}
if (!browser.msie) {
// Reinsert the body into the document after it's been cleaned
parent.appendChild(body);
}
};
if (browser.msie) {
if (window.onload) {
window.onload = (function (old_func) {
return function (e) {
callback(e);
old_func(e);
};
}(window.onload));
} else {
window.onload = callback;
}
} else {
document.addEventListener('DOMContentLoaded', callback, false);
}
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment