security: do not password reset mail to cloudron owned mail domain

https://forum.cloudron.io/topic/7951/privilege-escalation-through-mail-manager-role
This commit is contained in:
Girish Ramakrishnan
2022-11-10 12:57:32 +01:00
parent d49c171c79
commit 3477cf474f
2 changed files with 23 additions and 18 deletions

View File

@@ -80,6 +80,7 @@ const appPasswords = require('./apppasswords.js'),
eventlog = require('./eventlog.js'),
externalLdap = require('./externalldap.js'),
hat = require('./hat.js'),
mail = require('./mail.js'),
mailer = require('./mailer.js'),
mysql = require('mysql'),
qrcode = require('qrcode'),
@@ -630,24 +631,6 @@ async function getSuperadmins() {
return await getByRole(exports.ROLE_OWNER);
}
async function sendPasswordResetByIdentifier(identifier, auditSource) {
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof auditSource, 'object');
const user = identifier.indexOf('@') === -1 ? await getByUsername(identifier.toLowerCase()) : await getByEmail(identifier.toLowerCase());
if (!user) throw new BoxError(BoxError.NOT_FOUND, 'User not found');
const resetToken = hat(256);
const resetTokenCreationTime = new Date();
user.resetToken = resetToken;
user.resetTokenCreationTime = resetTokenCreationTime;
await update(user, { resetToken,resetTokenCreationTime }, auditSource);
const resetLink = `${settings.dashboardOrigin()}/login.html?resetToken=${user.resetToken}`;
await mailer.passwordReset(user, user.fallbackEmail || user.email, resetLink);
}
async function getPasswordResetLink(user, auditSource) {
assert.strictEqual(typeof user, 'object');
assert.strictEqual(typeof auditSource, 'object');
@@ -667,6 +650,23 @@ async function getPasswordResetLink(user, auditSource) {
return resetLink;
}
async function sendPasswordResetByIdentifier(identifier, auditSource) {
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof auditSource, 'object');
const user = identifier.indexOf('@') === -1 ? await getByUsername(identifier.toLowerCase()) : await getByEmail(identifier.toLowerCase());
if (!user) throw new BoxError(BoxError.NOT_FOUND, 'User not found');
const email = user.fallbackEmail || user.email;
// security measure to prevent a mail manager or admin resetting the superadmin's password
const mailDomains = await mail.listDomains();
if (mailDomains.some(d => d.enabled && email.endsWith(`@${d.domain}`))) throw new BoxError(BoxError.CONFLICT, 'Password reset email cannot be sent to email addresses hosted on Cloudron');
const resetLink = await getPasswordResetLink(user, auditSource);
await mailer.passwordReset(user, email, resetLink);
}
async function sendPasswordResetEmail(user, email, auditSource) {
assert.strictEqual(typeof user, 'object');
assert.strictEqual(typeof email, 'string');
@@ -675,6 +675,10 @@ async function sendPasswordResetEmail(user, email, auditSource) {
const error = validateEmail(email);
if (error) throw error;
// security measure to prevent a mail manager or admin resetting the superadmin's password
const mailDomains = await mail.listDomains();
if (mailDomains.some(d => d.enabled && email.endsWith(`@${d.domain}`))) throw new BoxError(BoxError.CONFLICT, 'Password reset email cannot be sent to email addresses hosted on Cloudron');
const resetLink = await getPasswordResetLink(user, auditSource);
await mailer.passwordReset(user, email, resetLink);
}