Make WordPress Core

Changeset 61838


Ignore:
Timestamp:
03/05/2026 09:22:05 AM (4 weeks ago)
Author:
ellatrix
Message:

Real-time collaboration: check wp_user_id before accepting awareness update.

Using the built-in HTTP polling sync server, awareness state is accepted and stored after the user is authorized. This state is keyed against their sync client ID, which is randomly generated.

However, nothing prevents a user from spoofing another client's client ID, which is discoverable by inspecting network responses. By replaying a sync request with a different client ID, they could temporarily overwrite another client's awareness state.

This change prevents this spoofing by storing and checking the user's WordPress user ID to ensure it matches the initial update.

Developed in: https://github.com/WordPress/wordpress-develop/pull/11120.
Syncs: https://github.com/WordPress/gutenberg/pull/76056.

Fixes #64782.
Props czarate.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/collaboration/class-wp-http-polling-sync-server.php

    r61746 r61838  
    182182        }
    183183
    184         $rooms = $request['rooms'];
     184        $rooms      = $request['rooms'];
     185        $wp_user_id = get_current_user_id();
    185186
    186187        foreach ( $rooms as $room ) {
    187             $room         = $room['room'];
     188            $client_id = $room['client_id'];
     189            $room      = $room['room'];
     190
     191            // Check that the client_id is not already owned by another user.
     192            $existing_awareness = $this->storage->get_awareness_state( $room );
     193            foreach ( $existing_awareness as $entry ) {
     194                if ( $client_id === $entry['client_id'] && $wp_user_id !== $entry['wp_user_id'] ) {
     195                    return new WP_Error(
     196                        'rest_cannot_edit',
     197                        __( 'Client ID is already in use by another user.' ),
     198                        array( 'status' => rest_authorization_required_code() )
     199                    );
     200                }
     201            }
     202
    188203            $type_parts   = explode( '/', $room, 2 );
    189204            $object_parts = explode( ':', $type_parts[1] ?? '', 2 );
     
    347362                'state'      => $awareness_update,
    348363                'updated_at' => $current_time,
     364                'wp_user_id' => get_current_user_id(),
    349365            );
    350366        }
  • trunk/tests/phpunit/tests/rest-api/rest-sync-server.php

    r61833 r61838  
    698698    }
    699699
     700    public function test_sync_awareness_client_id_cannot_be_used_by_another_user() {
     701        wp_set_current_user( self::$editor_id );
     702
     703        $room = $this->get_post_room();
     704
     705        // Editor establishes awareness with client_id 1.
     706        $this->dispatch_sync(
     707            array(
     708                $this->build_room( $room, 1, 0, array( 'name' => 'Editor' ) ),
     709            )
     710        );
     711
     712        // A different user tries to use the same client_id.
     713        $editor_id_2 = self::factory()->user->create( array( 'role' => 'editor' ) );
     714        wp_set_current_user( $editor_id_2 );
     715
     716        $response = $this->dispatch_sync(
     717            array(
     718                $this->build_room( $room, 1, 0, array( 'name' => 'Impostor' ) ),
     719            )
     720        );
     721
     722        $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 );
     723    }
     724
    700725    /*
    701726     * Multiple rooms tests.
Note: See TracChangeset for help on using the changeset viewer.