| @@ -14,9 +14,9 @@ | ||
| 14 | 14 | use WP_Defender\Model\Lockout_Ip; |
| 15 | 15 | use WP_Defender\Behavior\WPMUDEV; |
| 16 | 16 | use WP_Defender\Model\Lockout_Log; |
| 17 | 17 | use WP_Defender\Model\Onboard; |
| 18 | -use MaxMind\Db\Reader\InvalidDatabaseException; | |
| 18 | +use WP_DEFENDER_VENDOR\MaxMind\Db\Reader\InvalidDatabaseException; | |
| 19 | 19 | use WP_Defender\Model\Setting\Firewall as Model_Firewall; |
| 20 | 20 | use WP_Defender\Component\Trusted_Proxy_Preset\Cloudflare_Proxy; |
| 21 | 21 | use WP_Defender\Component\Trusted_Proxy_Preset\Trusted_Proxy_Preset; |
| 22 | 22 | use WP_Defender\Controller\Firewall as Firewall_Controller; |
| @@ -69,12 +69,44 @@ | ||
| 69 | 69 | |
| 70 | 70 | /** |
| 71 | 71 | * Check is the access from authenticated staff. |
| 72 | 72 | * |
| 73 | - * @return bool | |
| 73 | + * Validates the `wpmudev_is_staff` cookie token by verifying: | |
| 74 | + * - Token version and structure (v1.<payload>.<signature>) | |
| 75 | + * - HMAC-SHA256 signature against site salt (`wpmudev_remote_access`) | |
| 76 | + * - Token expiration timestamp against current time | |
| 77 | + * - Audience (`aud`) against current site URL (`home_url('/')`) | |
| 78 | + * - Subject (`sub`) against currently authenticated user ID | |
| 79 | + * | |
| 80 | + * @return bool True if staff access token is valid and unexpired, false otherwise. | |
| 74 | 81 | */ |
| 75 | 82 | private function is_authenticated_staff_access(): bool { |
| 76 | - return '1' === defender_get_data_from_request( 'wpmudev_is_staff', 'c' ); | |
| 83 | + $cookie = defender_get_data_from_request( 'wpmudev_is_staff', 'c' ); | |
| 84 | + if ( ! is_string( $cookie ) ) { | |
| 85 | + return false; | |
| 86 | + } | |
| 87 | + | |
| 88 | + $token = explode( '.', $cookie, 3 ); | |
| 89 | + if ( 3 !== count( $token ) || 'v1' !== $token[0] ) { | |
| 90 | + return false; | |
| 91 | + } | |
| 92 | + | |
| 93 | + $signature = hash_hmac( 'sha256', $token[1], wp_salt( 'wpmudev_remote_access' ) ); | |
| 94 | + if ( ! hash_equals( $signature, $token[2] ) ) { | |
| 95 | + return false; | |
| 96 | + } | |
| 97 | + // Improve flow to avoid bypassing staff access authentication via an unsigned cookie. Use legitim payload. | |
| 98 | + $payload = base64_decode( strtr( $token[1], '-_', '+/' ) . str_repeat( '=', ( 4 - strlen( $token[1] ) % 4 ) % 4 ), true );// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode | |
| 99 | + $payload = is_string( $payload ) ? json_decode( $payload, true ) : null; | |
| 100 | + | |
| 101 | + return is_array( $payload ) && | |
| 102 | + isset( $payload['aud'], $payload['exp'], $payload['sub'] ) && | |
| 103 | + is_string( $payload['aud'] ) && | |
| 104 | + is_numeric( $payload['exp'] ) && | |
| 105 | + is_numeric( $payload['sub'] ) && | |
| 106 | + (int) $payload['exp'] > time() && | |
| 107 | + hash_equals( home_url( '/' ), $payload['aud'] ) && | |
| 108 | + get_current_user_id() === (int) $payload['sub']; | |
| 77 | 109 | } |
| 78 | 110 | |
| 79 | 111 | /** |
| 80 | 112 | * Check if the access is from our staff access. |
| @@ -96,10 +128,13 @@ | ||
| 96 | 128 | $is_remote_access = is_object( WPMUDEV_Dashboard::$api ) && |
| 97 | 129 | true === WPMUDEV_Dashboard::$api->remote_access_details( 'enabled' ); |
| 98 | 130 | |
| 99 | 131 | if ( $is_remote_access ) { |
| 100 | - $access = $wpmu_dev->get_remote_access(); | |
| 101 | - if ( $this->is_authenticated_staff_access() || $this->is_commencing_staff_access( $access ) ) { | |
| 132 | + $access = $wpmu_dev->get_remote_access(); | |
| 133 | + $is_authenticated_staff_access = isset( $access['userid'] ) && | |
| 134 | + get_current_user_id() === (int) $access['userid'] && | |
| 135 | + $this->is_authenticated_staff_access(); | |
| 136 | + if ( $is_authenticated_staff_access || $this->is_commencing_staff_access( $access ) ) { | |
| 102 | 137 | $this->log( $access, Firewall_Controller::FIREWALL_LOG ); |
| 103 | 138 | |
| 104 | 139 | return true; |
| 105 | 140 | } |