Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/really-simple-ssl/assets/features/two-fa/assets.min.asset.php
+++ b/really-simple-ssl/assets/features/two-fa/assets.min.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '3d205eb77676a1f400ea');
+<?php return array('dependencies' => array(), 'version' => '244dba3cbf21df2e98b9');
--- a/really-simple-ssl/core/app/Support/Helpers/Storages/RequestStorage.php
+++ b/really-simple-ssl/core/app/Support/Helpers/Storages/RequestStorage.php
@@ -25,13 +25,23 @@
private function getRequestBody(): array
{
$body = [];
- if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
- $input = file_get_contents('php://input');
- $decoded = json_decode($input, true);
- if (is_array($decoded)) {
- $body = $decoded;
- }
+ if (!isset($_SERVER['REQUEST_METHOD'])) {
+ return $body;
}
+
+ $requestMethod = strtoupper((string) $_SERVER['REQUEST_METHOD']);
+
+ if ($requestMethod === 'GET') {
+ return $body;
+ }
+
+ $input = file_get_contents('php://input');
+ $decoded = json_decode($input, true);
+
+ if (is_array($decoded)) {
+ $body = $decoded;
+ }
+
return $body;
}
}
--- a/really-simple-ssl/core/config/env.php
+++ b/really-simple-ssl/core/config/env.php
@@ -11,7 +11,7 @@
return [
'plugin' => [
'name' => 'Really Simple Security',
- 'version' => '9.5.10',
+ 'version' => '9.5.10.1',
'pro' => false,
'path' => $pluginRootPath,
'base_path' => $pluginBaseFile,
--- a/really-simple-ssl/core/vendor/composer/installed.php
+++ b/really-simple-ssl/core/vendor/composer/installed.php
@@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
- 'reference' => '03db385d1d980351610744b2cd5e9fbc07e8286e',
+ 'reference' => '247858eb5cb03efb3125f60f0993c0880ae93702',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -13,7 +13,7 @@
'__root__' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
- 'reference' => '03db385d1d980351610744b2cd5e9fbc07e8286e',
+ 'reference' => '247858eb5cb03efb3125f60f0993c0880ae93702',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
--- a/really-simple-ssl/rlrsssl-really-simple-ssl.php
+++ b/really-simple-ssl/rlrsssl-really-simple-ssl.php
@@ -3,7 +3,7 @@
* Plugin Name: Really Simple Security
* Plugin URI: https://really-simple-ssl.com
* Description: Easily improve site security with WordPress Hardening, Two-Factor Authentication (2FA), Login Protection, Vulnerability Detection and SSL certificate generation.
- * Version: 9.5.10
+ * Version: 9.5.10.1
* Requires at least: 6.6
* Requires PHP: 7.4
* Author: Really Simple Security
@@ -122,7 +122,7 @@
define('rsssl_url', plugin_dir_url(__FILE__));
define('rsssl_path', trailingslashit(plugin_dir_path(__FILE__)));
define('rsssl_template_path', trailingslashit(plugin_dir_path(__FILE__)).'grid/templates/');
- define('rsssl_version', '9.5.10');
+ define('rsssl_version', '9.5.10.1');
define('rsssl_le_cron_generation_renewal_check', 20);
define('rsssl_le_manual_generation_renewal_check', 15);
}
--- a/really-simple-ssl/security/wordpress/two-fa/class-rsssl-two-factor-profile-settings.php
+++ b/really-simple-ssl/security/wordpress/two-fa/class-rsssl-two-factor-profile-settings.php
@@ -449,6 +449,7 @@
wp_localize_script('rsssl-profile-settings', 'rsssl_profile', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'backup_codes' => $backup_codes,
+ 'nonce' => wp_create_nonce('wp_rest'),
'root' => esc_url_raw(rest_url(Rsssl_Two_Factor::REST_NAMESPACE)),
'user_id' => get_current_user_id(),
'origin' => 'profile',
--- a/really-simple-ssl/security/wordpress/two-fa/controllers/class-rsssl-abstract-controller.php
+++ b/really-simple-ssl/security/wordpress/two-fa/controllers/class-rsssl-abstract-controller.php
@@ -5,6 +5,7 @@
use Exception;
use ReflectionClass;
use RSSSLSecurityWordPressTwo_FaModelsRsssl_Request_Parameters;
+use RSSSLSecurityWordPressTwo_FaProvidersRsssl_Provider_Loader;
use RSSSLSecurityWordPressTwo_FaRsssl_Two_Fa_Authentication;
use RSSSLSecurityWordPressTwo_FaTraitsRsssl_Args_Builder;
use RSSSLSecurityWordPressTwo_FaTraitsRsssl_Two_Fa_Helper;
@@ -112,8 +113,7 @@
*/
public function check_login_and_get_user( int $user_id, string $login_nonce ): WP_User {
if ( ! Rsssl_Two_Fa_Authentication::verify_login_nonce( $user_id, $login_nonce ) ) {
- // We throw an error
- wp_die();
+ throw new Exception( __( 'Invalid authentication request.', 'really-simple-ssl' ) );
}
/**
* Get the user by the user ID.
@@ -128,4 +128,84 @@
return $user;
}
-}
No newline at end of file
+ /**
+ * Check if the user already has a configured two-factor provider.
+ *
+ * Users with an active provider must complete that provider's challenge before
+ * the login can be completed or two-factor settings can be changed.
+ * A login nonce only proves the password step was passed.
+ *
+ * @param WP_User $user The user object.
+ *
+ * @return bool
+ */
+ protected function has_configured_provider( WP_User $user ): bool {
+ $loader = Rsssl_Provider_Loader::get_loader();
+ $login_protection_enabled = (bool) rsssl_get_option( 'login_protection_enabled' );
+
+ foreach ( $loader::available_providers() as $method => $provider ) {
+ if ( ! $this->is_provider_available_for_current_login_mode( $method, $login_protection_enabled ) ) {
+ continue;
+ }
+
+ if ( ! $provider::is_enabled( $user ) ) {
+ continue;
+ }
+
+ if ( $provider::is_configured( $user ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if the user has a configured provider other than the allowed method.
+ *
+ * Used when the allowed method still has its own challenge to verify. For
+ * example, email may finish with an email token, but it may not replace TOTP.
+ *
+ * @param WP_User $user The user object.
+ * @param string $allowed_method The method that may complete the current challenge.
+ *
+ * @return bool
+ */
+ protected function has_configured_provider_other_than( WP_User $user, string $allowed_method ): bool {
+ $loader = Rsssl_Provider_Loader::get_loader();
+ $login_protection_enabled = (bool) rsssl_get_option( 'login_protection_enabled' );
+
+ foreach ( $loader::available_providers() as $method => $provider ) {
+ if ( $allowed_method === $method ) {
+ continue;
+ }
+
+ if ( ! $this->is_provider_available_for_current_login_mode( $method, $login_protection_enabled ) ) {
+ continue;
+ }
+
+ if ( ! $provider::is_enabled( $user ) ) {
+ continue;
+ }
+
+ if ( $provider::is_configured( $user ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if the provider belongs to the active login protection mode.
+ *
+ * @param string $method The provider method.
+ * @param bool $login_protection_enabled Whether full login protection is enabled.
+ *
+ * @return bool
+ */
+ protected function is_provider_available_for_current_login_mode( string $method, bool $login_protection_enabled ): bool {
+ return $login_protection_enabled || 'passkey' === $method;
+ }
+
+}
--- a/really-simple-ssl/security/wordpress/two-fa/controllers/class-rsssl-base-controller.php
+++ b/really-simple-ssl/security/wordpress/two-fa/controllers/class-rsssl-base-controller.php
@@ -74,6 +74,15 @@
);
}
+ if ( $this->has_configured_provider( $user ) ) {
+ return new WP_REST_Response(
+ [
+ 'error' => __( 'Two-Factor Authentication must be completed before it can be disabled.', 'really-simple-ssl' ),
+ ],
+ 403
+ );
+ }
+
// if the 2FA is not enabled for the user, we only handle the passkey meta key
if ( ! (bool) rsssl_get_option( 'login_protection_enabled' ) ) {
// Remove the passkey meta key for the user.
@@ -136,8 +145,13 @@
}
$loader = Rsssl_Provider_Loader::get_loader();
+ $login_protection_enabled = (bool) rsssl_get_option( 'login_protection_enabled' );
foreach ( $loader::available_providers() as $method => $provider ) {
+ if ( ! $this->is_provider_available_for_current_login_mode( $method, $login_protection_enabled ) ) {
+ continue;
+ }
+
if ( ! $provider::is_enabled( $user ) ) {
continue;
}
@@ -158,6 +172,10 @@
* @return bool
*/
private function can_user_skip_onboarding( WP_User $user ): bool {
+ if ( $this->has_configured_provider( $user ) ) {
+ return false;
+ }
+
if ( ! $this->is_forced_user( $user->ID ) ) {
return true;
}
--- a/really-simple-ssl/security/wordpress/two-fa/controllers/class-rsssl-email-controller.php
+++ b/really-simple-ssl/security/wordpress/two-fa/controllers/class-rsssl-email-controller.php
@@ -84,6 +84,17 @@
} catch (Exception $e) {
return new WP_REST_Response(['error' => $e->getMessage()], 403);
}
+
+ // Do not start a new email setup from the pre-auth flow after 2FA exists.
+ if ( $this->has_configured_provider( $user ) ) {
+ return new WP_REST_Response(
+ array(
+ 'error' => __( 'Two-Factor Authentication must be completed before it can be changed.', 'really-simple-ssl' ),
+ ),
+ 403
+ );
+ }
+
// Check if the provider.
if ('email' !== $parameters->provider) {
return new WP_REST_Response(array('error' => 'Invalid provider'), 401);
@@ -111,6 +122,16 @@
return new WP_REST_Response(['error' => $e->getMessage()], 403);
}
+ // Do not start a new email setup from the pre-auth flow after 2FA exists.
+ if ( $this->has_configured_provider( $user ) ) {
+ return new WP_REST_Response(
+ array(
+ 'error' => __( 'Two-Factor Authentication must be completed before it can be changed.', 'really-simple-ssl' ),
+ ),
+ 403
+ );
+ }
+
// Check if the provider.
if ('email' !== $parameters->provider) {
return new WP_REST_Response(array('error' => __('Invalid provider', 'really-simple-ssl')), 401);
@@ -148,6 +169,16 @@
return new WP_REST_Response(['error' => $e->getMessage()], 403);
}
+ // Email may finish only its own challenge; it may not replace another active method.
+ if ( $this->has_configured_provider_other_than( $user, 'email' ) ) {
+ return new WP_REST_Response(
+ array(
+ 'error' => __( 'Two-Factor Authentication must be completed before it can be changed.', 'really-simple-ssl' ),
+ ),
+ 403
+ );
+ }
+
// Validate the provided token.
if (!Rsssl_Two_Factor_Email::get_instance()->validate_token($user->ID, self::sanitize_token($parameters->token))) {
@@ -329,4 +360,4 @@
delete_transient($transient_key);
return true;
}
-}
No newline at end of file
+}