PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
← All changes | src/component/class-webauthn.php +79 -23 5.6.0 → 6.3.0 View file →
@@ -7,12 +7,15 @@
7 7
8 8 namespace WP_Defender\Component;
9 9
10 10 use WP_User;
11 -use Webauthn\PublicKeyCredentialSource;
12 -use Webauthn\PublicKeyCredentialUserEntity;
11 +use WP_DEFENDER_VENDOR\Webauthn\PublicKeyCredentialSource;
12 +use WP_DEFENDER_VENDOR\Webauthn\PublicKeyCredentialUserEntity;
13 +use WP_DEFENDER_VENDOR\Webauthn\TrustPath\CertificateTrustPath;
14 +use WP_DEFENDER_VENDOR\Webauthn\TrustPath\EcdaaKeyIdTrustPath;
15 +use WP_DEFENDER_VENDOR\Webauthn\TrustPath\EmptyTrustPath;
13 16 use WP_Defender\Traits\Webauthn as Webauthn_Trait;
14 -use Webauthn\PublicKeyCredentialSourceRepository;
17 +use WP_DEFENDER_VENDOR\Webauthn\PublicKeyCredentialSourceRepository;
15 18
16 19 /**
17 20 * Handles WebAuthn functionalities providing methods to manage and verify user credentials.
18 21 *
@@ -36,8 +39,31 @@
36 39 */
37 40 public const USER_HANDLE_MISMATCH_KEY = 'user_handle_match_failed';
38 41
39 42 /**
43 + * User ID that findOneByCredentialId() lookups are currently scoped to.
44 + * Must be set only via set_expected_user_id() from trusted server-side
45 + * context (e.g. the WP_User passed down the 2FA login chain) — never
46 + * derived from request input inside this class.
47 + *
48 + * @var int|null
49 + */
50 + private $expected_user_id = null;
51 +
52 + /**
53 + * Restrict findOneByCredentialId() to a specific user's credential set.
54 + * Call this before delegating to Webauthn\Server so the library's internal
55 + * repository lookups stay scoped to the expected 2FA target user.
56 + *
57 + * @param int|null $user_id Trusted user ID, or null to clear the scope.
58 + *
59 + * @return void
60 + */
61 + public function set_expected_user_id( ?int $user_id ): void {
62 + $this->expected_user_id = $user_id;
63 + }
64 +
65 + /**
40 66 * Get user credentials.
41 67 *
42 68 * @param int $user_id The user ID.
43 69 *
@@ -60,8 +86,12 @@
60 86 }
61 87
62 88 /**
63 89 * Get one credential by credential ID.
90 + * {@inheritDoc}
91 + * Interface-mandated single-parameter signature. Scoping to a specific
92 + * user is done via $this->expected_user_id, set beforehand through
93 + * set_expected_user_id() by trusted calling code — never via $_POST.
64 94 *
65 95 * @param string $public_key_credential_id The public key credential ID.
66 96 *
67 97 * @return PublicKeyCredentialSource|null
@@ -66,24 +96,21 @@
66 96 *
67 97 * @return PublicKeyCredentialSource|null
68 98 */
69 99 public function findOneByCredentialId( string $public_key_credential_id ): ?PublicKeyCredentialSource {
70 - $username = defender_get_data_from_request( 'username', 'p' );
71 - if ( isset( $username ) ) {
72 - $user = get_user_by( 'login', $username );
100 + if ( null === $this->expected_user_id ) {
101 + // No trusted scope set — deny rather than search globally.
102 + // Never needs an unscoped, cross-account lookup.
103 + return null;
104 + }
73 105
74 - if ( is_object( $user ) ) {
75 - $user_id = $user->ID;
76 - } else {
77 - $user_id = 0;
78 - }
79 - } else {
80 - $user_id = get_current_user_id();
106 + $data = $this->getCredentials( $this->expected_user_id );
107 + $key = base64_encode( $public_key_credential_id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
108 + if ( isset( $data[ $key ]['credential_source'] ) ) {
109 + $credential_source = $data[ $key ]['credential_source']; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
110 +
111 + return PublicKeyCredentialSource::createFromArray( $this->normalize_legacy_trust_path( $credential_source ) );
81 112 }
82 - $data = $this->getCredentials( $user_id );
83 - if ( isset( $data[ base64_encode( $public_key_credential_id ) ]['credential_source'] ) ) { // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
84 - return PublicKeyCredentialSource::createFromArray( $data[ base64_encode( $public_key_credential_id ) ]['credential_source'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
85 - }
86 113
87 114 return null;
88 115 }
89 116
@@ -120,14 +147,21 @@
120 147 $user_data = $this->getCredentials( $user_id );
121 148
122 149 if ( is_array( $user_data ) ) {
123 150 foreach ( $user_data as $data ) {
124 - if ( ! empty( $type ) && ! empty( $data['authenticator_type'] ) && $type !== $data['authenticator_type'] ) {
151 + if (
152 + ! in_array( $type, array( null, '' ), true )
153 + && isset( $data['authenticator_type'] )
154 + && '' !== $data['authenticator_type']
155 + && $type !== $data['authenticator_type']
156 + ) {
125 157 continue;
126 158 }
127 159
128 160 if ( isset( $data['credential_source'] ) ) {
129 - $sources[] = PublicKeyCredentialSource::createFromArray( $data['credential_source'] );
161 + $sources[] = PublicKeyCredentialSource::createFromArray(
162 + $this->normalize_legacy_trust_path( $data['credential_source'] )
163 + );
130 164 }
131 165 }
132 166 }
133 167
@@ -134,8 +168,30 @@
134 168 return $sources;
135 169 }
136 170
137 171 /**
172 + * Map trust-path class names stored by Defender 6.1 to their Mozart-prefixed 6.2 classes.
173 + *
174 + * @param array $credential_source Stored WebAuthn credential source.
175 + *
176 + * @return array
177 + */
178 + private function normalize_legacy_trust_path( array $credential_source ): array {
179 + $legacy_type = $credential_source['trustPath']['type'] ?? null;
180 + $legacy_map = array(
181 + 'Webauthn\\TrustPath\\EmptyTrustPath' => EmptyTrustPath::class,
182 + 'Webauthn\\TrustPath\\EcdaaKeyIdTrustPath' => EcdaaKeyIdTrustPath::class,
183 + 'Webauthn\\TrustPath\\CertificateTrustPath' => CertificateTrustPath::class,
184 + );
185 +
186 + if ( is_string( $legacy_type ) && isset( $legacy_map[ $legacy_type ] ) ) {
187 + $credential_source['trustPath']['type'] = $legacy_map[ $legacy_type ];
188 + }
189 +
190 + return $credential_source;
191 + }
192 +
193 + /**
138 194 * Store credential into database.
139 195 *
140 196 * @param PublicKeyCredentialSource $public_key_credential_source The credential source to store.
141 197 *
@@ -199,13 +255,13 @@
199 255 * @return void
200 256 * @since 3.4.0
201 257 */
202 258 public function addUserHandleMatchFailed( $user, $data ): void {
203 - if ( ! empty( $user->ID ) && ! empty( $data['rawId'] ) ) {
259 + if ( isset( $user->ID ) && $user->ID > 0 && '' !== $data['rawId'] ) {
204 260 $meta_val = $this->getUserHandleMatchFailed( $user->ID );
205 261 $meta_val['show_notice'] = $meta_val['show_notice'] ?? true;
206 262
207 - if ( empty( $meta_val['authenticators'] ) || ! in_array( $data['rawId'], $meta_val['authenticators'], true ) ) {
263 + if ( ! isset( $meta_val['authenticators'] ) || array() === $meta_val['authenticators'] || ! in_array( $data['rawId'], $meta_val['authenticators'], true ) ) {
208 264 $meta_val['authenticators'][] = $data['rawId'];
209 265 }
210 266
211 267 $this->setUserHandleMatchFailed( $user->ID, $meta_val );
@@ -221,12 +277,12 @@
221 277 * @return void
222 278 * @since 3.4.0
223 279 */
224 280 public function removeUserHandleMatchFailed( int $user_id, string $auth_id ): void {
225 - if ( ! empty( $auth_id ) ) {
281 + if ( '' !== $auth_id ) {
226 282 $meta_val = $this->getUserHandleMatchFailed( $user_id );
227 283
228 - if ( ! empty( $meta_val['authenticators'] ) && is_array( $meta_val['authenticators'] ) ) {
284 + if ( isset( $meta_val['authenticators'] ) && is_array( $meta_val['authenticators'] ) && array() !== $meta_val['authenticators'] ) {
229 285 $pos = array_search( $auth_id, $meta_val['authenticators'], true );
230 286
231 287 if ( false !== $pos ) {
232 288 array_splice( $meta_val['authenticators'], $pos, 1 );