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/controller/class-webauthn.php +88 -44 5.5.0 → 6.3.0 View file →
@@ -7,19 +7,20 @@
7 7
8 8 namespace WP_Defender\Controller;
9 9
10 10 use Error;
11 +use WP_User;
11 12 use Exception;
12 -use Webauthn\Server;
13 +use WP_DEFENDER_VENDOR\Webauthn\Server;
13 14 use WP_Defender\Controller;
14 15 use WP_Defender\Behavior\WPMUDEV;
15 -use Nyholm\Psr7\Factory\Psr17Factory;
16 -use Webauthn\PublicKeyCredentialRpEntity;
17 -use Nyholm\Psr7Server\ServerRequestCreator;
18 -use Webauthn\PublicKeyCredentialUserEntity;
19 -use Webauthn\AuthenticatorSelectionCriteria;
20 -use Webauthn\PublicKeyCredentialRequestOptions;
21 -use Webauthn\PublicKeyCredentialCreationOptions;
16 +use WP_DEFENDER_VENDOR\Nyholm\Psr7\Factory\Psr17Factory;
17 +use WP_DEFENDER_VENDOR\Webauthn\PublicKeyCredentialRpEntity;
18 +use WP_DEFENDER_VENDOR\Nyholm\Psr7Server\ServerRequestCreator;
19 +use WP_DEFENDER_VENDOR\Webauthn\PublicKeyCredentialUserEntity;
20 +use WP_DEFENDER_VENDOR\Webauthn\AuthenticatorSelectionCriteria;
21 +use WP_DEFENDER_VENDOR\Webauthn\PublicKeyCredentialRequestOptions;
22 +use WP_DEFENDER_VENDOR\Webauthn\PublicKeyCredentialCreationOptions;
22 23 use WP_Defender\Traits\Webauthn as Webauthn_Trait;
23 24 use WP_Defender\Component\Two_Fa as Two_Fa_Component;
24 25 use WP_Defender\Component\Webauthn as Webauthn_Component;
25 26 use WP_Defender\Controller\Two_Factor as Two_Fa_Controller;
@@ -65,10 +66,10 @@
65 66 $this,
66 67 'get_credential_request_option',
67 68 )
68 69 );
69 - add_action( 'wp_ajax_defender_webauthn_verify_response', array( $this, 'verify_response' ) );
70 - add_action( 'wp_ajax_nopriv_defender_webauthn_verify_response', array( $this, 'verify_response' ) );
70 + add_action( 'wp_ajax_defender_webauthn_verify_response', array( $this, 'handle_verify_response_request' ) );
71 + add_action( 'wp_ajax_nopriv_defender_webauthn_verify_response', array( $this, 'handle_verify_response_request' ) );
71 72 // Handling requests in the frontend.
72 73 if ( wd_di()->get( Two_Fa_Controller::class )->woo_integration_enabled() ) {
73 74 add_action( 'wp_ajax_nopriv_defender_webauthn_create_challenge', array( $this, 'create_challenge' ) );
74 75 add_action( 'wp_ajax_nopriv_defender_webauthn_verify_challenge', array( $this, 'verify_challenge' ) );
@@ -104,9 +105,9 @@
104 105 public function get_current_user_authenticators(): array {
105 106 $arr = array();
106 107 $user_id = get_current_user_id();
107 108 $user_credentials = $this->service->getCredentials( $user_id );
108 - if ( ! empty( $user_credentials ) && is_array( $user_credentials ) ) {
109 + if ( array() !== $user_credentials ) {
109 110 foreach ( $user_credentials as $key => $value ) {
110 111 $arr[] = array(
111 112 'key' => $this->base64url_encode( $key ),
112 113 'label' => $value['label'],
@@ -159,9 +160,9 @@
159 160 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
160 161 }
161 162
162 163 $type = defender_get_data_from_request( 'type', 'g' );
163 - if ( empty( $type ) ) {
164 + if ( '' === $type ) {
164 165 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
165 166 }
166 167
167 168 $user_id = get_current_user_id();
@@ -189,15 +190,12 @@
189 190 $authenticator_type = AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE;
190 191 }
191 192
192 193 // Create authenticator selection.
193 - $resident_key = false;
194 - $user_verification = AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
195 - $authenticator_selection_criteria = new AuthenticatorSelectionCriteria(
196 - $authenticator_type,
197 - $resident_key,
198 - $user_verification
199 - );
194 + $authenticator_selection_criteria = new AuthenticatorSelectionCriteria();
195 + $authenticator_selection_criteria->setAuthenticatorAttachment( $authenticator_type );
196 + $authenticator_selection_criteria->setRequireResidentKey( false );
197 + $authenticator_selection_criteria->setUserVerification( AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED );
200 198
201 199 $rp_entity = new PublicKeyCredentialRpEntity(
202 200 $this->get_site_name(),
203 201 $this->get_site_domain()
@@ -246,11 +244,12 @@
246 244 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
247 245 }
248 246
249 247 $posted_data = defender_get_data_from_request( null, 'p' );
250 - if ( empty( $posted_data['data'] ) || empty( $posted_data['client_id'] ) ) {
248 + if ( '' === $posted_data['data'] || '' === $posted_data['client_id'] ) {
251 249 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
252 250 }
251 + $client_id = sanitize_text_field( $posted_data['client_id'] );
253 252
254 253 $psr17_factory = new Psr17Factory();
255 254 $creator = new ServerRequestCreator(
256 255 $psr17_factory,
@@ -271,12 +270,11 @@
271 270 $this->service,
272 271 null
273 272 );
274 273
275 - $response_data = base64_decode( sanitize_text_field( $posted_data['data'] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
276 - $client_id = sanitize_text_field( $posted_data['client_id'] );
274 + $response_data = base64_decode( sanitize_text_field( $posted_data['data'] ), true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
277 275 $pub_key_cco = $this->get_trans_val( 'pub_key_cco', $client_id );
278 - $pub_key_cco = PublicKeyCredentialCreationOptions::createFromString( base64_decode( $pub_key_cco ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
276 + $pub_key_cco = PublicKeyCredentialCreationOptions::createFromString( base64_decode( $pub_key_cco, true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
279 277
280 278 $public_key_credential_source = $server->loadAndCheckAttestationResponse(
281 279 $response_data,
282 280 $pub_key_cco,
@@ -307,12 +305,16 @@
307 305 );
308 306 }
309 307 wp_send_json_success( $response );
310 308 } catch ( Error $error ) {
311 - $this->delete_trans( 'pub_key_cco', $client_id );
309 + if ( null !== $client_id ) {
310 + $this->delete_trans( 'pub_key_cco', $client_id );
311 + }
312 312 wp_send_json_error( $error->getMessage() );
313 313 } catch ( Exception $exception ) {
314 - $this->delete_trans( 'pub_key_cco', $client_id );
314 + if ( null !== $client_id ) {
315 + $this->delete_trans( 'pub_key_cco', $client_id );
316 + }
315 317 wp_send_json_error( $exception->getMessage() );
316 318 }
317 319 }
318 320
@@ -328,9 +330,9 @@
328 330 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
329 331 }
330 332
331 333 $cred_id = defender_get_data_from_request( 'key', 'p' );
332 - if ( empty( $cred_id ) ) {
334 + if ( '' === $cred_id ) {
333 335 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
334 336 }
335 337
336 338 $cred_id = $this->base64url_decode( $cred_id );
@@ -343,9 +345,9 @@
343 345 $this->service->removeUserHandleMatchFailed( $user_id, $cred_id );
344 346
345 347 if ( 0 === count( $option_user_credentials ) ) {
346 348 $enabled_providers = get_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, true );
347 - if ( empty( $enabled_providers ) ) {
349 + if ( ! is_array( $enabled_providers ) ) {
348 350 $enabled_providers = array();
349 351 }
350 352 $key = array_search( Webauthn_Provider::$slug, $enabled_providers, true );
351 353 if ( false !== $key ) {
@@ -377,9 +379,9 @@
377 379 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
378 380 }
379 381
380 382 $posted_data = defender_get_data_from_request( null, 'p' );
381 - if ( empty( $posted_data['key'] ) || empty( $posted_data['label'] ) ) {
383 + if ( ! isset( $posted_data['key'], $posted_data['label'] ) || '' === $posted_data['key'] || '' === $posted_data['label'] ) {
382 384 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
383 385 }
384 386
385 387 $cred_id = sanitize_text_field( $posted_data['key'] );
@@ -415,9 +417,9 @@
415 417 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
416 418 }
417 419
418 420 $posted_data = defender_get_data_from_request( null, 'p' );
419 - if ( empty( $posted_data['username'] ) ) {
421 + if ( ! isset( $posted_data['username'] ) || '' === $posted_data['username'] ) {
420 422 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
421 423 }
422 424
423 425 $rp_entity = new PublicKeyCredentialRpEntity(
@@ -441,9 +443,9 @@
441 443 if ( false === $user_entity ) {
442 444 throw new Exception( esc_html__( 'User does not exist.', 'defender-security' ) );
443 445 }
444 446
445 - $auth_type = ! empty( $posted_data['type'] ) ? sanitize_text_field( $posted_data['type'] ) : null;
447 + $auth_type = isset( $posted_data['type'] ) && '' !== $posted_data['type'] ? sanitize_text_field( $posted_data['type'] ) : null;
446 448 if ( in_array( $auth_type, self::ALLOWED_AUTH_TYPES, true ) ) {
447 449 $credential_sources = $this->service->findAllForUserByType( $user->ID, $auth_type );
448 450 } else {
449 451 $credential_sources = $this->service->findAllForUserEntity( $user_entity );
@@ -488,16 +490,26 @@
488 490 }
489 491 }
490 492
491 493 /**
494 + * Handle verify response request.
495 + *
496 + * @return void
497 + */
498 + public function handle_verify_response_request(): void {
499 + $this->verify_response();
500 + }
501 +
502 + /**
492 503 * Verify response.
493 504 *
494 - * @param bool $will_return Either return array or echo json.
505 + * @param bool $will_return Either return array or echo json.
506 + * @param WP_User|null $expected_user The expected user.
495 507 *
496 - * @return array
508 + * @return array|void
497 509 * @throws Exception If something goes wrong.
498 510 */
499 - public function verify_response( bool $will_return = false ) {
511 + public function verify_response( bool $will_return = false, ?WP_User $expected_user = null ) {
500 512 $client_id = null;
501 513 try {
502 514 if ( ! $this->verify_nonce( 'wpdef_webauthn', 'post' ) ) {
503 515 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
@@ -503,26 +515,52 @@
503 515 throw new Exception( esc_html__( 'Bad nonce.', 'defender-security' ) );
504 516 }
505 517
506 518 $posted_data = defender_get_data_from_request( null, 'p' );
507 - if ( empty( $posted_data['data'] ) || empty( $posted_data['username'] ) || empty( $posted_data['client_id'] ) ) {
519 + if (
520 + ! isset( $posted_data['data'], $posted_data['username'], $posted_data['client_id'] )
521 + || '' === $posted_data['data']
522 + || '' === $posted_data['username']
523 + || '' === $posted_data['client_id']
524 + ) {
508 525 throw new Exception( esc_html__( 'Missing field(s).', 'defender-security' ) );
509 526 }
527 + $client_id = sanitize_text_field( $posted_data['client_id'] );
528 + // Check expected user.
529 + if ( $expected_user instanceof WP_User ) {
530 + $user = $expected_user;
531 + } else {
532 + $user = wp_get_current_user();
533 + }
510 534
511 - $user_entity = false;
512 - $response_data = base64_decode( $posted_data['data'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
513 - $username = sanitize_text_field( $posted_data['username'] );
514 - $client_id = sanitize_text_field( $posted_data['client_id'] );
515 - $pub_key_cro = PublicKeyCredentialRequestOptions::createFromString( base64_decode( $this->get_trans_val( 'pub_key_cro', $client_id ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
516 - $user = get_user_by( 'login', $username );
535 + // Establish a trusted scope BEFORE any access to the repository —
536 + // both before our own pre-check and before internal library calls
537 + // inside loadAndCheckAssertionResponse().
538 + $this->service->set_expected_user_id( $user->ID );
539 + $response_data = base64_decode( $posted_data['data'], true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
540 + if ( false === $response_data ) {
541 + throw new Exception( esc_html__( 'Invalid response data.', 'defender-security' ) );
542 + }
517 543
518 - if ( is_object( $user ) ) {
519 - $user_entity = $this->get_user_entity( $user->ID );
544 + $decoded_response = json_decode( $response_data, true );
545 + if ( ! is_array( $decoded_response ) || ! isset( $decoded_response['rawId'] ) || ! is_string( $decoded_response['rawId'] ) || '' === $decoded_response['rawId'] ) {
546 + throw new Exception( esc_html__( 'Invalid assertion payload.', 'defender-security' ) );
520 547 }
521 548
549 + $credential_id = $this->base64url_decode( $decoded_response['rawId'] );
550 + if ( false === $credential_id || '' === $credential_id ) {
551 + throw new Exception( esc_html__( 'Invalid credential id.', 'defender-security' ) );
552 + }
553 + $credential = $this->service->findOneByCredentialId( $credential_id );
554 + if ( ! $credential ) {
555 + throw new Exception( esc_html__( 'Invalid user.', 'defender-security' ) );
556 + }
557 +
558 + $user_entity = $this->get_user_entity( $user->ID );
522 559 if ( false === $user_entity ) {
523 560 throw new Exception( esc_html__( 'User does not exist.', 'defender-security' ) );
524 561 }
562 + $pub_key_cro = PublicKeyCredentialRequestOptions::createFromString( base64_decode( $this->get_trans_val( 'pub_key_cro', $client_id ), true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
525 563
526 564 $psr17_factory = new Psr17Factory();
527 565 $creator = new ServerRequestCreator(
528 566 $psr17_factory,
@@ -551,14 +589,18 @@
551 589 $server_request
552 590 );
553 591
554 592 $this->delete_trans( 'pub_key_cro', $client_id );
593 + // Reset the area just in case.
594 + $this->service->set_expected_user_id( null );
555 595
556 596 $data = esc_html__( 'Authenticator verified successfully.', 'defender-security' );
557 597
558 598 return defender_maybe_echo_json( $data, true, $will_return );
559 599 } catch ( Error $error ) {
560 - $this->delete_trans( 'pub_key_cro', $client_id );
600 + if ( null !== $client_id ) {
601 + $this->delete_trans( 'pub_key_cro', $client_id );
602 + }
561 603
562 604 return defender_maybe_echo_json( $error->getMessage(), false, $will_return );
563 605 } catch ( Exception $exception ) {
564 606 $data = array( 'message' => $exception->getMessage() );
@@ -572,9 +614,11 @@
572 614
573 615 $this->service->addUserHandleMatchFailed( $user, $decoded_response );
574 616 }
575 617
576 - $this->delete_trans( 'pub_key_cro', $client_id );
618 + if ( null !== $client_id ) {
619 + $this->delete_trans( 'pub_key_cro', $client_id );
620 + }
577 621
578 622 return defender_maybe_echo_json( $data, false, $will_return );
579 623 }
580 624 }