Skip to content

Commit

Permalink
Fix some security issue
Browse files Browse the repository at this point in the history
Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
  • Loading branch information
alexandrebodin committed Jun 11, 2020
1 parent 7120bb2 commit ccb428e
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ module.exports = async () => {
message: `<p>We heard that you lost your password. Sorry about that!</p>
<p>But don鈥檛 worry! You can use the following link to reset your password:</p>
<p><%= URL %>?code=<%= TOKEN %></p>
<p>Thanks.</p>`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = async (ctx, next) => {
{
interval: 1 * 60 * 1000,
max: 5,
prefixKey: `${ctx.request.url}:${ctx.request.ip}`,
prefixKey: `${ctx.request.path}:${ctx.request.ip}`,
message,
},
strapi.plugins['users-permissions'].config.ratelimit
Expand Down
32 changes: 8 additions & 24 deletions packages/strapi-plugin-users-permissions/controllers/Auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,29 +315,21 @@ module.exports = {
key: 'advanced',
});

const userInfo = _.omit(user, ['password', 'resetPasswordToken', 'role', 'provider']);

settings.message = await strapi.plugins['users-permissions'].services.userspermissions.template(
settings.message,
{
URL: advanced.email_reset_password,
USER: _.omit(user.toJSON ? user.toJSON() : user, [
'password',
'resetPasswordToken',
'role',
'provider',
]),
USER: userInfo,
TOKEN: resetPasswordToken,
}
);

settings.object = await strapi.plugins['users-permissions'].services.userspermissions.template(
settings.object,
{
USER: _.omit(user.toJSON ? user.toJSON() : user, [
'password',
'resetPasswordToken',
'role',
'provider',
]),
USER: userInfo,
}
);

Expand Down Expand Up @@ -647,29 +639,21 @@ module.exports = {
}
});

const userInfo = _.omit(user, ['password', 'resetPasswordToken', 'role', 'provider']);

settings.message = await strapi.plugins['users-permissions'].services.userspermissions.template(
settings.message,
{
URL: `${strapi.config.server.url}/auth/email-confirmation`,
USER: _.omit(user.toJSON ? user.toJSON() : user, [
'password',
'resetPasswordToken',
'role',
'provider',
]),
USER: userInfo,
CODE: jwt,
}
);

settings.object = await strapi.plugins['users-permissions'].services.userspermissions.template(
settings.object,
{
USER: _.omit(user.toJSON ? user.toJSON() : user, [
'password',
'resetPasswordToken',
'role',
'provider',
]),
USER: userInfo,
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

const _ = require('lodash');
const { isValidEmailTemplate } = require('./validation/email-template');

module.exports = {
/**
Expand Down Expand Up @@ -196,14 +197,24 @@ module.exports = {
return ctx.badRequest(null, [{ messages: [{ id: 'Cannot be empty' }] }]);
}

const emailTemplates = ctx.request.body['email-templates'];

for (let key in emailTemplates) {
const template = emailTemplates[key].options.message;

if (!isValidEmailTemplate(template)) {
return ctx.badRequest(null, [{ messages: [{ id: 'Invalid template' }] }]);
}
}

await strapi
.store({
environment: '',
type: 'plugin',
name: 'users-permissions',
key: 'email',
})
.set({ value: ctx.request.body['email-templates'] });
.set({ value: emailTemplates });

ctx.send({ ok: true });
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

const { isValidEmailTemplate } = require('../email-template');

describe('isValidEmailTemplate', () => {
test('Accepts one valid pattern', () => {
expect(isValidEmailTemplate('<%= CODE %>')).toBe(true);
expect(isValidEmailTemplate('<%=CODE%>')).toBe(true);
});

test('Refuses invalid patterns', () => {
expect(isValidEmailTemplate('<%- CODE %>')).toBe(false);
expect(isValidEmailTemplate('<% CODE %>')).toBe(false);
expect(isValidEmailTemplate('<%= <% CODE %> %>')).toBe(false);
expect(isValidEmailTemplate('<%- <% CODE %> %>')).toBe(false);
expect(isValidEmailTemplate('${ <% CODE %> }')).toBe(false);
expect(isValidEmailTemplate('<%CODE%>')).toBe(false);
expect(isValidEmailTemplate('${CODE}')).toBe(false);
expect(isValidEmailTemplate('${ CODE }')).toBe(false);
});

test('Fails on non authorized keys', () => {
expect(isValidEmailTemplate('<% random expression %>')).toBe(false);
expect(isValidEmailTemplate('<% random expression }%>')).toBe(false);
expect(isValidEmailTemplate('<% some.var.azdazd %>')).toBe(false);
expect(isValidEmailTemplate('<% function() %>')).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

const _ = require('lodash');

const invalidPatternsRegexes = [/<%[^=]([^<>%]*)%>/m, /\${([^{}]*)}/m];
const authorizedKeys = ['URL', 'CODE', 'USER', 'USER.email', 'USER.username', 'TOKEN'];

const isValidEmailTemplate = template => {
for (let reg of invalidPatternsRegexes) {
if (reg.test(template)) {
return false;
}
}

const matches = Array.from(template.matchAll(/<%=([^<>%=]*)%>/g));
for (let match of matches) {
const [, group] = match;
const trimGroup = _.trim(group);

if (!authorizedKeys.includes(trimGroup)) {
return false;
}
}

return true;
};

module.exports = {
isValidEmailTemplate,
};

0 comments on commit ccb428e

Please sign in to comment.