Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/changelog/fix-stamp-meta-ownership
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Fixed an issue where quote authorization stamps could reference unrelated posts.
8 changes: 7 additions & 1 deletion includes/class-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,13 @@ private function maybe_get_stamp() {
return false;
}

$post = $this->get_queried_object();
$post = $this->get_queried_object();

// Ensure the meta belongs to the queried post to prevent arbitrary meta disclosure.
if ( (int) $meta->post_id !== $post->ID ) {
return false;
}
Comment thread
pfefferle marked this conversation as resolved.

$user_uri = get_user_id( $post->post_author );

if ( ! $user_uri ) {
Expand Down
58 changes: 58 additions & 0 deletions tests/phpunit/tests/includes/class-test-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,64 @@ public function test_maybe_get_stamp_invalid_author() {
$this->assertFalse( $result, 'Should return false for invalid post author' );
}

/**
* Test maybe_get_stamp rejects a stamp belonging to a different post.
*
* @covers ::maybe_get_stamp
*/
public function test_maybe_get_stamp_wrong_post() {
// Create two posts.
$post_a = self::factory()->post->create(
array(
'post_author' => self::$user_id,
'post_title' => 'Post A',
'post_content' => 'Content A',
'post_status' => 'publish',
)
);
$post_b = self::factory()->post->create(
array(
'post_author' => self::$user_id,
'post_title' => 'Post B',
'post_content' => 'Content B',
'post_status' => 'publish',
)
);

// Add stamp meta to post A.
$meta_id = \add_post_meta( $post_a, '_activitypub_quoted_by', 'https://remote.example.com/posts/789' );

// Request post B with post A's stamp — should be rejected.
Query::get_instance()->__destruct();
$this->go_to( home_url( '/?p=' . $post_b . '&stamp=' . $meta_id ) );
\set_query_var( 'stamp', $meta_id );

$reflection = new \ReflectionClass( Query::class );
$method = $reflection->getMethod( 'maybe_get_stamp' );
if ( \PHP_VERSION_ID < 80100 ) {
$method->setAccessible( true );
}

$query = Query::get_instance();
$result = $method->invoke( $query );

$this->assertFalse( $result, 'Should return false when stamp meta belongs to a different post' );

// Verify the same stamp works when queried with the correct post.
Query::get_instance()->__destruct();
$this->go_to( home_url( '/?p=' . $post_a . '&stamp=' . $meta_id ) );
\set_query_var( 'stamp', $meta_id );

$query = Query::get_instance();
$object = $query->get_activitypub_object();

$this->assertNotNull( $object, 'Should create QuoteAuthorization when stamp matches the queried post' );
$this->assertEquals( 'QuoteAuthorization', $object->get_type(), 'Should be QuoteAuthorization type' );

// Clean up.
\delete_post_meta( $post_a, '_activitypub_quoted_by' );
}

/**
* Test should_negotiate_content for author page with permalink as Actor ID.
*
Expand Down
Loading