Add XSS filter

This commit is contained in:
calzoneman 2014-01-23 11:45:08 -06:00
parent feca68538e
commit 551d5b2c36
3 changed files with 44 additions and 25 deletions

View File

@ -8,6 +8,7 @@ var AsyncQueue = require("./asyncqueue");
var MakeEmitter = require("./emitter"); var MakeEmitter = require("./emitter");
var InfoGetter = require("./get-info"); var InfoGetter = require("./get-info");
var ChatCommand = require("./chatcommand"); var ChatCommand = require("./chatcommand");
var XSS = require("./xss");
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
@ -572,16 +573,6 @@ Channel.prototype.part = function (user) {
} }
}; };
/**
* Set the MOTD and broadcast it to connected users
*/
Channel.prototype.setMOTD = function (message) {
this.motd.motd = message;
// TODO XSS filter
this.motd.html = message.replace(/\n/g, "<br>");
this.sendMOTD(this.users);
};
/** /**
* Send the MOTD to the given users * Send the MOTD to the given users
*/ */
@ -2194,9 +2185,9 @@ Channel.prototype.validateChatFilter = function (f) {
} }
f.replace = f.replace.substring(0, 1000); f.replace = f.replace.substring(0, 1000);
f.replace = XSS.sanitizeHTML(f.replace);
f.flags = f.flags.substring(0, 4); f.flags = f.flags.substring(0, 4);
// TODO XSS prevention
try { try {
new RegExp(f.source, f.flags); new RegExp(f.source, f.flags);
} catch (e) { } catch (e) {
@ -2493,7 +2484,7 @@ Channel.prototype.handleSetJS = function (user, data) {
* Sets the MOTD * Sets the MOTD
*/ */
Channel.prototype.setMotd = function (motd) { Channel.prototype.setMotd = function (motd) {
// TODO XSS motd = XSS.sanitizeHTML(motd);
var html = motd.replace(/\n/g, "<br>"); var html = motd.replace(/\n/g, "<br>");
this.motd = { this.motd = {
motd: motd, motd: motd,
@ -2557,7 +2548,7 @@ Channel.prototype.handleChat = function (user, data) {
} }
if (smuted) { if (smuted) {
// TODO XSS msg = XSS.sanitizeText(msg);
msg = this.filterMessage(msg); msg = this.filterMessage(msg);
var msgobj = { var msgobj = {
username: user.name, username: user.name,
@ -2629,7 +2620,7 @@ Channel.prototype.filterMessage = function (msg) {
* Sends a chat message * Sends a chat message
*/ */
Channel.prototype.sendMessage = function (user, msg, meta) { Channel.prototype.sendMessage = function (user, msg, meta) {
// TODO HTML escape msg = XSS.sanitizeText(msg);
msg = this.filterMessage(msg); msg = this.filterMessage(msg);
var msgobj = { var msgobj = {
username: user.name, username: user.name,
@ -2645,7 +2636,7 @@ Channel.prototype.sendMessage = function (user, msg, meta) {
} }
this.logger.log("<" + user.name + (meta.addClass ? "." + meta.addClass : "") + "> " + this.logger.log("<" + user.name + (meta.addClass ? "." + meta.addClass : "") + "> " +
msg); XSS.decodeText(msg));
}; };
/** /**

View File

@ -46,6 +46,16 @@ TagParser.prototype.readLiteral = function (regexp) {
str += this.text[this.i]; str += this.text[this.i];
this.i++; this.i++;
} }
str = str.replace(/&#([0-9]{2,7});?/g, function (m, p1) {
return String.fromCharCode(parseInt(p1));
});
str = str.replace(/&#x([0-9a-fA-F]{2,7});?/g, function (m, p1) {
return String.fromCharCode(parseInt(p1, 16));
});
str = str.replace(/[\x00-\x1f]/g, "");
return str; return str;
}; };
@ -53,7 +63,7 @@ TagParser.prototype.readLiteral = function (regexp) {
a string. Otherwise, read a literal a string. Otherwise, read a literal
*/ */
TagParser.prototype.readLiteralOrString = function (regexp) { TagParser.prototype.readLiteralOrString = function (regexp) {
if (this.text[this.i].match(/["']/)) { if (this.text[this.i].match(/["'`]/)) {
return this.readString(); return this.readString();
} }
return this.readLiteral(regexp); return this.readLiteral(regexp);
@ -78,6 +88,16 @@ TagParser.prototype.readString = function () {
this.i++; this.i++;
} }
this.i++; this.i++;
str = str.replace(/&#([0-9]{2,7});?/g, function (m, p1) {
return String.fromCharCode(parseInt(p1));
});
str = str.replace(/&#x([0-9a-fA-F]{2,7});?/g, function (m, p1) {
return String.fromCharCode(parseInt(p1, 16));
});
str = str.replace(/[\x00-\x1f]/g, "");
return str; return str;
}; };
@ -120,7 +140,7 @@ TagParser.prototype.parse = function () {
} }
this.i++; this.i++;
this.skipWhitespace(); //this.skipWhitespace();
var value = this.readLiteralOrString(); var value = this.readLiteralOrString();
if (key.trim().length > 0) { if (key.trim().length > 0) {
attrs[key] = value; attrs[key] = value;
@ -181,7 +201,8 @@ const badTags = new RegExp([
*/ */
const badAttrs = new RegExp([ const badAttrs = new RegExp([
"\\bon\\S*", "\\bon\\S*",
"\\bformaction" "\\bformaction",
"\\baction"
].join("|"), "i"); ].join("|"), "i");
/* These are things commonly used in the values of HTML attributes of /* These are things commonly used in the values of HTML attributes of
@ -226,7 +247,7 @@ function sanitizeHTML(str) {
// If it's an evil attribute, just nuke it entirely // If it's an evil attribute, just nuke it entirely
if (k.match(badAttrs)) { if (k.match(badAttrs)) {
delete t.attributes[k]; delete t.attributes[k];
} else { } else {
if (t.attributes[k].match(badAttrValues)) { if (t.attributes[k].match(badAttrValues)) {
// As above, replacing with a nonempty string is important. // As above, replacing with a nonempty string is important.
t.attributes[k] = t.attributes[k].replace(badAttrValues, "[removed]"); t.attributes[k] = t.attributes[k].replace(badAttrValues, "[removed]");
@ -240,7 +261,14 @@ function sanitizeHTML(str) {
if (k.trim().length > 0) { if (k.trim().length > 0) {
fmt += " " + k; fmt += " " + k;
if (t.attributes[k].trim().length > 0) { if (t.attributes[k].trim().length > 0) {
fmt += '="' + t.attributes[k] + '"'; var delim = '"';
if (t.attributes[k].match(/[^\\]"/)) {
delim = "'";
if (t.attributes[k].match(/[^\\]'/)) {
delim = "`";
}
}
fmt += "=" + delim + t.attributes[k] + delim;
} }
} }
} }
@ -264,10 +292,10 @@ function sanitizeText(str) {
} }
function decodeText(str) { function decodeText(str) {
str = str.replace(/&#([0-9]{2,4});?/g, function (m, p1) { str = str.replace(/&#([0-9]{2,7});?/g, function (m, p1) {
return String.fromCharCode(parseInt(p1)); return String.fromCharCode(parseInt(p1));
}); });
str = str.replace(/&#x([0-9a-f]{2,4});?/ig, function (m, p1) { str = str.replace(/&#x([0-9a-f]{2,7});?/ig, function (m, p1) {
return String.fromCharCode(parseInt(p1, 16)); return String.fromCharCode(parseInt(p1, 16));
}); });
str = str.replace(/&lt;/g, "<") str = str.replace(/&lt;/g, "<")