CVE-2023-1979 (published 2023-05-08T17:15Z)

The Web Stories for WordPress plugin supports the WordPress built-in functionality of protecting content with a password. The content is then only accessible to website visitors after entering the password. In WordPress, users with the "Author" role can create stories, but don\'t have the ability to edit password protected stories. The vulnerability allowed users with said role to bypass this permission check when trying to duplicate the protected story in the plugin\'s own dashboard, giving them access to the seemingly protected content. We recommend upgrading to version 1.32 or beyond commit\xa0 ad49781c2a35c5c92ef704d4b621ab4e5cb77d68 https://github.com/GoogleForCreators/web-stories-wp/commit/ad49781c2a35c5c92ef704d4b621ab4e5cb77d68 \n

CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-863
cwe_name: Incorrect Authorization
description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
extended_description: 
            Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource.
            When access control checks are incorrectly applied, users are able to access data or perform actions that they should not be allowed to perform. This can lead to a wide range of problems, including information exposures, denial of service, and arbitrary code execution.
         
         

Fix information:
repo_url: https://github.com/GoogleForCreators/web-stories-wp
commit_details: repo_url: https://github.com/GoogleForCreators/web-stories-wp
author: Pascal Birchler
author_date: 2023-05-02 15:26:03+02:00
msg: Merge pull request from GHSA-xcx6-4gm7-wrfm

* Update permission check when duplicating stories

Check for update instead of read permission

* Disallow view context for `style_presets` field

* Do not return `story_data` for password protected stories
repository: repo_url: https://github.com/GoogleForCreators/web-stories-wp
repo_name: GoogleForCreators/web-stories-wp
description: Web Stories for WordPress
repo_language: JavaScript
owner: GoogleForCreators
file_changes: filename: Stories_Base_Controller.php
change_type: ModificationType.MODIFY
diff: @@ -134,7 +134,7 @@ class Stories_Base_Controller extends WP_REST_Posts_Controller {
 
 		if ( ! empty( $schema['properties']['story_data'] ) && rest_is_field_included( 'story_data', $fields ) ) {
 			$post_story_data    = json_decode( $post->post_content_filtered, true );
-			$data['story_data'] = rest_sanitize_value_from_schema( $post_story_data, $schema['properties']['story_data'] );
+			$data['story_data'] = post_password_required( $post ) ? (object) [] : rest_sanitize_value_from_schema( $post_story_data, $schema['properties']['story_data'] );
 		}
 
 		/**
@@ -184,7 +184,7 @@ class Stories_Base_Controller extends WP_REST_Posts_Controller {
 			return $original_post;
 		}
 
-		if ( ! $this->check_read_permission( $original_post ) ) {
+		if ( ! $this->check_update_permission( $original_post ) ) {
 			return new \WP_Error(
 				'rest_cannot_create',
 				__( 'Sorry, you are not allowed to duplicate this story.', 'web-stories' ),

programming_language: PHP
filename: Stories_Controller.php
change_type: ModificationType.MODIFY
diff: @@ -215,7 +215,7 @@ class Stories_Controller extends Stories_Base_Controller {
 		$schema['properties']['style_presets'] = [
 			'description' => __( 'Style presets used by all stories', 'web-stories' ),
 			'type'        => 'object',
-			'context'     => [ 'view', 'edit' ],
+			'context'     => [ 'edit' ],
 		];
 
 		$schema['properties']['preview_link'] = [

programming_language: PHP
filename: Stories_Controller.php
change_type: ModificationType.MODIFY
diff: @@ -411,6 +411,36 @@ class Stories_Controller extends DependencyInjectedRestTestCase {
 		$this->assertArrayHasKey( 'https://api.w.org/lock', $links );
 	}
 
+	/**
+	 * @covers ::get_item
+	 * @covers ::prepare_item_for_response
+	 * @covers \Google\Web_Stories\REST_API\Stories_Base_Controller::prepare_item_for_response
+	 */
+	public function test_get_item_no_story_data_for_password_protected_post(): void {
+		$this->controller->register_routes();
+
+		$story = self::factory()->post->create(
+			[
+				'post_type'     => Story_Post_Type::POST_TYPE_SLUG,
+				'post_status'   => 'publish',
+				'post_password' => 'Top Secret',
+				'post_author'   => self::$user_id,
+			]
+		);
+
+		wp_set_current_user( self::$author_id );
+
+		$request  = new WP_REST_Request( \WP_REST_Server::READABLE, '/web-stories/v1/web-story/' . $story );
+		$response = rest_get_server()->dispatch( $request );
+
+		$data = $response->get_data();
+
+		$this->assertIsArray( $data );
+		$this->assertArrayHasKey( 'story_data', $data );
+		$this->assertIsObject( $data['story_data'] );
+		$this->assertEmpty( (array) $data['story_data'] );
+	}
+
 	/**
 	 * @covers ::get_item
 	 * @covers \Google\Web_Stories\REST_API\Stories_Base_Controller::get_available_actions
@@ -1070,7 +1100,7 @@ class Stories_Controller extends DependencyInjectedRestTestCase {
 	/**
 	 * @covers ::create_item
 	 */
-	public function test_create_item_duplicate_id_permission(): void {
+	public function test_create_item_duplicate_id_no_permission_for_private_post(): void {
 		$this->controller->register_routes();
 
 		$unsanitized_content    = file_get_contents( WEB_STORIES_TEST_DATA_DIR . '/story_post_content.html' );
@@ -1107,6 +1137,48 @@ class Stories_Controller extends DependencyInjectedRestTestCase {
 		$this->assertErrorResponse( 'rest_cannot_create', $response, 403 );
 	}
 
+
+	/**
+	 * @covers ::create_item
+	 */
+	public function test_create_item_duplicate_id_no_permission_for_password_protected_post(): void {
+		$this->controller->register_routes();
+
+		$unsanitized_content    = file_get_contents( WEB_STORIES_TEST_DATA_DIR . '/story_post_content.html' );
+		$unsanitized_story_data = wp_json_encode( [ 'pages' => [] ] );
+		$original_id            = self::factory()->post->create(
+			[
+				'post_type'             => Story_Post_Type::POST_TYPE_SLUG,
+				'post_content'          => $unsanitized_content,
+				'post_title'            => 'Example title',
+				'post_excerpt'          => 'Example excerpt',
+				'post_author'           => self::$user_id,
+				'post_status'           => 'publish',
+				'post_password'         => 'Top Secret',
+				'post_content_filtered' => $unsanitized_story_data,
+			]
+		);
+
+		$attachment_id = self::factory()->attachment->create_upload_object( WEB_STORIES_TEST_DATA_DIR . '/attachment.jpg' );
+
+		$this->assertNotWPError( $attachment_id );
+
+		set_post_thumbnail( $original_id, $attachment_id );
+
+		wp_set_current_user( self::$author_id );
+		$this->kses_int();
+
+		$request = new WP_REST_Request( \WP_REST_Server::CREATABLE, '/web-stories/v1/web-story' );
+		$request->set_body_params(
+			[
+				'original_id' => $original_id,
+			]
+		);
+
+		$response = rest_get_server()->dispatch( $request );
+		$this->assertErrorResponse( 'rest_cannot_create', $response, 403 );
+	}
+
 	/**
 	 * @covers ::update_item
 	 * @covers \Google\Web_Stories\REST_API\Stories_Base_Controller::update_item

programming_language: PHP