From 20ca0b5e1b5eadeb3a545ef1c4c45dc36949beed Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sat, 30 Nov 2013 10:50:02 -0600 Subject: [PATCH 1/3] Add some soft limits to user.js --- lib/user.js | 48 +++++++++++++----------------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/lib/user.js b/lib/user.js index 0fe5872d..800dd699 100644 --- a/lib/user.js +++ b/lib/user.js @@ -34,8 +34,6 @@ var User = function (socket) { afk: false, icon: false }; - this.throttle = {}; - this.flooded = {}; this.queueLimiter = $util.newRateLimiter(); this.chatLimiter = $util.newRateLimiter(); this.profile = { @@ -59,38 +57,6 @@ User.prototype.inPendingChannel = function () { return this.pendingChannel != null && !this.pendingChannel.dead; }; -// Throttling/cooldown -User.prototype.noflood = function (name, hz) { - var time = new Date().getTime(); - if (!(name in this.throttle)) { - this.throttle[name] = [time]; - return false; - } else if (name in this.flooded && time < this.flooded[name]) { - this.socket.emit("noflood", { - action: name, - msg: "You're still on cooldown!" - }); - return true; - } else { - this.throttle[name].push(time); - var diff = (time - this.throttle[name][0]) / 1000.0; - // Twice might be an accident, more than that is probably spam - if (this.throttle[name].length > 2) { - var rate = this.throttle[name].length / diff; - this.throttle[name] = [time]; - if (rate > hz) { - this.flooded[name] = time + 5000; - this.socket.emit("noflood", { - action: name, - msg: "Stop doing that so fast! Cooldown: 5s" - }); - return true; - } - return false; - } - } -}; - User.prototype.setAFK = function (afk) { if (!this.inChannel()) return; @@ -149,8 +115,9 @@ User.prototype.initCallbacks = function () { self.socket.on("joinChannel", function (data) { data = (typeof data !== "object") ? {} : data; - if (self.inChannel() || self.inPendingChannel()) + if (self.inChannel() || self.inPendingChannel()) { return; + } if (typeof data.name != "string") { return; } @@ -166,6 +133,7 @@ User.prototype.initCallbacks = function () { self.pendingChannel = self.server.getChannel(data.name); if (self.loggedIn) { // TODO fix + // I'm not sure what I meant by "fix", but I suppose I'll find out soon self.pendingChannel.getRank(self.name, function (err, rank) { if (!err && rank > self.rank) self.rank = rank; @@ -187,6 +155,8 @@ User.prototype.initCallbacks = function () { var session = (typeof data.session === "string") ? data.session : ""; if (pw.length > 100) pw = pw.substring(0, 100); + if (session.length > 64) + session = session.substring(0, 64); if (self.loggedIn) return; @@ -342,6 +312,10 @@ User.prototype.initCallbacks = function () { if (typeof data.query !== "string") { return; } + // Soft limit to prevent someone from making a massive query + if (data.query.length > 255) { + return; + } if (data.source === "yt") { var searchfn = InfoGetter.Getters.ytSearch; searchfn(data.query.split(" "), function (e, vids) { @@ -526,6 +500,10 @@ User.prototype.initCallbacks = function () { if (typeof data.name !== "string") { return; } + // Soft limit to prevent someone from saving a list with a massive name + if (data.name.length > 200) { + data.name = data.name.substring(0, 200); + } if (self.rank < 1) { self.socket.emit("savePlaylist", { success: false, From 6cf2c40240c0345b8742283f5177e21645c48ad2 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 1 Dec 2013 17:10:37 -0600 Subject: [PATCH 2/3] add extra user input validation and truncation --- lib/api.js | 125 +++++++++++++++++++++++++++++++++++++++++++++++-- lib/channel.js | 56 ++++++++++------------ lib/user.js | 2 +- 3 files changed, 148 insertions(+), 35 deletions(-) diff --git a/lib/api.js b/lib/api.js index cf7fe407..3a20b7b2 100644 --- a/lib/api.js +++ b/lib/api.js @@ -183,9 +183,9 @@ module.exports = function (Server) { app.post("/api/login", function (req, res) { res.type("application/jsonp"); res.setHeader("Access-Control-Allow-Origin", "*"); - var name = req.body.name; - var pw = req.body.pw; - var session = req.body.session; + var name = req.body.name || ""; + var pw = req.body.pw || ""; + var session = req.body.session || ""; // for some reason CyTube previously allowed guest logins // over the API...wat @@ -226,6 +226,15 @@ module.exports = function (Server) { res.setHeader("Access-Control-Allow-Origin", "*"); var name = req.body.name; var pw = req.body.pw; + if (typeof name !== "string" || + typeof pw !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } var ip = getIP(req); // Limit registrations per IP within a certain time period @@ -300,6 +309,17 @@ module.exports = function (Server) { var oldpw = req.body.oldpw; var newpw = req.body.newpw; + if (typeof name !== "string" || + typeof oldpw !== "string" || + typeof newpw !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } + if(!oldpw || !newpw) { res.jsonp({ success: false, @@ -340,6 +360,15 @@ module.exports = function (Server) { res.setHeader("Access-Control-Allow-Origin", "*"); var name = req.body.name; var email = req.body.email; + if (typeof name !== "string" || + typeof email !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } var ip = getIP(req); var hash = false; @@ -407,6 +436,14 @@ module.exports = function (Server) { app.get("/api/account/passwordrecover", function (req, res) { res.type("application/jsonp"); var hash = req.query.hash; + if (typeof hash !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } var ip = getIP(req); db.recoverUserPassword(hash, function (err, auth) { @@ -457,6 +494,25 @@ module.exports = function (Server) { var session = req.body.session; var img = req.body.profile_image; var text = req.body.profile_text; + if (typeof name !== "string" || + typeof session !== "string" || + typeof img !== "string" || + typeof text !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } + + if (img.length > 255) { + img = img.substring(0, 255); + } + + if (text.length > 255) { + text = text.substring(0, 255); + } db.userLoginSession(name, session, function (err, row) { if(err) { @@ -507,6 +563,18 @@ module.exports = function (Server) { var pw = req.body.pw; var email = req.body.email; + if (typeof name !== "string" || + typeof pw !== "string" || + typeof email !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } + + if(!email.match(/^[\w_\.]+@[\w_\.]+[a-z]+$/i)) { res.jsonp({ success: false, @@ -555,6 +623,16 @@ module.exports = function (Server) { var name = req.query.name; var session = req.query.session; + if (typeof name !== "string" || + typeof session !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } + db.userLoginSession(name, session, function (err, row) { if(err) { res.jsonp({ @@ -593,6 +671,17 @@ module.exports = function (Server) { var session = req.query.session; var types = req.query.actions; + if (typeof name !== "string" || + typeof session !== "string" || + typeof types !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } + db.userLoginSession(name, session, function (err, row) { if(err) { if(err !== "Invalid session" && @@ -644,6 +733,16 @@ module.exports = function (Server) { var name = req.query.name; var session = req.query.session; + if (typeof name !== "string" || + typeof session !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } + db.userLoginSession(name, session, function (err, row) { if(err) { if(err !== "Invalid session" && @@ -671,6 +770,16 @@ module.exports = function (Server) { var name = req.query.name; var session = req.query.session; + if (typeof name !== "string" || + typeof session !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } + db.userLoginSession(name, session, function (err, row) { if(err) { if(err !== "Invalid session" && @@ -698,6 +807,16 @@ module.exports = function (Server) { var name = req.query.name; var session = req.query.session; + if (typeof name !== "string" || + typeof session !== "string") { + res.status(400); + res.jsonp({ + success: false, + error: "Invalid request" + }); + return; + } + db.userLoginSession(name, session, function (err, row) { if(err) { if(err !== "Invalid session" && diff --git a/lib/channel.js b/lib/channel.js index 1c7001ba..a03e07d0 100644 --- a/lib/channel.js +++ b/lib/channel.js @@ -826,6 +826,9 @@ Channel.prototype.handlePendingJoins = function () { }; Channel.prototype.userJoin = function(user, password) { + if (password.length > 100) { + password = password.substring(0, 100); + } var self = this; if (!self.ready) { self.addPendingJoin(user, password); @@ -1869,12 +1872,13 @@ Channel.prototype.tryUpdate = function(user, data) { return; } - if(this.playlist.current.media.id != data.id) { + if (this.playlist.current.media.id !== data.id || + isNaN(+data.currentTime)) { return; } - this.playlist.current.media.currentTime = data.currentTime; - this.playlist.current.media.paused = data.paused; + this.playlist.current.media.currentTime = +data.currentTime; + this.playlist.current.media.paused = Boolean(data.paused); this.sendAll("mediaUpdate", this.playlist.current.media.timeupdate()); } @@ -2013,10 +2017,7 @@ Channel.prototype.trySetLock = function(user, data) { return; } - if(data.locked == undefined) { - return; - } - + data.locked = Boolean(data.locked); this.logger.log("*** " + user.name + " set playlist lock to " + data.locked); this.setLock(data.locked); } @@ -2036,6 +2037,11 @@ Channel.prototype.tryRemoveFilter = function(user, f) { return; } + // Don't care about the other parameters since we're just removing + if (typeof f.name !== "string") { + return; + } + this.logger.log("%%% " + user.name + " removed filter: " + f.name); this.removeFilter(f); } @@ -2079,6 +2085,14 @@ Channel.prototype.tryUpdateFilter = function(user, f) { return; } + if (f.replace.length > 1000) { + f.replace = f.replace.substring(0, 1000); + } + + if (f.flags.length > 4) { + f.flags = f.flags.substring(0, 4); + } + var re = f.source; var flags = f.flags; // Temporary fix @@ -2304,7 +2318,10 @@ Channel.prototype.tryUpdateMotd = function(user, data) { return; } - data.motd = data.motd || ""; + if (data.motd.length > 20000) { + data.motd = data.motd.substring(0, 20000); + } + this.updateMotd(data.motd); this.logger.log("%%% " + user.name + " set the MOTD"); } @@ -2432,29 +2449,6 @@ Channel.prototype.sendMessage = function (user, msg, meta, filter) { } }; -Channel.prototype.sendMessageOld = function(username, msg, msgclass, data) { - // I don't want HTML from strangers - msg = sanitize(msg).escape(); - msg = this.filterMessage(msg); - var msgobj = { - username: username, - msg: msg, - msgclass: msgclass, - time: Date.now() - }; - if(data) { - for(var key in data) { - msgobj[key] = data[key]; - } - } - this.sendAll("chatMsg", msgobj); - this.chatbuffer.push(msgobj); - if(this.chatbuffer.length > 15) - this.chatbuffer.shift(); - var unescaped = sanitize(msg).entityDecode(); - this.logger.log("<" + username + "." + msgclass + "> " + unescaped); -}; - /* REGION Rank stuff */ Channel.prototype.trySetRank = function(user, data) { diff --git a/lib/user.js b/lib/user.js index 800dd699..696e9391 100644 --- a/lib/user.js +++ b/lib/user.js @@ -314,7 +314,7 @@ User.prototype.initCallbacks = function () { } // Soft limit to prevent someone from making a massive query if (data.query.length > 255) { - return; + data.query = data.query.substring(0, 255); } if (data.source === "yt") { var searchfn = InfoGetter.Getters.ytSearch; From 3ced278bd086e68c101bc7d5c294706bafbed4a7 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Sun, 1 Dec 2013 17:20:42 -0600 Subject: [PATCH 3/3] Support links in polls --- lib/channel.js | 6 +++++- lib/poll.js | 10 +++++++++- www/assets/js/callbacks.js | 6 +++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/channel.js b/lib/channel.js index a03e07d0..d1fada54 100644 --- a/lib/channel.js +++ b/lib/channel.js @@ -826,7 +826,7 @@ Channel.prototype.handlePendingJoins = function () { }; Channel.prototype.userJoin = function(user, password) { - if (password.length > 100) { + if (typeof password === "string" && password.length > 100) { password = password.substring(0, 100); } var self = this; @@ -1932,6 +1932,10 @@ Channel.prototype.tryOpenPoll = function(user, data) { return; } + for (var i = 0; i < data.opts.length; i++) { + data.opts[i] = ""+data.opts[i]; + } + var obscured = (data.obscured === true); var poll = new Poll(user.name, data.title, data.opts, obscured); this.poll = poll; diff --git a/lib/poll.js b/lib/poll.js index 396e6ae0..af9c6536 100644 --- a/lib/poll.js +++ b/lib/poll.js @@ -8,11 +8,19 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +const link = /(\w+:\/\/(?:[^:\/\[\]\s]+|\[[0-9a-f:]+\])(?::\d+)?(?:\/[^\/\s]*)*)/ig; +var XSS = require("./xss"); var Poll = function(initiator, title, options, obscured) { this.initiator = initiator; - this.title = title; + title = XSS.sanitizeText(title); + this.title = title.replace(link, "$1"); this.options = options; + for (var i = 0; i < this.options.length; i++) { + this.options[i] = XSS.sanitizeText(this.options[i]); + this.options[i] = this.options[i].replace(link, "$1"); + + } this.obscured = obscured || false; this.counts = new Array(options.length); for(var i = 0; i < this.counts.length; i++) { diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index 08421e02..4bd7f856 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -1100,7 +1100,7 @@ Callbacks = { newPoll: function(data) { Callbacks.closePoll(); var pollMsg = $("
").addClass("poll-notify") - .text(data.initiator + " opened a poll: \"" + data.title + "\"") + .html(data.initiator + " opened a poll: \"" + data.title + "\"") .appendTo($("#messagebuffer")); scrollChat(); @@ -1116,7 +1116,7 @@ Callbacks = { }); } - $("

").text(data.title).appendTo(poll); + $("

").html(data.title).appendTo(poll); for(var i = 0; i < data.options.length; i++) { (function(i) { var callback = function () { @@ -1129,7 +1129,7 @@ Callbacks = { $(this).parent().addClass("option-selected"); } $("