Skip to content

Commit f94bbcc

Browse files
committed
Merge branch 'release/3.7.8'
2 parents e2c5fc5 + fafc8f4 commit f94bbcc

File tree

9 files changed

+62
-16
lines changed

9 files changed

+62
-16
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# v3.7.8
2+
## 04/16/2024
3+
4+
1. [](#improved)
5+
* Use `random_bytes()` for password reset and activation, only fallback to `mt_rand()` if there's a generation error
6+
* Added a new `site_host` field in the "Security" section to use in password reset and activation links sent in email. This allows you to avoid any "Password Reset Poisoning" attacks.
7+
* Added a new warning in reset and activation emails that shows the "site host" clearly in order to avoid any nefariously sent emails.
8+
19
# v3.7.7
210
## 01/05/2024
311

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ dynamic_page_visibility: false # Integrate access into page visibil
174174
parent_acl: false # Look to parent `access` rules for access requirements
175175
protect_protected_page_media: false # Take `access` rules into account when directly accessing a page's media
176176

177+
site_host: # Optionally used in password reset and activation emails, to avoid "password poisoning attacks", this should be the URL of your site including the protocol. e.g. https://foo.com
178+
177179
rememberme:
178180
enabled: true # Enable 'remember me' functionality
179181
timeout: 604800 # Timeout in seconds. Defaults to 1 week
@@ -427,6 +429,10 @@ user_registration:
427429
send_welcome_email: false # Send a welcome email to the user (probably should not be used with `send_activation_email`
428430
```
429431
432+
## Email Security Considerations
433+
434+
For increased security and to deter users from being tricked into resetting their passwords or activating their accounts on 'fake' sites utilizing a [Password Poisoning Attack](https://portswigger.net/web-security/host-header/exploiting/password-reset-poisoning), you can now set the `site_host` property in the "Security" tab of the login properties, (e.g. `https://foo.com`) to ensure the users are sent to the original site only.
435+
430436
## Sending an activation email
431437

432438
By default the registration process adds a new user, and sets it as enabled.

blueprints.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: Login
22
slug: login
33
type: plugin
4-
version: 3.7.7
4+
version: 3.7.8
55
testing: false
66
description: Enables user authentication and login screen.
77
icon: sign-in
@@ -411,6 +411,12 @@ form:
411411
title: PLUGIN_LOGIN.SECURITY_TAB
412412

413413
fields:
414+
site_host:
415+
type: text
416+
size: medium
417+
label: PLUGIN_LOGIN.SITE_HOST
418+
help: PLUGIN_LOGIN.SITE_HOST_HELP
419+
placeholder: "https://example.com"
414420
max_pw_resets_count:
415421
type: number
416422
size: x-small

classes/Controller.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,14 @@ protected function taskForgot()
389389
return true;
390390
}
391391

392-
$token = md5(uniqid((string)mt_rand(), true));
393-
$expire = time() + 604800; // next week
392+
try {
393+
$random_bytes = random_bytes(16);
394+
} catch (\Exception $e) {
395+
$random_bytes = mt_rand();
396+
}
397+
398+
$token = md5(uniqid($random_bytes, true));
399+
$expire = time() + 86400; // 24 hours
394400

395401
$user->reset = $token . '::' . $expire;
396402
$user->save();

classes/Email.php

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Grav\Common\Config\Config;
66
use Grav\Common\Grav;
77
use Grav\Common\Language\Language;
8-
use Grav\Common\Page\Pages;
98
use Grav\Common\User\Interfaces\UserInterface;
109
use Grav\Common\Utils;
1110
use Grav\Plugin\Login\Invitations\Invitation;
@@ -39,9 +38,12 @@ public static function sendActivationEmail(UserInterface $user, UserInterface $a
3938
throw new \RuntimeException('User activation route does not exist!');
4039
}
4140

