forked from svrjs/svrjs
Enable use of PBKDF2-hashed passwords
This commit is contained in:
parent
f8cc7e45cd
commit
d93511e97b
3 changed files with 124 additions and 46 deletions
|
@ -3,7 +3,7 @@
|
||||||
"port": 80,
|
"port": 80,
|
||||||
"pubport": 80,
|
"pubport": 80,
|
||||||
"page404": "404.html",
|
"page404": "404.html",
|
||||||
"timestamp": 1692101055417,
|
"timestamp": 1692113030263,
|
||||||
"blacklist": [],
|
"blacklist": [],
|
||||||
"nonStandardCodes": [],
|
"nonStandardCodes": [],
|
||||||
"enableCompression": true,
|
"enableCompression": true,
|
||||||
|
|
92
svr.js
92
svr.js
|
@ -312,6 +312,10 @@ function generateETag(filePath, stat) {
|
||||||
// Brute force-related
|
// Brute force-related
|
||||||
var bruteForceDb = {};
|
var bruteForceDb = {};
|
||||||
|
|
||||||
|
// PBKDF2 cache
|
||||||
|
var pbkdf2Cache = [];
|
||||||
|
var pbkdf2CacheIntervalId = -1;
|
||||||
|
|
||||||
// SVR.JS worker spawn-related
|
// SVR.JS worker spawn-related
|
||||||
var SVRJSInitialized = false;
|
var SVRJSInitialized = false;
|
||||||
var exiting = false;
|
var exiting = false;
|
||||||
|
@ -4453,7 +4457,32 @@ if (!cluster.isPrimary) {
|
||||||
checkIfPasswordMatches(list, password, callback, _i+1);
|
checkIfPasswordMatches(list, password, callback, _i+1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb(sha256(password + list[_i].salt));
|
var hashedPassword = sha256(password + list[_i].salt);
|
||||||
|
if(list[_i].pbkdf2) {
|
||||||
|
if(crypto.__disabled__ !== undefined) {
|
||||||
|
callServerError(500, undefined, new Error("SVR.JS doesn't support PBKDF2-hashed passwords on Node.JS versions without crypto support."));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
var cacheEntry = pbkdf2Cache.find(function(entry) {
|
||||||
|
return (entry.password == hashedPassword && entry.salt == list[_i].salt)
|
||||||
|
});
|
||||||
|
if(cacheEntry) {
|
||||||
|
cb(cacheEntry.hash);
|
||||||
|
} else {
|
||||||
|
crypto.pbkdf2(password, list[_i].salt, 36250, 64, "sha512", function(err, derivedKey) {
|
||||||
|
if(err) {
|
||||||
|
callServerError(500, undefined, err);
|
||||||
|
} else {
|
||||||
|
var key = derivedKey.toString("hex");
|
||||||
|
pbkdf2Cache.push({hash: key, password: hashedPassword, salt: list[_i].salt, addDate: new Date()});
|
||||||
|
cb(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cb(hashedPassword);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function authorizedCallback(bruteProtection) {
|
function authorizedCallback(bruteProtection) {
|
||||||
|
@ -4487,34 +4516,34 @@ if (!cluster.isPrimary) {
|
||||||
usernameMatch.push({name: username, pass: "FAKEPASS", salt: "FAKESALT"}); //Fake credentials
|
usernameMatch.push({name: username, pass: "FAKEPASS", salt: "FAKESALT"}); //Fake credentials
|
||||||
}
|
}
|
||||||
checkIfPasswordMatches(usernameMatch, password, function(authorized) {
|
checkIfPasswordMatches(usernameMatch, password, function(authorized) {
|
||||||
if (!authorized) {
|
if (!authorized) {
|
||||||
if (bruteProtection) {
|
if (bruteProtection) {
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
process.send("\x12AUTHW" + reqip);
|
process.send("\x12AUTHW" + reqip);
|
||||||
} else {
|
} else {
|
||||||
if (!bruteForceDb[reqip]) bruteForceDb[reqip] = {
|
if (!bruteForceDb[reqip]) bruteForceDb[reqip] = {
|
||||||
invalidAttempts: 0
|
invalidAttempts: 0
|
||||||
};
|
};
|
||||||
bruteForceDb[reqip].invalidAttempts++;
|
bruteForceDb[reqip].invalidAttempts++;
|
||||||
if (bruteForceDb[reqip].invalidAttempts >= 10) {
|
if (bruteForceDb[reqip].invalidAttempts >= 10) {
|
||||||
bruteForceDb[reqip].lastAttemptDate = new Date();
|
bruteForceDb[reqip].lastAttemptDate = new Date();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
callServerError(401, undefined, undefined, ha);
|
||||||
callServerError(401, undefined, undefined, ha);
|
serverconsole.errmessage("User " + username + " failed to log in.");
|
||||||
serverconsole.errmessage("User " + username + " failed to log in.");
|
} else {
|
||||||
} else {
|
if (bruteProtection) {
|
||||||
if (bruteProtection) {
|
if (process.send) {
|
||||||
if (process.send) {
|
process.send("\x12AUTHR" + reqip);
|
||||||
process.send("\x12AUTHR" + reqip);
|
} else {
|
||||||
} else {
|
if (bruteForceDb[reqip]) bruteForceDb[reqip] = {
|
||||||
if (bruteForceDb[reqip]) bruteForceDb[reqip] = {
|
invalidAttempts: 0
|
||||||
invalidAttempts: 0
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
modExecute(mods, vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, fd, callServerError, getCustomHeaders, origHref, redirect, parsePostData));
|
||||||
}
|
}
|
||||||
modExecute(mods, vres(req, res, serverconsole, responseEnd, href, ext, uobject, search, "index.html", users, page404, head, foot, fd, callServerError, getCustomHeaders, origHref, redirect, parsePostData));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (authcode.disableBruteProtection) {
|
if (authcode.disableBruteProtection) {
|
||||||
|
@ -4802,7 +4831,10 @@ function start(init) {
|
||||||
if (version.indexOf("Nightly-") === 0) serverconsole.locwarnmessage("This version is only for test purposes and may be unstable.");
|
if (version.indexOf("Nightly-") === 0) serverconsole.locwarnmessage("This version is only for test purposes and may be unstable.");
|
||||||
if (http2.__disabled__ !== undefined) serverconsole.locwarnmessage("HTTP/2 isn't supported by your Node.JS version! You may not be able to use HTTP/2 with SVR.JS");
|
if (http2.__disabled__ !== undefined) serverconsole.locwarnmessage("HTTP/2 isn't supported by your Node.JS version! You may not be able to use HTTP/2 with SVR.JS");
|
||||||
if (configJSON.enableHTTP2 && !secure) serverconsole.locwarnmessage("HTTP/2 without HTTPS may not work in web browsers. Web browsers only support HTTP/2 with HTTPS!");
|
if (configJSON.enableHTTP2 && !secure) serverconsole.locwarnmessage("HTTP/2 without HTTPS may not work in web browsers. Web browsers only support HTTP/2 with HTTPS!");
|
||||||
if (process.isBun) serverconsole.locwarnmessage("Bun support is experimental. Some features of SVR.JS, SVR.JS mods and SVR.JS server-side JavaScript may not work as expected.");
|
if (process.isBun) {
|
||||||
|
serverconsole.locwarnmessage("Bun support is experimental. Some features of SVR.JS, SVR.JS mods and SVR.JS server-side JavaScript may not work as expected.");
|
||||||
|
if(users.some(function(entry) {return entry.pbkdf2;})) serverconsole.locwarnmessage("PBKDF2 password hashing function in Bun blocks the event loop, which may result in denial of service.");
|
||||||
|
}
|
||||||
if (cluster.isPrimary === undefined) serverconsole.locwarnmessage("You're running SVR.JS on single thread. Reliability may suffer, as the server is stopped after crash.");
|
if (cluster.isPrimary === undefined) serverconsole.locwarnmessage("You're running SVR.JS on single thread. Reliability may suffer, as the server is stopped after crash.");
|
||||||
if (crypto.__disabled__ !== undefined) serverconsole.locwarnmessage("Your Node.JS version doesn't have crypto support! The 'crypto' module is essential for providing cryptographic functionality in Node.js. Without crypto support, certain security features may be unavailable, and some functionality may not work as expected. It's recommended to use a Node.JS version that includes crypto support to ensure the security and proper functioning of your server.");
|
if (crypto.__disabled__ !== undefined) serverconsole.locwarnmessage("Your Node.JS version doesn't have crypto support! The 'crypto' module is essential for providing cryptographic functionality in Node.js. Without crypto support, certain security features may be unavailable, and some functionality may not work as expected. It's recommended to use a Node.JS version that includes crypto support to ensure the security and proper functioning of your server.");
|
||||||
if (process.getuid && process.getuid() == 0) serverconsole.locwarnmessage("You're running SVR.JS as root. It's recommended to run SVR.JS as an non-root user. Running SVR.JS as root may increase the risks of OS command execution vulnerabilities.");
|
if (process.getuid && process.getuid() == 0) serverconsole.locwarnmessage("You're running SVR.JS as root. It's recommended to run SVR.JS as an non-root user. Running SVR.JS as root may increase the risks of OS command execution vulnerabilities.");
|
||||||
|
@ -4898,6 +4930,7 @@ function start(init) {
|
||||||
},
|
},
|
||||||
stop: function (retcode) {
|
stop: function (retcode) {
|
||||||
reallyExiting = true;
|
reallyExiting = true;
|
||||||
|
clearInterval(pbkdf2CacheIntervalId);
|
||||||
if((!cluster.isPrimary && cluster.isPrimary !== undefined) && server.listening) {
|
if((!cluster.isPrimary && cluster.isPrimary !== undefined) && server.listening) {
|
||||||
try {
|
try {
|
||||||
server.close(function() {
|
server.close(function() {
|
||||||
|
@ -5019,6 +5052,13 @@ function start(init) {
|
||||||
}
|
}
|
||||||
}, 300000);
|
}, 300000);
|
||||||
}
|
}
|
||||||
|
if (!cluster.isPrimary) {
|
||||||
|
pbkdf2CacheIntervalId = setInterval(function () {
|
||||||
|
pbkdf2Cache = pbkdf2Cache.every(function(entry) {
|
||||||
|
return entry.addDate > (new Date() - 3600000);
|
||||||
|
});
|
||||||
|
}, 3600000);
|
||||||
|
}
|
||||||
if (!cluster.isPrimary && cluster.isPrimary !== undefined) {
|
if (!cluster.isPrimary && cluster.isPrimary !== undefined) {
|
||||||
process.on("message", function (line) {
|
process.on("message", function (line) {
|
||||||
try {
|
try {
|
||||||
|
|
74
svrpasswd.js
74
svrpasswd.js
|
@ -196,42 +196,63 @@ function saveConfig() {
|
||||||
var args = process.argv;
|
var args = process.argv;
|
||||||
var user = "";
|
var user = "";
|
||||||
var action = "change";
|
var action = "change";
|
||||||
|
var hash = "auto";
|
||||||
if (process.argv.length <= (process.argv[0].indexOf("node") > -1 || process.argv[0].indexOf("bun") > -1 ? 2 : 1)) args.push("-h");
|
if (process.argv.length <= (process.argv[0].indexOf("node") > -1 || process.argv[0].indexOf("bun") > -1 ? 2 : 1)) args.push("-h");
|
||||||
for (var i = (process.argv[0].indexOf("node") > -1 || process.argv[0].indexOf("bun") > -1 ? 2 : 1); i < args.length; i++) {
|
for (var i = (process.argv[0].indexOf("node") > -1 || process.argv[0].indexOf("bun") > -1 ? 2 : 1); i < args.length; i++) {
|
||||||
if (args[i] == "-h" || args[i] == "--help" || args[i] == "-?" || args[i] == "/h" || args[i] == "/?") {
|
if (args[i] == "-h" || args[i] == "--help" || args[i] == "-?" || args[i] == "/h" || args[i] == "/?") {
|
||||||
console.log("SVR.JS user tool usage:");
|
console.log("SVR.JS user tool usage:");
|
||||||
console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-a|--add|-d|--delete] <username>");
|
console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [--sha256|--pbkdf2] [-a|--add|-d|--delete] <username>");
|
||||||
console.log("-h -? /h /? --help -- Displays help");
|
console.log("-h -? /h /? --help -- Displays help");
|
||||||
console.log("-a --add -- Add an user");
|
console.log("-a --add -- Add an user");
|
||||||
console.log("-d --delete -- Deletes an user");
|
console.log("-d --delete -- Deletes an user");
|
||||||
|
console.log("--sha256 -- Uses salted SHA256");
|
||||||
|
console.log("--pbkdf2 -- Uses PBKDF2");
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} else if (args[i] == "-a" || args[i] == "--add") {
|
} else if (args[i] == "-a" || args[i] == "--add") {
|
||||||
if (action != "change") {
|
if (action != "change") {
|
||||||
console.log("Multiple actions specified.");
|
console.log("Multiple actions specified.");
|
||||||
console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-a|--add|-d|--delete] <username>");
|
console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [--sha256|--pbkdf2] [-a|--add|-d|--delete] <username>");
|
||||||
console.log("-h -? /h /? --help -- Displays help");
|
console.log("-h -? /h /? --help -- Displays help");
|
||||||
console.log("-a --add -- Add an user");
|
console.log("-a --add -- Add an user");
|
||||||
console.log("-d --delete -- Deletes an user");
|
console.log("-d --delete -- Deletes an user");
|
||||||
|
console.log("--sha256 -- Uses salted SHA256");
|
||||||
|
console.log("--pbkdf2 -- Uses PBKDF2");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
action = "add";
|
action = "add";
|
||||||
} else if (args[i] == "-d" || args[i] == "--delete") {
|
} else if (args[i] == "-d" || args[i] == "--delete") {
|
||||||
if (action != "change") {
|
if (action != "change") {
|
||||||
console.log("Multiple actions specified.");
|
console.log("Multiple actions specified.");
|
||||||
console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-a|--add|-d|--delete] <username>");
|
console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [--sha256|--pbkdf2] [-a|--add|-d|--delete] <username>");
|
||||||
console.log("-h -? /h /? --help -- Displays help");
|
console.log("-h -? /h /? --help -- Displays help");
|
||||||
console.log("-a --add -- Add an user");
|
console.log("-a --add -- Add an user");
|
||||||
console.log("-d --delete -- Deletes an user");
|
console.log("-d --delete -- Deletes an user");
|
||||||
|
console.log("--sha256 -- Uses salted SHA256");
|
||||||
|
console.log("--pbkdf2 -- Uses PBKDF2");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
action = "delete";
|
action = "delete";
|
||||||
} else {
|
} else if (args[i] == "--sha256" || args[i] == "--pbkdf2") {
|
||||||
if (user != "") {
|
if (hash != "auto") {
|
||||||
console.log("Multiple users specified.");
|
console.log("Multiple hash types specified.");
|
||||||
console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-a|--add|-d|--delete] <username>");
|
console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [--sha256|--pbkdf2] [-a|--add|-d|--delete] <username>");
|
||||||
console.log("-h -? /h /? --help -- Displays help");
|
console.log("-h -? /h /? --help -- Displays help");
|
||||||
console.log("-a --add -- Add an user");
|
console.log("-a --add -- Add an user");
|
||||||
console.log("-d --delete -- Deletes an user");
|
console.log("-d --delete -- Deletes an user");
|
||||||
|
console.log("--sha256 -- Uses salted SHA256");
|
||||||
|
console.log("--pbkdf2 -- Uses PBKDF2");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
hash = (args[i] == "--sha256" ? "sha256" : "pbkdf2");
|
||||||
|
} else {
|
||||||
|
if (user != "") {
|
||||||
|
console.log("Multiple users specified.");
|
||||||
|
onsole.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [--sha256|--pbkdf2] [-a|--add|-d|--delete] <username>");
|
||||||
|
console.log("-h -? /h /? --help -- Displays help");
|
||||||
|
console.log("-a --add -- Add an user");
|
||||||
|
console.log("-d --delete -- Deletes an user");
|
||||||
|
console.log("--sha256 -- Uses salted SHA256");
|
||||||
|
console.log("--pbkdf2 -- Uses PBKDF2");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
user = args[i];
|
user = args[i];
|
||||||
|
@ -240,13 +261,25 @@ for (var i = (process.argv[0].indexOf("node") > -1 || process.argv[0].indexOf("b
|
||||||
|
|
||||||
if (user == "") {
|
if (user == "") {
|
||||||
console.log("No user specified.");
|
console.log("No user specified.");
|
||||||
console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-a|--add|-d|--delete] <username>");
|
console.log("node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [--sha256|--pbkdf2] [-a|--add|-d|--delete] <username>");
|
||||||
console.log("-h -? /h /? --help -- Displays help");
|
console.log("-h -? /h /? --help -- Displays help");
|
||||||
console.log("-a --add -- Add an user");
|
console.log("-a --add -- Add an user");
|
||||||
console.log("-d --delete -- Deletes an user");
|
console.log("-d --delete -- Deletes an user");
|
||||||
|
console.log("--sha256 -- Uses salted SHA256");
|
||||||
|
console.log("--pbkdf2 -- Uses PBKDF2");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hash == "auto") {
|
||||||
|
if(crypto.__disabled__ !== "undefined") {
|
||||||
|
hash = "sha256";
|
||||||
|
} else if(process.isBun) {
|
||||||
|
hash = "sha256"; //Prevent PBKDF2 function event loop block in Bun
|
||||||
|
} else {
|
||||||
|
hash = "pbkdf2"; //Secure by default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getUserIndex(username) {
|
function getUserIndex(username) {
|
||||||
var ind = -1
|
var ind = -1
|
||||||
for (var i = 0; i < users.length; i++) {
|
for (var i = 0; i < users.length; i++) {
|
||||||
|
@ -277,20 +310,23 @@ function password(callback) {
|
||||||
var rl = readline.createInterface({
|
var rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout,
|
output: process.stdout,
|
||||||
prompt: 'Password: ',
|
prompt: 'Password: '
|
||||||
terminal: false
|
|
||||||
});
|
});
|
||||||
rl.prompt();
|
rl.prompt();
|
||||||
|
process.stdout.writeold = process.stdout.write;
|
||||||
|
process.stdout.write = function(s){process.stdout.writeold(s.replace(/[^\r\n]/g,""));};
|
||||||
rl.once('line', (line) => {
|
rl.once('line', (line) => {
|
||||||
//rl.close();
|
process.stdout.write = process.stdout.writeold;
|
||||||
var rl = readline.createInterface({
|
var rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout,
|
output: process.stdout,
|
||||||
prompt: 'Confirm password: ',
|
prompt: 'Confirm password: ',
|
||||||
terminal: false
|
|
||||||
});
|
});
|
||||||
rl.prompt();
|
rl.prompt();
|
||||||
|
process.stdout.writeold = process.stdout.write;
|
||||||
|
process.stdout.write = function(s){process.stdout.writeold(s.replace(/[^\r\n]/g,""));};
|
||||||
rl.on('line', (line2) => {
|
rl.on('line', (line2) => {
|
||||||
|
process.stdout.write = process.stdout.writeold;
|
||||||
rl.close();
|
rl.close();
|
||||||
if (line != line2) callback(false);
|
if (line != line2) callback(false);
|
||||||
else callback(line);
|
else callback(line);
|
||||||
|
@ -317,11 +353,12 @@ if (action == "delete") {
|
||||||
console.log("Passwords don't match!");
|
console.log("Passwords don't match!");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
var salt = generateSalt()
|
var salt = generateSalt();
|
||||||
users.push({
|
users.push({
|
||||||
name: user,
|
name: user,
|
||||||
pass: sha256(password + salt),
|
pass: (hash == "sha256" ? sha256(password + salt) : crypto.pbkdf2Sync(password, salt, 36250, 64, "sha512")),
|
||||||
salt: salt
|
salt: salt,
|
||||||
|
pbkdf2: (hash == "sha256" ? undefined : true)
|
||||||
});
|
});
|
||||||
saveConfig();
|
saveConfig();
|
||||||
console.log("User added successfully");
|
console.log("User added successfully");
|
||||||
|
@ -333,11 +370,12 @@ if (action == "delete") {
|
||||||
console.log("Passwords don't match!");
|
console.log("Passwords don't match!");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
var salt = generateSalt()
|
var salt = generateSalt();
|
||||||
users[userindex] = {
|
users[userindex] = {
|
||||||
name: user,
|
name: user,
|
||||||
pass: sha256(password + salt),
|
pass: (hash == "sha256" ? sha256(password + salt) : crypto.pbkdf2Sync(password, salt, 36250, 64, "sha512")),
|
||||||
salt: salt
|
salt: salt,
|
||||||
|
pbkdf2: (hash == "sha256" ? undefined : true)
|
||||||
};
|
};
|
||||||
saveConfig();
|
saveConfig();
|
||||||
console.log("Password changed successfully");
|
console.log("Password changed successfully");
|
||||||
|
|
Reference in a new issue