mirror of https://github.com/calzoneman/sync.git
Implement basic XSS filter
This commit is contained in:
parent
bf014530f9
commit
271a23cdad
|
@ -0,0 +1,171 @@
|
|||
function TagParser(text) {
|
||||
this.text = text;
|
||||
this.i = 0;
|
||||
this.tag = this.parse();
|
||||
}
|
||||
|
||||
TagParser.prototype.skipWhitespace = function () {
|
||||
while (this.i < this.text.length && this.text[this.i].match(/\s/)) {
|
||||
this.i++;
|
||||
}
|
||||
};
|
||||
|
||||
TagParser.prototype.readLiteral = function (regexp) {
|
||||
if (regexp === void 0) {
|
||||
regexp = /[^\s>]/;
|
||||
}
|
||||
var str = "";
|
||||
while (this.i < this.text.length && this.text[this.i].match(regexp)) {
|
||||
str += this.text[this.i];
|
||||
this.i++;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
TagParser.prototype.readLiteralOrString = function (regexp) {
|
||||
if (this.text[this.i].match(/["']/)) {
|
||||
return this.readString();
|
||||
}
|
||||
return this.readLiteral(regexp);
|
||||
};
|
||||
|
||||
TagParser.prototype.readString = function () {
|
||||
var delim = this.text[this.i++];
|
||||
|
||||
var str = "";
|
||||
while (this.i < this.text.length && this.text[this.i] !== delim) {
|
||||
if (this.text[this.i] === "\\" && this.text[this.i+1] === delim) {
|
||||
str += this.text[this.i+1];
|
||||
this.i++;
|
||||
} else {
|
||||
str += this.text[this.i];
|
||||
}
|
||||
this.i++;
|
||||
}
|
||||
this.i++;
|
||||
return str;
|
||||
};
|
||||
|
||||
TagParser.prototype.parse = function () {
|
||||
this.i = this.text.indexOf("<");
|
||||
if (this.i === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.i++;
|
||||
this.skipWhitespace();
|
||||
|
||||
var tname = this.readLiteral();
|
||||
|
||||
// Attributes
|
||||
var attrs = {};
|
||||
while (this.text[this.i] !== ">") {
|
||||
var key = this.readLiteralOrString(/[^\s=>]/);
|
||||
this.skipWhitespace();
|
||||
if (this.text[this.i] !== "=") {
|
||||
if (key.trim().length > 0) {
|
||||
attrs[key] = "";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
this.i++;
|
||||
this.skipWhitespace();
|
||||
var value = this.readLiteralOrString();
|
||||
if (key.trim().length > 0) {
|
||||
attrs[key] = value;
|
||||
}
|
||||
this.skipWhitespace();
|
||||
}
|
||||
this.i++;
|
||||
|
||||
return {
|
||||
tagName: tname,
|
||||
attributes: attrs,
|
||||
text: this.text.substring(0, this.i)
|
||||
};
|
||||
};
|
||||
|
||||
/* Some of these may not even be HTML tags, I borrowed them from the
|
||||
[now deprecated] XSS module of node-validator
|
||||
*/
|
||||
const badTags = new RegExp([
|
||||
"alert",
|
||||
"applet",
|
||||
"audio",
|
||||
"basefont",
|
||||
"base",
|
||||
"behavior",
|
||||
"bgsound",
|
||||
"blink",
|
||||
"body",
|
||||
"embed",
|
||||
"expression",
|
||||
"form",
|
||||
"frameset",
|
||||
"frame",
|
||||
"head",
|
||||
"html",
|
||||
"ilayer",
|
||||
"iframe",
|
||||
"input",
|
||||
"layer",
|
||||
"link",
|
||||
"meta",
|
||||
"object",
|
||||
"style",
|
||||
"script",
|
||||
"textarea",
|
||||
"title",
|
||||
"video",
|
||||
"xml",
|
||||
"xss"
|
||||
].join("|"), "i");
|
||||
|
||||
const badAttrs = new RegExp([
|
||||
"\\bon\\S*",
|
||||
"\\bformaction"
|
||||
].join("|"), "i");
|
||||
|
||||
const badAttrValues = new RegExp([
|
||||
"alert",
|
||||
"document.cookie",
|
||||
"expression",
|
||||
"javascript",
|
||||
"location",
|
||||
"window"
|
||||
].join("|"), "ig");
|
||||
|
||||
function sanitizeHTML(str) {
|
||||
var i = str.indexOf("<");
|
||||
if (i === -1) {
|
||||
// No HTML tags
|
||||
return str;
|
||||
}
|
||||
|
||||
while (i !== -1) {
|
||||
var t = new TagParser(str.substring(i)).tag;
|
||||
if (t.tagName.replace("/", "").match(badTags)) {
|
||||
str = str.replace(t.text, "[tag removed]");
|
||||
i = str.indexOf("<", i+1);
|
||||
continue;
|
||||
}
|
||||
for (var k in t.attributes) {
|
||||
if (k.match(badAttrs)) {
|
||||
delete t.attributes[k];
|
||||
} else if (t.attributes[k].match(badAttrValues)) {
|
||||
t.attributes[k] = t.attributes[k].replace(badAttrValues, "");
|
||||
}
|
||||
}
|
||||
var fmt = "<" + t.tagName;
|
||||
for (var k in t.attributes) {
|
||||
fmt += " " + k + '="' + t.attributes[k] + '"';
|
||||
}
|
||||
str = str.replace(t.text, fmt + ">");
|
||||
i = str.indexOf("<", i + fmt.length + 1);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
module.exports.sanitizeHTML = sanitizeHTML;
|
|
@ -0,0 +1,16 @@
|
|||
var sanitize = require('../lib/xss').sanitizeHTML;
|
||||
var assert = require('assert');
|
||||
|
||||
function basicTest() {
|
||||
assert(sanitize("< script src = bad.js>blah</script>") ===
|
||||
"[tag removed]blah[tag removed]");
|
||||
|
||||
assert(sanitize("< img src=asdf onerror='alert(\"xss\")'>") ===
|
||||
"<img src=\"asdf\">");
|
||||
|
||||
assert(sanitize("<a href='javascript:alert(document.cookie)'>") ===
|
||||
"<a href=\":()\">");
|
||||
}
|
||||
|
||||
basicTest();
|
||||
console.log("Tests passed.");
|
Loading…
Reference in New Issue