mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-12-25 06:35:54 -05:00
Added bad login IP address limitation.
This commit is contained in:
parent
dfbd933dc7
commit
c9dc923db1
16
db.js
16
db.js
@ -677,7 +677,13 @@ module.exports.CreateDB = function (parent, func) {
|
||||
// TODO: Starting in MongoDB 4.0.3, you should use countDocuments() instead of count() that is deprecated. We should detect MongoDB version and switch.
|
||||
// https://docs.mongodb.com/manual/reference/method/db.collection.countDocuments/
|
||||
//obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.countDocuments({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } }
|
||||
obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); } }
|
||||
obj.isMaxType = function (max, type, domainid, func) {
|
||||
if (obj.eventsfile.countDocuments) {
|
||||
if (max == null) { func(false); } else { obj.file.countDocuments({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); }
|
||||
} else {
|
||||
if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); }
|
||||
}
|
||||
}
|
||||
|
||||
// Database actions on the events collection
|
||||
obj.GetAllEvents = function (func) { obj.eventsfile.find({}).toArray(func); };
|
||||
@ -693,7 +699,13 @@ module.exports.CreateDB = function (parent, func) {
|
||||
obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
|
||||
obj.RemoveAllEvents = function (domain) { obj.eventsfile.deleteMany({ domain: domain }, { multi: true }); };
|
||||
obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.deleteMany({ domain: domain, nodeid: nodeid }, { multi: true }); };
|
||||
obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) { obj.eventsfile.count({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null)?count:0); }); }
|
||||
obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) {
|
||||
if (obj.eventsfile.countDocuments) {
|
||||
obj.eventsfile.countDocuments({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); });
|
||||
} else {
|
||||
obj.eventsfile.count({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); });
|
||||
}
|
||||
}
|
||||
|
||||
// Database actions on the power collection
|
||||
obj.getAllPower = function (func) { obj.powerfile.find({}).toArray(func); };
|
||||
|
@ -223,7 +223,6 @@ module.exports.CreateLetsEncrypt = function (parent) {
|
||||
var somethingIsinFolder = false;
|
||||
try {
|
||||
var filesinFolder = require('fs').readdirSync(obj.runAsProduction ? obj.configPath : obj.configPathStaging);
|
||||
console.log('filesinFolder', filesinFolder);
|
||||
somethingIsinFolder = (filesinFolder.indexOf(obj.runAsProduction ? 'live' : 'staging') != -1);
|
||||
} catch (ex) { console.log(ex); }
|
||||
|
||||
|
14
meshuser.js
14
meshuser.js
@ -689,14 +689,20 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||
case 'help': {
|
||||
r = 'Available commands: help, info, versions, args, resetserver, showconfig, usersessions, tasklimiter, setmaxtasks, cores,\r\n'
|
||||
r += 'migrationagents, agentstats, webstats, mpsstats, swarmstats, acceleratorsstats, updatecheck, serverupdate, nodeconfig,\r\n';
|
||||
r += 'heapdump, relays, autobackup, backupconfig, dupagents, dispatchtable.';
|
||||
r += 'heapdump, relays, autobackup, backupconfig, dupagents, dispatchtable, badlogins.';
|
||||
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";
|
||||
var badLoginCount = 0;
|
||||
parent.cleanBadLoginTable();
|
||||
for (var i in parent.badLoginTable) { badLoginCount++; r += (i + ' - ' + parent.badLoginTable[i].length + " entries\r\n"); }
|
||||
if (badLoginCount == 0) { r += 'No bad logins.'; }
|
||||
break;
|
||||
}
|
||||
case 'dispatchtable': {
|
||||
r = '';
|
||||
for (var i in parent.parent.eventsDispatch) {
|
||||
r += (i + ', ' + parent.parent.eventsDispatch[i].length + '\r\n');
|
||||
}
|
||||
for (var i in parent.parent.eventsDispatch) { r += (i + ', ' + parent.parent.eventsDispatch[i].length + '\r\n'); }
|
||||
break;
|
||||
}
|
||||
case 'dupagents': {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "meshcentral",
|
||||
"version": "0.4.4-l",
|
||||
"version": "0.4.4-m",
|
||||
"keywords": [
|
||||
"Remote Management",
|
||||
"Intel AMT",
|
||||
|
@ -58,7 +58,9 @@
|
||||
},
|
||||
"_Redirects": {
|
||||
"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 }
|
||||
},
|
||||
"_domains": {
|
||||
"": {
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -270,7 +270,7 @@
|
||||
// Display the right server message
|
||||
var messageid = parseInt('{{{messageid}}}');
|
||||
var okmessages = ['', "Hold on, reset mail sent."];
|
||||
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested."];
|
||||
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later."];
|
||||
if (messageid > 0) {
|
||||
var msg = '';
|
||||
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
|
||||
|
@ -267,7 +267,7 @@
|
||||
// Display the right server message
|
||||
var messageid = parseInt('{{{messageid}}}');
|
||||
var okmessages = ['', "Hold on, reset mail sent."];
|
||||
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested."];
|
||||
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later."];
|
||||
if (messageid > 0) {
|
||||
var msg = '';
|
||||
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -268,7 +268,7 @@
|
||||
// Display the right server message
|
||||
var messageid = parseInt('{{{messageid}}}');
|
||||
var okmessages = ['', "Hold on, reset mail sent."];
|
||||
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested."];
|
||||
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later."];
|
||||
if (messageid > 0) {
|
||||
var msg = '';
|
||||
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
|
||||
|
@ -265,7 +265,7 @@
|
||||
// Display the right server message
|
||||
var messageid = parseInt('{{{messageid}}}');
|
||||
var okmessages = ['', "Hold on, reset mail sent."];
|
||||
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested."];
|
||||
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later."];
|
||||
if (messageid > 0) {
|
||||
var msg = '';
|
||||
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
|
||||
|
48
webserver.js
48
webserver.js
@ -639,6 +639,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
const domain = checkUserIpAddress(req, res);
|
||||
if (domain == null) { parent.debug('web', 'handleLoginRequest: invalid domain'); res.sendStatus(404); return; }
|
||||
|
||||
// Check if this is a banned ip address
|
||||
if (obj.checkAllowLogin(req) == false) {
|
||||
// Wait and redirect the user
|
||||
setTimeout(function () {
|
||||
req.session.messageid = 114; // IP address blocked, try again later.
|
||||
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
|
||||
}, 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095));
|
||||
return;
|
||||
}
|
||||
|
||||
// Normally, use the body username/password. If this is a token, use the username/password in the session.
|
||||
var xusername = req.body.username, xpassword = req.body.password;
|
||||
if ((xusername == null) && (xpassword == null) && (req.body.token != null)) { xusername = req.session.tokenusername; xpassword = req.session.tokenpassword; }
|
||||
@ -660,6 +670,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
req.session.messageid = 108; // Invalid token, try again.
|
||||
parent.debug('web', 'handleLoginRequest: invalid 2FA token');
|
||||
obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + user.name], obj, { action: 'authfail', username: user.name, userid: 'user/' + domain.id + '/' + user.name, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + cleanRemoteAddr(req.ip) });
|
||||
obj.setbadLogin(req);
|
||||
} else {
|
||||
parent.debug('web', 'handleLoginRequest: 2FA token required');
|
||||
}
|
||||
@ -693,10 +704,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
parent.debug('web', 'handleLoginRequest: login failed, locked account');
|
||||
req.session.messageid = 110; // Account locked.
|
||||
obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'User login attempt on locked account from ' + cleanRemoteAddr(req.ip) });
|
||||
obj.setbadLogin(req);
|
||||
} else {
|
||||
parent.debug('web', 'handleLoginRequest: login failed, bad username and password');
|
||||
req.session.messageid = 112; // Login failed, check username and password.
|
||||
obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'Invalid user login attempt from ' + cleanRemoteAddr(req.ip) });
|
||||
obj.setbadLogin(req);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1015,6 +1028,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
if ((req.body.token != null) || (req.body.hwtoken != null)) {
|
||||
req.session.messageid = 108; // Invalid token, try again.
|
||||
obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + user.name], obj, { action: 'authfail', username: user.name, userid: 'user/' + domain.id + '/' + user.name, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + cleanRemoteAddr(req.ip) });
|
||||
obj.setbadLogin(req);
|
||||
}
|
||||
req.session.loginmode = '5';
|
||||
req.session.tokenemail = email;
|
||||
@ -3434,6 +3448,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
|
||||
// Authenticates a session and forwards
|
||||
function PerformWSSessionAuth(ws, req, noAuthOk, func) {
|
||||
// Check if this is a banned ip address
|
||||
if (obj.checkAllowLogin(req) == false) { try { ws.send(JSON.stringify({ action: 'close', cause: 'banned', msg: 'banned-1' })); ws.close(); } catch (e) { } return; }
|
||||
try {
|
||||
// Hold this websocket until we are ready.
|
||||
ws._socket.pause();
|
||||
@ -3476,6 +3492,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
// If not authenticated, close the websocket connection
|
||||
parent.debug('web', 'ERR: Websocket bad user/pass auth');
|
||||
//obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + obj.args.user.toLowerCase()], obj, { action: 'authfail', userid: 'user/' + domain.id + '/' + obj.args.user.toLowerCase(), username: obj.args.user, domain: domain.id, msg: 'Invalid user login attempt from ' + cleanRemoteAddr(req.ip) });
|
||||
//obj.setbadLogin(req);
|
||||
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'noauth-2' })); ws.close(); } catch (e) { }
|
||||
}
|
||||
}
|
||||
@ -4014,5 +4031,36 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
} catch (ex) { console.log(ex); func(fd, tag); }
|
||||
}
|
||||
|
||||
// This is the invalid login throttling code
|
||||
obj.badLoginTable = {};
|
||||
obj.badLoginTableLastClean = 0;
|
||||
if (parent.config.settings == null) { parent.config.settings = {}; }
|
||||
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; }
|
||||
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 (obj.badLoginTable[ip] == null) { obj.badLoginTable[ip] = [Date.now()]; } else { obj.badLoginTable[ip].push(Date.now()); }
|
||||
}
|
||||
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;
|
||||
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) {
|
||||
var ipTable = obj.badLoginTable[ip];
|
||||
while ((ipTable.length > 0) && (ipTable[0] < cutoffTime)) { ipTable.shift(); }
|
||||
if (ipTable.length == 0) { delete obj.badLoginTable[ip]; }
|
||||
}
|
||||
obj.badLoginTableLastClean = 0;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
Loading…
Reference in New Issue
Block a user