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 +69 -18 6.0.1 → 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
@@ -130,14 +157,38 @@
130 157 continue;
131 158 }
132 159
133 160 if ( isset( $data['credential_source'] ) ) {
134 - $sources[] = PublicKeyCredentialSource::createFromArray( $data['credential_source'] );
161 + $sources[] = PublicKeyCredentialSource::createFromArray(
162 + $this->normalize_legacy_trust_path( $data['credential_source'] )
163 + );
135 164 }
136 165 }
137 166 }
138 167
139 168 return $sources;
169 + }
170 +
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;
140 191 }
141 192
142 193 /**
143 194 * Store credential into database.