diff --git a/src/categories/create.js b/src/categories/create.js index c4aa403425..d8f3e4a65c 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -149,7 +149,7 @@ module.exports = function (Categories) { Categories.assignColours = function () { const backgrounds = ['#AB4642', '#DC9656', '#F7CA88', '#A1B56C', '#86C1B9', '#7CAFC2', '#BA8BAF', '#A16946']; const text = ['#ffffff', '#ffffff', '#333333', '#ffffff', '#333333', '#ffffff', '#ffffff', '#ffffff']; - const index = Math.floor(Math.random() * backgrounds.length); + const index = utils.secureRandom(0, backgrounds.length - 1); return [backgrounds[index], text[index]]; }; diff --git a/src/controllers/404.js b/src/controllers/404.js index 33f04f142e..becc206e76 100644 --- a/src/controllers/404.js +++ b/src/controllers/404.js @@ -8,6 +8,7 @@ const meta = require('../meta'); const plugins = require('../plugins'); const middleware = require('../middleware'); const helpers = require('../middleware/helpers'); +const { secureRandom } = require('../utils'); exports.handle404 = helpers.try(async (req, res) => { const relativePath = nconf.get('relative_path'); @@ -64,6 +65,6 @@ exports.send404 = helpers.try(async (req, res) => { path: validator.escape(path), title: '[[global:404.title]]', bodyClass: helpers.buildBodyClass(req, res), - icon: icons[Math.floor(Math.random() * icons.length)], + icon: icons[secureRandom(0, icons.length - 1)], }); }); diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index 3f110b79f9..0c84a0f210 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -3,6 +3,7 @@ module.exports = function (module) { const _ = require('lodash'); const helpers = require('./helpers'); + const { secureRandom } = require('../../utils'); module.setAdd = async function (key, value) { if (!Array.isArray(value)) { @@ -200,7 +201,7 @@ module.exports = function (module) { return; } - const randomIndex = Math.floor(Math.random() * data.members.length); + const randomIndex = secureRandom(0, data.members.length - 1); const value = data.members[randomIndex]; await module.setRemove(data._key, value); return value; diff --git a/src/meta/cacheBuster.js b/src/meta/cacheBuster.js index 58129ff2f8..d1e4d2d239 100644 --- a/src/meta/cacheBuster.js +++ b/src/meta/cacheBuster.js @@ -11,7 +11,11 @@ let cached; // cache buster is an 11-character, lowercase, alphanumeric string function generate() { - return (Math.random() * 1e18).toString(32).slice(0, 11); + const crypto = require('crypto'); + const length = 11; + const generated = crypto.randomBytes(Math.ceil(length / 2)) + .toString('hex').slice(0, length); + return generated; } exports.write = async function write() { diff --git a/src/password.js b/src/password.js index 7c0d01931c..6f9617f6ab 100644 --- a/src/password.js +++ b/src/password.js @@ -29,7 +29,9 @@ async function getFakeHash() { if (fakeHashCache) { return fakeHashCache; } - fakeHashCache = await exports.hash(12, Math.random().toString()); + const length = 18; + fakeHashCache = crypto.randomBytes(Math.ceil(length / 2)) + .toString('hex').slice(0, length); return fakeHashCache; } diff --git a/src/socket.io/user.js b/src/socket.io/user.js index c20c545f8a..48b491ab43 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -46,10 +46,10 @@ SocketUser.reset.send = async function (socket, email) { try { await user.reset.send(email); await logEvent('[[success:success]]'); - await sleep(2500 + ((Math.random() * 500) - 250)); + await sleep(2500 + (utils.secureRandom(0, 500) - 250)); } catch (err) { await logEvent(err.message); - await sleep(2500 + ((Math.random() * 500) - 250)); + await sleep(2500 + (utils.secureRandom(0, 500) - 250)); const internalErrors = ['[[error:invalid-email]]']; if (!internalErrors.includes(err.message)) { throw err; diff --git a/src/utils.js b/src/utils.js index fb59865f69..6600b855ce 100644 --- a/src/utils.js +++ b/src/utils.js @@ -31,6 +31,16 @@ utils.generateUUID = function () { return rnd.join('-'); }; +utils.secureRandom = function (low, high) { + if (low > high) { + throw new Error("The 'low' parameter must be less than or equal to the 'high' parameter."); + } + const randomBuffer = crypto.randomBytes(4); + const randomInt = randomBuffer.readUInt32BE(0); + const range = high - low + 1; + return low + (randomInt % range); +}; + utils.getSass = function () { try { const sass = require('sass-embedded'); diff --git a/test/utils.js b/test/utils.js index dbf397e995..370c05d298 100644 --- a/test/utils.js +++ b/test/utils.js @@ -102,7 +102,7 @@ describe('Utility Methods', () => { }); }); - describe('UUID generation', () => { + describe('UUID generation / secureRandom', () => { it('return unique random value every time', () => { delete require.cache[require.resolve('../src/utils')]; const { generateUUID } = require('../src/utils'); @@ -110,6 +110,19 @@ describe('Utility Methods', () => { const uuid2 = generateUUID(); assert.notEqual(uuid1, uuid2, 'matches'); }); + + it('should return a random number between 1-10 inclusive', () => { + const { secureRandom } = require('../src/utils'); + const r1 = secureRandom(1, 10); + assert(r1 >= 1); + assert(r1 <= 10); + }); + + it('should always return 3', () => { + const { secureRandom } = require('../src/utils') + const r1 = secureRandom(3, 3); + assert.strictEqual(r1, 3); + }); }); describe('cleanUpTag', () => {