diff --git a/changelog b/changelog index 60b25546..906af988 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,8 @@ +Sat Oct 12 18:58 2013 CDT + * lib/user.js, lib/channel.js: Improve strictness of data checking + to prevent errors from incoming bad data. Kick users who send + bad data or attempt channel moderation with insufficient rank. + Sat Oct 12 18:24 2013 CDT * lib/user.js: Fix bad chatMsg packet causing exceptions diff --git a/lib/channel.js b/lib/channel.js index af45ecd2..c8f3b436 100644 --- a/lib/channel.js +++ b/lib/channel.js @@ -362,8 +362,10 @@ Channel.prototype.readLog = function (filterIp, callback) { } Channel.prototype.tryReadLog = function (user) { - if(user.rank < 3) + if(user.rank < 3) { + user.kick("Attempted readChanLog with insufficient permission"); return; + } var filterIp = true; if(user.global_rank >= 255) @@ -623,6 +625,7 @@ Channel.prototype.tryNameBan = function(actor, name) { Channel.prototype.unbanName = function(actor, name) { var self = this; if(!self.hasPermission(actor, "ban")) { + actor.kick("Attempted unban with insufficient permission"); return false; } @@ -730,8 +733,10 @@ Channel.prototype.tryIPBan = function(actor, name, range) { Channel.prototype.unbanIP = function(actor, ip) { var self = this; - if(!self.hasPermission(actor, "ban")) + if(!self.hasPermission(actor, "ban")) { + actor.kick("Attempted unban with insufficient permission"); return false; + } self.ipbans[ip] = null; self.users.forEach(function(u) { @@ -748,11 +753,11 @@ Channel.prototype.unbanIP = function(actor, ip) { } Channel.prototype.tryUnban = function(actor, data) { - if(data.ip_hidden) { + if(typeof data.ip_hidden === "string") { var ip = this.hideIP(data.ip_hidden); this.unbanIP(actor, ip); } - if(data.name) { + if(typeof data.name === "string") { this.unbanName(actor, data.name); } } @@ -1318,9 +1323,11 @@ Channel.prototype.tryQueue = function(user, data) { return; } if(typeof data.pos !== "string") { + user.kick("Bad queue packet"); return; } if(typeof data.id !== "string" && data.id !== false) { + user.kick("Bad queue packet"); return; } @@ -1580,6 +1587,7 @@ Channel.prototype.tryQueuePlaylist = function(user, data) { if(typeof data.name !== "string" || typeof data.pos !== "string") { + user.kick("Bad queuePlaylist packet"); return; } @@ -1639,6 +1647,7 @@ Channel.prototype.trySetTemp = function(user, data) { return; } if(typeof data.uid != "number" || typeof data.temp != "boolean") { + user.kick("Bad setTemp packet"); return; } @@ -1667,8 +1676,10 @@ Channel.prototype.tryDequeue = function(user, data) { if(!this.hasPermission(user, "playlistdelete")) return; - if(typeof data !== "number") + if(typeof data !== "number") { + user.kick("Bad delete packet"); return; + } var plitem = this.playlist.items.find(data); if(plitem && plitem.media) @@ -1682,6 +1693,7 @@ Channel.prototype.tryUncache = function(user, data) { return; } if(typeof data.id != "string") { + user.kick("Bad uncache packet"); return; } if (!self.registered) @@ -1723,6 +1735,7 @@ Channel.prototype.tryJumpTo = function(user, data) { } if(typeof data !== "number") { + user.kick("Bad jumpTo packet"); return; } @@ -1776,11 +1789,14 @@ Channel.prototype.tryShufflequeue = function(user) { Channel.prototype.tryUpdate = function(user, data) { if(this.leader != user) { + user.kick("Received mediaUpdate from non-leader"); return; } - if(typeof data.id !== "string" || typeof data.currentTime !== "number") - return; + if(typeof data.id !== "string" || typeof data.currentTime !== "number") { + user.kick("Bad mediaUpdate packet"); + return; + } if(this.playlist.current === null) { return; @@ -1832,8 +1848,10 @@ Channel.prototype.tryMove = function(user, data) { return; } - if(typeof data.from !== "number" || (typeof data.after !== "number" && typeof data.after !== "string")) + if(typeof data.from !== "number" || (typeof data.after !== "number" && typeof data.after !== "string")) { + user.kick("Bad moveMedia packet"); return; + } this.move(data, user); } @@ -1845,7 +1863,8 @@ Channel.prototype.tryOpenPoll = function(user, data) { return; } - if(!data.title || !data.opts) { + if(typeof data.title !== "string" || !(data.opts instanceof Array)) { + user.kick("Invalid newPoll packet"); return; } @@ -1878,6 +1897,7 @@ Channel.prototype.tryVote = function(user, data) { return; } if(typeof data.option !== "number") { + user.kick("Bad vote packet"); return; } @@ -1952,8 +1972,10 @@ Channel.prototype.tryToggleLock = function(user) { } Channel.prototype.tryRemoveFilter = function(user, f) { - if(!this.hasPermission(user, "filteredit")) - return false; + if(!this.hasPermission(user, "filteredit")) { + user.kick("Attempted removeFilter with insufficient permission"); + return; + } this.logger.log("%%% " + user.name + " removed filter: " + f.name); this.removeFilter(f); @@ -1989,6 +2011,13 @@ Channel.prototype.updateFilter = function(filter, emit) { Channel.prototype.tryUpdateFilter = function(user, f) { if(!this.hasPermission(user, "filteredit")) { + user.kick("Attempted updateFilter with insufficient permission"); + return; + } + + if (typeof f.source !== "string" || typeof f.flags !== "string" || + typeof f.replace !== "string") { + user.kick("Bad updateFilter packet"); return; } @@ -2006,8 +2035,8 @@ Channel.prototype.tryUpdateFilter = function(user, f) { return; } var filter = new Filter(f.name, f.source, f.flags, f.replace); - filter.active = f.active; - filter.filterlinks = f.filterlinks; + filter.active = !!f.active; + filter.filterlinks = !!f.filterlinks; this.logger.log("%%% " + user.name + " updated filter: " + f.name); this.updateFilter(filter); } @@ -2026,16 +2055,21 @@ Channel.prototype.moveFilter = function(data) { } Channel.prototype.tryMoveFilter = function(user, data) { - if(!this.hasPermission(user, "filteredit")) + if(!this.hasPermission(user, "filteredit")) { + user.kick("Attempted moveFilter with insufficient permission"); return; + } - if(typeof data.to !== "number" || typeof data.from !== "number") + if(typeof data.to !== "number" || typeof data.from !== "number") { + user.kick("Bad moveFilter packet"); return; + } this.moveFilter(data); } Channel.prototype.tryUpdatePermissions = function(user, perms) { if(user.rank < 3) { + user.kick("Attempted setPermissions with insufficient permission"); return; } for(var key in perms) { @@ -2047,6 +2081,7 @@ Channel.prototype.tryUpdatePermissions = function(user, perms) { Channel.prototype.tryUpdateOptions = function(user, data) { if(user.rank < 2) { + user.kick("Attempted setOptions with insufficient permission"); return; } @@ -2083,9 +2118,14 @@ Channel.prototype.tryUpdateOptions = function(user, data) { Channel.prototype.trySetCSS = function(user, data) { if(user.rank < 3) { + user.kick("Attempted setChannelCSS with insufficient permission"); return; } + if (typeof data.css !== "string") { + user.kick("Bad setChannelCSS packet"); + return; + } var css = data.css || ""; if(css.length > 20000) { css = css.substring(0, 20000); @@ -2100,6 +2140,11 @@ Channel.prototype.trySetCSS = function(user, data) { Channel.prototype.trySetJS = function(user, data) { if(user.rank < 3) { + user.kick("Attempted setChannelJS with insufficient permission"); + return; + } + if (typeof data.js !== "string") { + user.kick("Bad setChannelJS packet"); return; } @@ -2131,6 +2176,12 @@ Channel.prototype.updateMotd = function(motd) { Channel.prototype.tryUpdateMotd = function(user, data) { if(!this.hasPermission(user, "motdedit")) { + user.kick("Attempted setMotd with insufficient permission"); + return; + } + + if (typeof data.motd !== "string") { + user.kick("Bad setMotd packet"); return; } @@ -2241,11 +2292,15 @@ Channel.prototype.sendMessage = function(username, msg, msgclass, data) { Channel.prototype.trySetRank = function(user, data) { var self = this; - if(user.rank < 2) + if(user.rank < 2) { + user.kick("Attempted setChannelRank with insufficient permission"); return; + } - if(typeof data.user !== "string" || typeof data.rank !== "number") + if(typeof data.user !== "string" || typeof data.rank !== "number") { + user.kick("Bad setChannelRank packet"); return; + } if(data.rank >= user.rank) return; @@ -2337,10 +2392,12 @@ Channel.prototype.changeLeader = function(name) { Channel.prototype.tryChangeLeader = function(user, data) { if(user.rank < 2) { + user.kick("Attempted assignLeader with insufficient permission"); return; } - if(data.name == undefined) { + if(typeof data.name !== "string") { + user.kick("Bad assignLeader packet"); return; } diff --git a/lib/user.js b/lib/user.js index cff835f7..679fac51 100644 --- a/lib/user.js +++ b/lib/user.js @@ -119,6 +119,11 @@ User.prototype.autoAFK = function () { }, self.channel.opts.afk_timeout * 1000); }; +User.prototype.kick = function (reason) { + this.socket.emit("kick", { reason: reason }); + this.socket.disconnect(true); +}; + User.prototype.initCallbacks = function () { var self = this; self.socket.on("disconnect", function () { @@ -128,19 +133,19 @@ User.prototype.initCallbacks = function () { }); self.socket.on("joinChannel", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) return; - if (typeof data.name != "string") + if (typeof data.name != "string") { + self.kick("Bad joinChannel packet"); return; + } if (!data.name.match(/^[\w-_]{1,30}$/)) { self.socket.emit("errorMsg", { msg: "Invalid channel name. Channel names may consist of"+ " 1-30 characters in the set a-z, A-Z, 0-9, -, and _" }); - self.socket.emit("kick", { - reason: "Bad channel name" - }); - + self.kick("Invalid channel name"); return; } data.name = data.name.toLowerCase(); @@ -156,9 +161,10 @@ User.prototype.initCallbacks = function () { }); self.socket.on("login", function (data) { - var name = data.name || ""; - var pw = data.pw || ""; - var session = data.session || ""; + data = (typeof data !== "object") ? {} : data; + var name = (typeof data.name === "string") ? data.name : ""; + var pw = (typeof data.pw === "string") ? data.pw : ""; + var session = (typeof data.session === "string") ? data.session : ""; if (pw.length > 100) pw = pw.substring(0, 100); @@ -186,52 +192,32 @@ User.prototype.initCallbacks = function () { }); self.socket.on("assignLeader", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryChangeLeader(self, data); } }); - self.socket.on("promote", function (data) { - if (self.inChannel()) { - self.channel.tryPromoteUser(self, data); - } - }); - - self.socket.on("demote", function (data) { - if (self.inChannel()) { - self.channel.tryDemoteUser(self, data); - } - }); - self.socket.on("setChannelRank", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.trySetRank(self, data); } }); - self.socket.on("banName", function (data) { - if (self.inChannel()) { - self.channel.banName(self, data.name || ""); - } - }); - - self.socket.on("banIP", function (data) { - if (self.inChannel()) { - self.channel.tryIPBan(self, data); - } - }); - self.socket.on("unban", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryUnban(self, data); } }); self.socket.on("chatMsg", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { if (typeof data.msg !== "string") { self.socket.emit("kick", { - reason: "Invalid chatMsg packet!" + reason: "Invalid chatMsg packet" }); self.socket.disconnect(true); return; @@ -245,6 +231,7 @@ User.prototype.initCallbacks = function () { }); self.socket.on("newPoll", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryOpenPoll(self, data); } @@ -263,36 +250,42 @@ User.prototype.initCallbacks = function () { }); self.socket.on("queue", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryQueue(self, data); } }); self.socket.on("setTemp", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.trySetTemp(self, data); } }); self.socket.on("delete", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryDequeue(self, data); } }); self.socket.on("uncache", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryUncache(self, data); } }); self.socket.on("moveMedia", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryMove(self, data); } }); self.socket.on("jumpTo", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryJumpTo(self, data); } @@ -323,14 +316,20 @@ User.prototype.initCallbacks = function () { }); self.socket.on("mediaUpdate", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryUpdate(self, data); } }); self.socket.on("searchMedia", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { - if (data.source == "yt") { + if (typeof data.query !== "string") { + self.kick("Bad searchMedia packet"); + return; + } + if (data.source === "yt") { var searchfn = InfoGetter.Getters.ytSearch; searchfn(data.query.split(" "), function (e, vids) { if (!e) { @@ -358,6 +357,7 @@ User.prototype.initCallbacks = function () { }); self.socket.on("vote", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryVote(self, data); } @@ -378,52 +378,64 @@ User.prototype.initCallbacks = function () { if (!self.inChannel()) { return; } + if (self.rank < 10) { + self.kick("Attempted unregisterChannel with insufficient permission"); + return; + } self.channel.unregister(self); }); self.socket.on("setOptions", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryUpdateOptions(self, data); } }); self.socket.on("setPermissions", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryUpdatePermissions(self, data); } }); self.socket.on("setChannelCSS", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.trySetCSS(self, data); } }); self.socket.on("setChannelJS", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.trySetJS(self, data); } }); self.socket.on("updateFilter", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryUpdateFilter(self, data); } }); self.socket.on("removeFilter", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryRemoveFilter(self, data); } }); self.socket.on("moveFilter", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryMoveFilter(self, data); } }); self.socket.on("setMotd", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryUpdateMotd(self, data); } @@ -456,12 +468,14 @@ User.prototype.initCallbacks = function () { }); self.socket.on("voteskip", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryVoteskip(self); } }); self.socket.on("listPlaylists", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.name === "" || self.rank < 1) { self.socket.emit("listPlaylists", { pllist: [], @@ -483,6 +497,11 @@ User.prototype.initCallbacks = function () { }); self.socket.on("savePlaylist", function (data) { + data = (typeof data !== "object") ? {} : data; + if (typeof data.name !== "string") { + self.kick("Bad savePlaylist packet"); + return; + } if (self.rank < 1) { self.socket.emit("savePlaylist", { success: false, @@ -533,13 +552,16 @@ User.prototype.initCallbacks = function () { }); self.socket.on("queuePlaylist", function (data) { + data = (typeof data !== "object") ? {} : data; if (self.inChannel()) { self.channel.tryQueuePlaylist(self, data); } }); self.socket.on("deletePlaylist", function (data) { + data = (typeof data !== "object") ? {} : data; if (typeof data.name != "string") { + self.kick("Bad deletePlaylist packet"); return; }