From 07d4099892266026b6991f2b8a6902978e20448a Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 19 Nov 2019 13:33:52 -0800 Subject: [PATCH] Added bad login IP blocking cooloff support. --- meshcentral.js | 3 +-- meshuser.js | 15 +++++++++++++-- package.json | 2 +- sample-config.json | 2 +- webserver.js | 16 +++++++++++++--- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/meshcentral.js b/meshcentral.js index ae319cf7..f2219459 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -2039,8 +2039,7 @@ function mainStart() { var modules = ['ws', 'cbor', 'nedb', 'https', 'yauzl', 'xmldom', 'ipcheck', 'express', 'archiver', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'cookie-session', 'express-handlebars']; if (require('os').platform() == 'win32') { modules.push('node-windows'); if (sspi == true) { modules.push('node-sspi'); } } // Add Windows modules if (ldap == true) { modules.push('ldapauth-fork'); } - //if (config.letsencrypt != null) { modules.push('greenlock@2.8.8'); modules.push('le-store-certbot'); modules.push('le-challenge-fs'); modules.push('le-acme-core'); } // Add Greenlock Modules - if (config.letsencrypt != null) { if ((nodeVersion < 10) || (require('crypto').generateKeyPair == null)) { console.log("WARNING: Let's Encrypt support requires Node v10.12.0 or higher."); } else { modules.push('greenlock'); } } // Add Greenlock Module + if (config.letsencrypt != null) { if ((nodeVersion < 10) || (require('crypto').generateKeyPair == null)) { if (!args.launch) { console.log("WARNING: Let's Encrypt support requires Node v10.12.0 or higher."); } } else { modules.push('greenlock'); } } // Add Greenlock Module if (config.settings.mqtt != null) { modules.push('aedes'); } // Add MQTT Modules if (config.settings.mongodb != null) { modules.push('mongodb'); } // Add MongoDB, official driver. if (config.settings.vault != null) { modules.push('node-vault'); } // Add official HashiCorp's Vault module. diff --git a/meshuser.js b/meshuser.js index 14d690ad..76dd9992 100644 --- a/meshuser.js +++ b/meshuser.js @@ -693,10 +693,21 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use break; } case 'badlogins': { - r = "Max is " + parent.parent.config.settings.maxinvalidlogin.count + " bad login(s) in " + parent.parent.config.settings.maxinvalidlogin.time + " minute(s).\r\n"; + if (typeof parent.parent.config.settings.maxinvalidlogin.coolofftime == 'number') { + r = "Max is " + parent.parent.config.settings.maxinvalidlogin.count + " bad login(s) in " + parent.parent.config.settings.maxinvalidlogin.time + " minute(s), " + parent.parent.config.settings.maxinvalidlogin.coolofftime + " minute(s) cooloff.\r\n"; + } else { + r = "Max is " + parent.parent.config.settings.maxinvalidlogin.count + " bad login(s) in " + parent.parent.config.settings.maxinvalidlogin.time + " minute(s).\r\n"; + } var badLoginCount = 0; parent.cleanBadLoginTable(); - for (var i in parent.badLoginTable) { badLoginCount++; r += (i + ' - ' + parent.badLoginTable[i].length + " entries\r\n"); } + for (var i in parent.badLoginTable) { + badLoginCount++; + if (typeof parent.badLoginTable[i] == 'number') { + r += "Cooloff for " + Math.floor((parent.badLoginTable[i] - Date.now()) / 60000) + " minute(s)\r\n"; + } else { + r += (i + ' - ' + parent.badLoginTable[i].length + " entries\r\n"); + } + } if (badLoginCount == 0) { r += 'No bad logins.'; } break; } diff --git a/package.json b/package.json index 37cff4fd..56452c52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.4-m", + "version": "0.4.4-o", "keywords": [ "Remote Management", "Intel AMT", diff --git a/sample-config.json b/sample-config.json index 03d9b526..d24d6bf1 100644 --- a/sample-config.json +++ b/sample-config.json @@ -60,7 +60,7 @@ "meshcommander": "https://www.meshcommander.com/" }, "__MaxInvalidLogin": "Time in minutes, max amount of bad logins from a source IP in the time before logins are rejected.", - "MaxInvalidLogin": { "time": 10, "count": 10 } + "MaxInvalidLogin": { "time": 10, "count": 10, "coolofftime": 10 } }, "_domains": { "": { diff --git a/webserver.js b/webserver.js index fad9c6fc..d184dfe1 100644 --- a/webserver.js +++ b/webserver.js @@ -4038,26 +4038,36 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (parent.config.settings.maxinvalidlogin == null) { parent.config.settings.maxinvalidlogin = { time: 10, count: 10 }; } if (typeof parent.config.settings.maxinvalidlogin.time != 'number') { parent.config.settings.maxinvalidlogin.time = 10; } if (typeof parent.config.settings.maxinvalidlogin.count != 'number') { parent.config.settings.maxinvalidlogin.count = 10; } + if ((typeof parent.config.settings.maxinvalidlogin.coolofftime != 'number') || (parent.config.settings.maxinvalidlogin.coolofftime < 1)) { parent.config.settings.maxinvalidlogin.coolofftime = null; } obj.setbadLogin = function (ip) { // Set an IP address that just did a bad login request if (typeof ip == 'object') { ip = cleanRemoteAddr(ip.ip); } if (++obj.badLoginTableLastClean > 100) { obj.cleanBadLoginTable(); } + if (typeof obj.badLoginTable[ip] == 'number') { if (obj.badLoginTable[ip] < Date.now()) { delete obj.badLoginTable[ip]; } else { return; } } // Check cooloff period if (obj.badLoginTable[ip] == null) { obj.badLoginTable[ip] = [Date.now()]; } else { obj.badLoginTable[ip].push(Date.now()); } + if ((obj.badLoginTable[ip].length >= parent.config.settings.maxinvalidlogin.count) && (parent.config.settings.maxinvalidlogin.coolofftime != null)) { + obj.badLoginTable[ip] = Date.now() + (parent.config.settings.maxinvalidlogin.coolofftime * 60000); // Move to cooloff period + } } obj.checkAllowLogin = function (ip) { // Check if an IP address is allowed to login if (typeof ip == 'object') { ip = cleanRemoteAddr(ip.ip); } var cutoffTime = Date.now() - (parent.config.settings.maxinvalidlogin.time * 60000); // Time in minutes var ipTable = obj.badLoginTable[ip]; if (ipTable == null) return true; + if (typeof ipTable == 'number') { if (obj.badLoginTable[ip] < Date.now()) { delete obj.badLoginTable[ip]; } else { return false; } } // Check cooloff period while ((ipTable.length > 0) && (ipTable[0] < cutoffTime)) { ipTable.shift(); } if (ipTable.length == 0) { delete obj.badLoginTable[ip]; return true; } return (ipTable.length < parent.config.settings.maxinvalidlogin.count); // No more than x bad logins in x minutes } obj.cleanBadLoginTable = function () { // Clean up the IP address login blockage table, we do this occasionaly. var cutoffTime = Date.now() - (parent.config.settings.maxinvalidlogin.time * 60000); // Time in minutes - for (var i in ipTable) { + for (var ip in obj.badLoginTable) { var ipTable = obj.badLoginTable[ip]; - while ((ipTable.length > 0) && (ipTable[0] < cutoffTime)) { ipTable.shift(); } - if (ipTable.length == 0) { delete obj.badLoginTable[ip]; } + if (typeof ipTable == 'number') { + if (obj.badLoginTable[ip] < Date.now()) { delete obj.badLoginTable[ip]; } // Check cooloff period + } else { + while ((ipTable.length > 0) && (ipTable[0] < cutoffTime)) { ipTable.shift(); } + if (ipTable.length == 0) { delete obj.badLoginTable[ip]; } + } } obj.badLoginTableLastClean = 0; }