42-
/** @var Pages $pages */
43-
$pages = Grav::instance()['pages'];
44-
$activationLink = $pages->url(
41+
$site_host = $config->get('plugins.login.site_host');
42+
if (!empty($site_host)) {
43+
$activationRoute = rtrim($site_host, '/') . '/' . ltrim($activationRoute, '/');
44+
}
45+
46+
$activationLink = Utilis::url(
4547
$activationRoute . '/token' . $param_sep . $token . '/username' . $param_sep . $user->username,
4648
null,
4749
true
@@ -89,11 +91,14 @@ public static function sendResetPasswordEmail(UserInterface $user, UserInterface
8991
throw new \RuntimeException('Password reset route does not exist!');
9092
}
9193

92-
/** @var Pages $pages */
93-
$pages = Grav::instance()['pages'];
94-
$resetLink = $pages->url(
94+
$site_host = static::getConfig()->get('plugins.login.site_host');
95+
if (!empty($site_host)) {
96+
$resetRoute = rtrim($site_host, '/') . '/' . ltrim($resetRoute, '/');
97+
}
98+
99+
$resetLink = Utils::url(
95100
"{$resetRoute}/task{$param_sep}login.reset/token{$param_sep}{$token}/user{$param_sep}{$user->username}/nonce{$param_sep}" . Utils::getNonce('reset-form'),
96-
null,
101+
true,
97102
true
98103
);
99104

@@ -190,9 +195,7 @@ public static function sendInvitationEmail(Invitation $invitation, string $messa
190195
throw new \RuntimeException('User registration route does not exist!');
191196
}
192197

193-
/** @var Pages $pages */
194-
$pages = Grav::instance()['pages'];
195-
$invitationLink = $pages->url("{$inviteRoute}/{$param_sep}{$invitation->token}", null, true);
198+
$invitationLink = Utils::url("{$inviteRoute}/{$param_sep}{$invitation->token}", true, true);
196199

197200
$context = [
198201
'invitation_link' => $invitationLink,
@@ -218,11 +221,17 @@ protected static function sendEmail(string $template, array $context, array $par
218221

219222
$config = static::getConfig();
220223

224+
$site_host = $config->get('plugins.login.site_host');
225+
if (empty($site_host)) {
226+
$site_host = Grav::instance()['uri']->host();
227+
}
228+
221229
// Twig context.
222230
$context += [
223231
'actor' => $actor,
224232
'user' => $user,
225233
'site_name' => $config->get('site.title', 'Website'),
234+
'site_host' => $site_host,
226235
'author' => $config->get('site.author.name', ''),
227236
];
228237

classes/Login.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,13 @@ public function sendActivationEmail(UserInterface $user)
491491
throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD'));
492492
}
493493

494-
$token = md5(uniqid(mt_rand(), true));
494+
try {
495+
$random_bytes = random_bytes(16);
496+
} catch (\Exception $e) {
497+
$random_bytes = mt_rand();
498+
}
499+
500+
$token = md5(uniqid($random_bytes, true));
495501
$expire = time() + 604800; // next week
496502
$user->activation_token = $token . '::' . $expire;
497503
$user->save();

languages/en.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,7 @@ PLUGIN_LOGIN:
156156
INVITATION_EMAIL_MESSAGE: "We welcome you to register an account to on site."
157157
INVALID_INVITE_EMAILS: "<strong>Error:</strong> An invalid list of emails was provided"
158158
INVALID_FORM: "<strong>Error:</strong> Invalid form"
159-
FAILED_TO_SEND_EMAILS: "Failed to send emails to: %s"
159+
FAILED_TO_SEND_EMAILS: "Failed to send emails to: %s"
160+
HOST_WARNING: '<div style="background-color: #FFEDAD; color: #725F1C; border: 1px solid #FFD74E; padding: 10px; margin: 10px 0; border-radius: 5px;">NOTE: If you did not initiate this email or you don''t recognize the originating site: <strong>"%s"</strong> please ignore or delete this email.</div>'
161+
SITE_HOST: "Site Host"
162+
SITE_HOST_HELP: "For extra security, force this URL to be used in all password reset and activation emails. Leave empty to use the default site URL"

templates/emails/login/activate.html.twig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
{%- do email.message.setSubject(subject) %}
88

99
{%- block content -%}
10+
{{ 'PLUGIN_LOGIN.HOST_WARNING'|t(site_host)|raw }}
1011
{{ 'PLUGIN_LOGIN.ACTIVATION_EMAIL_BODY'|t(user.fullname, activation_link, site_name, author)|raw }}
1112
{%- endblock content -%}
1213

templates/emails/login/reset-password.html.twig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
{%- do email.message.setSubject(subject) %}
88

99
{%- block content -%}
10+
{{ 'PLUGIN_LOGIN.HOST_WARNING'|t(site_host)|raw }}
1011
{{ 'PLUGIN_LOGIN.FORGOT_EMAIL_BODY'|t(user.fullname ?? user.username, reset_link, author, site_name)|raw }}
1112
{%- endblock content -%}
1213

0 commit comments

Comments
 (0)