From 35c0ab3c7463c3c9bd1d3ea35a5a5d63f5cfda8a Mon Sep 17 00:00:00 2001 From: Jb Audras Date: Tue, 16 May 2023 14:26:01 +0000 Subject: [PATCH] Media: Prevent CSRF setting attachment thumbnails. Props martinkrcho, paulkevan, peterwilsoncc, xknown, peterwilsoncc. git-svn-id: https://develop.svn.wordpress.org/trunk@55764 602fd350-edb4-49c9-b593-d223f7449a82 --- src/js/media/views/frame/video-details.js | 1 + src/wp-admin/includes/ajax-actions.php | 4 + src/wp-includes/media.php | 3 +- .../ajax/wpAjaxSendAttachmentToEditor.php | 91 +++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/js/media/views/frame/video-details.js b/src/js/media/views/frame/video-details.js index 2dbb22f774..6a0d604202 100644 --- a/src/js/media/views/frame/video-details.js +++ b/src/js/media/views/frame/video-details.js @@ -106,6 +106,7 @@ VideoDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.VideoDeta wp.ajax.send( 'set-attachment-thumbnail', { data : { + _ajax_nonce: wp.media.view.settings.nonce.setAttachmentThumbnail, urls: urls, thumbnail_id: attachment.get( 'id' ) } diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index c33d6905f4..eca37d5e17 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/wp-admin/includes/ajax-actions.php @@ -2771,6 +2771,10 @@ function wp_ajax_set_attachment_thumbnail() { wp_send_json_error(); } + if ( false === check_ajax_referer( 'set-attachment-thumbnail', '_ajax_nonce', false ) ) { + wp_send_json_error(); + } + $post_ids = array(); // For each URL, try to find its corresponding post ID. foreach ( $_POST['urls'] as $url ) { diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 0d22ada14e..1ff05ce072 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -4528,7 +4528,8 @@ function wp_enqueue_media( $args = array() ) { /** This filter is documented in wp-admin/includes/media.php */ 'captions' => ! apply_filters( 'disable_captions', '' ), 'nonce' => array( - 'sendToEditor' => wp_create_nonce( 'media-send-to-editor' ), + 'sendToEditor' => wp_create_nonce( 'media-send-to-editor' ), + 'setAttachmentThumbnail' => wp_create_nonce( 'set-attachment-thumbnail' ), ), 'post' => array( 'id' => 0, diff --git a/tests/phpunit/tests/ajax/wpAjaxSendAttachmentToEditor.php b/tests/phpunit/tests/ajax/wpAjaxSendAttachmentToEditor.php index 277ce3d299..29066ff287 100644 --- a/tests/phpunit/tests/ajax/wpAjaxSendAttachmentToEditor.php +++ b/tests/phpunit/tests/ajax/wpAjaxSendAttachmentToEditor.php @@ -101,4 +101,95 @@ class Tests_Ajax_wpAjaxSendAttachmentToEditor extends WP_Ajax_UnitTestCase { $this->assertTrue( $response['success'] ); $this->assertSame( $expected, $response['data'] ); } + + public function test_wp_ajax_set_attachment_thumbnail_success() { + // Become an administrator. + $post = $_POST; + $user_id = self::factory()->user->create( + array( + 'role' => 'administrator', + 'user_login' => 'user_36578_administrator', + 'user_email' => 'user_36578_administrator@example.com', + ) + ); + wp_set_current_user( $user_id ); + $_POST = array_merge( $_POST, $post ); + + // Upload the attachment itself. + $filename = DIR_TESTDATA . '/uploads/small-audio.mp3'; + $contents = file_get_contents( $filename ); + + $upload = wp_upload_bits( wp_basename( $filename ), null, $contents ); + $attachment = $this->_make_attachment( $upload ); + + // Upload the thumbnail. + $filename = DIR_TESTDATA . '/images/waffles.jpg'; + $contents = file_get_contents( $filename ); + + $upload = wp_upload_bits( wp_basename( $filename ), null, $contents ); + $thumbnail = $this->_make_attachment( $upload ); + + // Set up a default request. + $_POST['_ajax_nonce'] = wp_create_nonce( 'set-attachment-thumbnail' ); + $_POST['thumbnail_id'] = $thumbnail; + $_POST['urls'] = array( wp_get_attachment_url( $attachment ) ); + + // Make the request. + try { + $this->_handleAjax( 'set-attachment-thumbnail' ); + } catch ( WPAjaxDieContinueException $e ) { + unset( $e ); + } + + // Get the response. + $response = json_decode( $this->_last_response, true ); + + // Ensure everything is correct. + $this->assertTrue( $response['success'] ); + } + + public function test_wp_ajax_set_attachment_thumbnail_missing_nonce() { + // Become an administrator. + $post = $_POST; + $user_id = self::factory()->user->create( + array( + 'role' => 'administrator', + 'user_login' => 'user_36578_administrator', + 'user_email' => 'user_36578_administrator@example.com', + ) + ); + wp_set_current_user( $user_id ); + $_POST = array_merge( $_POST, $post ); + + // Upload the attachment itself. + $filename = DIR_TESTDATA . '/uploads/small-audio.mp3'; + $contents = file_get_contents( $filename ); + + $upload = wp_upload_bits( wp_basename( $filename ), null, $contents ); + $attachment = $this->_make_attachment( $upload ); + + // Upload the thumbnail. + $filename = DIR_TESTDATA . '/images/waffles.jpg'; + $contents = file_get_contents( $filename ); + + $upload = wp_upload_bits( wp_basename( $filename ), null, $contents ); + $thumbnail = $this->_make_attachment( $upload ); + + // Set up a default request. + $_POST['thumbnail_id'] = $thumbnail; + $_POST['urls'] = array( wp_get_attachment_url( $attachment ) ); + + // Make the request. + try { + $this->_handleAjax( 'set-attachment-thumbnail' ); + } catch ( WPAjaxDieContinueException $e ) { + unset( $e ); + } + + // Get the response. + $response = json_decode( $this->_last_response, true ); + + // Check that success is false without sending nonce. + $this->assertFalse( $response['success'] ); + } }