PluginProbe
FlyWP Helper – Page Cache, Page Optimization, Emails for FlyWP Server Control Panel / 1.8.0
FlyWP Helper – Page Cache, Page Optimization, Emails for FlyWP Server Control Panel v1.8.0
1.8.0 1.7.1 1.7.0 1.6.0 1.5.2 trunk 0.1 0.2.0 0.2.1 0.3 0.3.1 0.3.2 0.3.3 0.3.4 0.4 0.4.1 0.4.2 0.4.3 1.0 1.1 1.2 1.3.1 1.4.0 1.4.1 1.5.0 All 27 releases
← All changes | includes/Frontend/MagicLogin.php +7 -17 1.6.0 → 1.8.0 View file →
@@ -55,12 +55,9 @@
55 55 if ( wp_unslash( $_SERVER['REQUEST_METHOD'] ) !== 'POST' ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
56 56 return false;
57 57 }
58 58
59 - // Compared as a path, not as text. `sanitize_text_field()` strips percent-encoded octets
60 - // and tags, so it would let `/flywp-magic-login%20` and friends match here while WordPress
61 - // itself would 404 them — quietly defeating any nginx `location =`, WAF rule or allowlist
62 - // a host puts in front of this endpoint.
59 + // Compared as a path. Do not swap in `sanitize_text_field()`, which alters the value.
63 60 $path = wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
64 61
65 62 return $path === self::PATH;
66 63 }
@@ -100,11 +97,11 @@
100 97 if ( $token === '' ) {
101 98 $this->refuse( 'missing_token' );
102 99 }
103 100
104 - $claims = MagicLoginToken::parse( $token, flywp()->get_key(), time() );
101 + $claims = MagicLoginToken::parse( $token, flywp()->get_login_public_key(), time() );
105 102
106 - if ( ! $claims ) {
103 + if ( $claims === null ) {
107 104 $this->refuse( 'invalid_token' );
108 105 }
109 106
110 107 if ( ! $this->spend_token( $claims['jti'], $claims['exp'] ) ) {
@@ -113,10 +110,9 @@
113 110
114 111 $user = get_user_by( 'login', $claims['sub'] );
115 112
116 113 if ( ! $user instanceof WP_User ) {
117 - // Fail closed. This used to fall through to the first administrator on the site,
118 - // which turned a wrong username into an administrator session.
114 + // Fail closed on an unknown user; never substitute another account.
119 115 $this->refuse( 'unknown_user', $claims['sid'] );
120 116 }
121 117
122 118 wp_set_current_user( $user->ID, $user->user_login );
@@ -138,16 +134,11 @@
138 134
139 135 /**
140 136 * Mark a token as spent, refusing a second use of the same one.
141 137 *
142 - * Written before the cookie is issued, so a replay racing the original loses.
138 + * Written before the cookie is issued. Uses an options row rather than a transient so the
139 + * unique index on `option_name` decides the outcome; keep it that way.
143 140 *
144 - * Deliberately an options row rather than a transient. A transient lives in the object cache
145 - * when one is installed — which FlyWP installs on both stacks — where it can be evicted under
146 - * memory pressure or dropped by a cache flush, and the read-then-write a transient needs is a
147 - * race in its own right. The unique index on `option_name` settles both: the insert either
148 - * wins or it does not, and it survives a cache flush either way.
149 - *
150 141 * @param string $jti Token identifier.
151 142 * @param int $exp Token expiry, as a unix timestamp.
152 143 *
153 144 * @return bool False when this token has been used already, or the marker could not be stored.
@@ -209,10 +200,9 @@
209 200 * @param int|null $site_id FlyWP site id, when the token got far enough to name one.
210 201 */
211 202 do_action( 'flywp_magic_login_failed', $reason, $site_id );
212 203
213 - // Behind WP_DEBUG_LOG: this endpoint is unauthenticated, so an unconditional write here is
214 - // a disk-fill anyone can drive. Listeners on the action above get every attempt regardless.
204 + // Behind WP_DEBUG_LOG. Listeners on the action above get every attempt regardless.
215 205 if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
216 206 error_log( sprintf( 'FlyWP magic login refused (%s) for site %s', $reason, $site_id === null ? 'unknown' : $site_id ) );
217 207 }
218 208