From b22615ae9b1a07a21d6e47f7bed440570a235d66 Mon Sep 17 00:00:00 2001 From: Jake Spurlock Date: Thu, 29 Oct 2020 18:48:02 +0000 Subject: [PATCH] General: WordPress updates * XML-RPC: Improve error messages for unprivileged users. * External Libraries: Disable deserialization in Requests_Utility_FilteredIterator * Embeds: Disable embeds on deactivated Multisite sites. * Coding standards: Modify escaping functions to avoid potential false positives. * XML-RPC: Return error message if attachment ID is incorrect. * Upgrade/install: Improve logic check when determining installation status. * Meta: Sanitize meta key before checking protection status. * Themes: Ensure that only privileged users can set a background image when a theme is using the deprecated custom background page. Brings the changes from [49380,49382-49388] to the 5.1 branch. Props xknown, zieladam, peterwilsoncc, whyisjake, desrosj, dd32. git-svn-id: https://develop.svn.wordpress.org/branches/5.1@49395 602fd350-edb4-49c9-b593-d223f7449a82 --- src/js/_enqueues/admin/custom-background.js | 2 + src/js/_enqueues/deprecated/media-gallery.js | 4 +- src/wp-admin/admin-header.php | 14 ++--- src/wp-admin/custom-background.php | 2 + src/wp-admin/custom-header.php | 2 +- src/wp-admin/includes/media.php | 2 +- src/wp-admin/includes/ms.php | 2 +- src/wp-admin/includes/template.php | 12 ++-- src/wp-admin/media-new.php | 4 +- src/wp-admin/network/site-users.php | 2 +- .../Requests/Utility/FilteredIterator.php | 13 +++++ src/wp-includes/class-wp-xmlrpc-server.php | 18 +++++- src/wp-includes/embed.php | 7 ++- src/wp-includes/functions.php | 3 +- src/wp-includes/meta.php | 5 +- tests/phpunit/tests/functions.php | 1 - tests/phpunit/tests/meta/isProtectedMeta.php | 55 +++++++++++++++++++ tests/phpunit/tests/multisite/site.php | 30 ++++++++++ 18 files changed, 152 insertions(+), 26 deletions(-) create mode 100644 tests/phpunit/tests/meta/isProtectedMeta.php diff --git a/src/js/_enqueues/admin/custom-background.js b/src/js/_enqueues/admin/custom-background.js index a948e780a4..d53b98d33f 100644 --- a/src/js/_enqueues/admin/custom-background.js +++ b/src/js/_enqueues/admin/custom-background.js @@ -126,11 +126,13 @@ frame.on( 'select', function() { // Grab the selected attachment. var attachment = frame.state().get('selection').first(); + var nonceValue = $( '#_wpnonce' ).val() || ''; // Run an AJAX request to set the background image. $.post( ajaxurl, { action: 'set-background-image', attachment_id: attachment.id, + _ajax_nonce: nonceValue, size: 'full' }).done( function() { // When the request completes, reload the window. diff --git a/src/js/_enqueues/deprecated/media-gallery.js b/src/js/_enqueues/deprecated/media-gallery.js index 725d2beb7c..eca79c0b76 100644 --- a/src/js/_enqueues/deprecated/media-gallery.js +++ b/src/js/_enqueues/deprecated/media-gallery.js @@ -11,7 +11,7 @@ jQuery(function($) { * Adds a click event handler to the element with a 'wp-gallery' class. */ $( 'body' ).bind( 'click.wp-gallery', function(e) { - var target = $( e.target ), id, img_size; + var target = $( e.target ), id, img_size, nonceValue; if ( target.hasClass( 'wp-set-header' ) ) { // Opens the image to preview it full size. @@ -21,6 +21,7 @@ jQuery(function($) { // Sets the image as background of the theme. id = target.data( 'attachment-id' ); img_size = $( 'input[name="attachments[' + id + '][image-size]"]:checked').val(); + nonceValue = $( '#_wpnonce' ).val() && ''; /** * This AJAX action has been deprecated since 3.5.0, see custom-background.php @@ -28,6 +29,7 @@ jQuery(function($) { jQuery.post(ajaxurl, { action: 'set-background-image', attachment_id: id, + _ajax_nonce: nonceValue, size: img_size }, function() { var win = window.dialogArguments || opener || parent || top; diff --git a/src/wp-admin/admin-header.php b/src/wp-admin/admin-header.php index 5f0fb75916..0a2b0b9d3f 100644 --- a/src/wp-admin/admin-header.php +++ b/src/wp-admin/admin-header.php @@ -77,13 +77,13 @@ wp_enqueue_script( 'svg-painter' ); $admin_body_class = preg_replace( '/[^a-z0-9_-]+/i', '-', $hook_suffix ); ?> diff --git a/src/wp-admin/custom-background.php b/src/wp-admin/custom-background.php index e5151015ec..7d91333c22 100644 --- a/src/wp-admin/custom-background.php +++ b/src/wp-admin/custom-background.php @@ -574,6 +574,8 @@ class Custom_Background { * @deprecated 3.5.0 */ public function wp_set_background_image() { + check_ajax_referer( 'custom-background' ); + if ( ! current_user_can( 'edit_theme_options' ) || ! isset( $_POST['attachment_id'] ) ) { exit; } diff --git a/src/wp-admin/custom-header.php b/src/wp-admin/custom-header.php index 127a46a6a7..a502e47063 100644 --- a/src/wp-admin/custom-header.php +++ b/src/wp-admin/custom-header.php @@ -338,7 +338,7 @@ class Custom_Image_Header { ?> addLoadEvent = function(func){if(typeof jQuery!="undefined")jQuery(document).ready(func);else if(typeof wpOnload!='function'){wpOnload=func;}else{var oldonload=wpOnload;wpOnload=function(){oldonload();func();}}}; function tb_close(){var win=window.dialogArguments||opener||parent||top;win.tb_remove();} -var ajaxurl = '', - pagenow = 'id; ?>', - typenow = 'post_type; ?>', - adminpage = '', - thousandsSeparator = 'number_format['thousands_sep'] ); ?>', - decimalPoint = 'number_format['decimal_point'] ); ?>', +var ajaxurl = '', + pagenow = 'id ); ?>', + typenow = 'post_type ); ?>', + adminpage = '', + thousandsSeparator = 'number_format['thousands_sep'] ); ?>', + decimalPoint = 'number_format['decimal_point'] ); ?>', isRtl = ; - +
diff --git a/src/wp-admin/network/site-users.php b/src/wp-admin/network/site-users.php index 5689014f2d..545aba0ce6 100644 --- a/src/wp-admin/network/site-users.php +++ b/src/wp-admin/network/site-users.php @@ -217,7 +217,7 @@ if ( ! wp_is_large_network( 'users' ) && apply_filters( 'show_network_site_users require( ABSPATH . 'wp-admin/admin-header.php' ); ?> diff --git a/src/wp-includes/Requests/Utility/FilteredIterator.php b/src/wp-includes/Requests/Utility/FilteredIterator.php index 76a29e7228..c657433248 100644 --- a/src/wp-includes/Requests/Utility/FilteredIterator.php +++ b/src/wp-includes/Requests/Utility/FilteredIterator.php @@ -42,4 +42,17 @@ class Requests_Utility_FilteredIterator extends ArrayIterator { $value = call_user_func($this->callback, $value); return $value; } + + /** + * @inheritdoc + */ + public function unserialize( $serialized ) { + } + + /** + * @inheritdoc + */ + public function __unserialize( $serialized ) { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound + $this->unserialize( $serialized ); + } } diff --git a/src/wp-includes/class-wp-xmlrpc-server.php b/src/wp-includes/class-wp-xmlrpc-server.php index 923a98884f..85e0fac740 100644 --- a/src/wp-includes/class-wp-xmlrpc-server.php +++ b/src/wp-includes/class-wp-xmlrpc-server.php @@ -3829,6 +3829,21 @@ class wp_xmlrpc_server extends IXR_Server { return new IXR_Error( 403, __( 'Comment is required.' ) ); } + if ( + 'publish' === get_post_status( $post_id ) && + ! current_user_can( 'edit_post', $post_id ) && + post_password_required( $post_id ) + ) { + return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) ); + } + + if ( + 'private' === get_post_status( $post_id ) && + ! current_user_can( 'read_post', $post_id ) + ) { + return new IXR_Error( 403, __( 'Sorry, you are not allowed to comment on this post.' ) ); + } + $comment = array( 'comment_post_ID' => $post_id, 'comment_content' => $content_struct['content'], @@ -4233,7 +4248,8 @@ class wp_xmlrpc_server extends IXR_Server { /** This action is documented in wp-includes/class-wp-xmlrpc-server.php */ do_action( 'xmlrpc_call', 'wp.getMediaItem' ); - if ( ! $attachment = get_post( $attachment_id ) ) { + $attachment = get_post( $attachment_id ); + if ( ! $attachment || 'attachment' !== $attachment->post_type ) { return new IXR_Error( 404, __( 'Invalid attachment ID.' ) ); } diff --git a/src/wp-includes/embed.php b/src/wp-includes/embed.php index 7c32513283..f6bc732800 100644 --- a/src/wp-includes/embed.php +++ b/src/wp-includes/embed.php @@ -604,7 +604,12 @@ function get_oembed_response_data_for_url( $url, $args ) { $sites = get_sites( $qv ); $site = reset( $sites ); - if ( $site && (int) $site->blog_id !== get_current_blog_id() ) { + // Do not allow embeds for deleted/archived/spam sites. + if ( ! empty( $site->deleted ) || ! empty( $site->spam ) || ! empty( $site->archived ) ) { + return false; + } + + if ( $site && get_current_blog_id() !== (int) $site->blog_id ) { switch_to_blog( $site->blog_id ); $switched_blog = true; } diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index bef6d40805..3d0eac990d 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -1566,7 +1566,8 @@ function is_blog_installed() { continue; } - if ( ! $wpdb->get_results( "DESCRIBE $table;" ) ) { + $described_table = $wpdb->get_results( "DESCRIBE $table;" ); + if ( is_array( $described_table ) && count( $described_table ) === 0 ) { continue; } diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 0942a5a0db..12c9e912bd 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -1028,8 +1028,9 @@ function _get_meta_table( $type ) { * @param string|null $meta_type Optional. Type of object metadata is for (e.g., comment, post, term, or user). * @return bool Whether the meta key is considered protected. */ -function is_protected_meta( $meta_key, $meta_type = null ) { - $protected = ( '_' == $meta_key[0] ); +function is_protected_meta( $meta_key, $meta_type = '' ) { + $sanitized_key = preg_replace( "/[^\x20-\x7E\p{L}]/", '', $meta_key ); + $protected = strlen( $sanitized_key ) > 0 && ( '_' === $sanitized_key[0] ); /** * Filters whether a meta key is considered protected. diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 88b9a46475..89185792f5 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -236,7 +236,6 @@ class Tests_Functions extends WP_UnitTestCase { $this->assertFalse( is_serialized( 'C:16:"Serialized_Class":6:{a:0:{}}' ) ); } - /** * @group add_query_arg */ diff --git a/tests/phpunit/tests/meta/isProtectedMeta.php b/tests/phpunit/tests/meta/isProtectedMeta.php new file mode 100644 index 0000000000..c204d381f5 --- /dev/null +++ b/tests/phpunit/tests/meta/isProtectedMeta.php @@ -0,0 +1,55 @@ +assertTrue( is_protected_meta( $key ) ); + } + + public function protected_data() { + $protected_keys = array( + array( '_wp_attachment' ), + ); + for ( $i = 0, $max = 31; $i < $max; $i ++ ) { + $protected_keys[] = array( chr( $i ) . '_wp_attachment' ); + } + for ( $i = 127, $max = 159; $i <= $max; $i ++ ) { + $protected_keys[] = array( chr( $i ) . '_wp_attachment' ); + } + $protected_keys[] = array( chr( 95 ) . '_wp_attachment' ); + + return $protected_keys; + } + + /** + * @dataProvider unprotected_data + */ + public function test_unprotected( $key ) { + $this->assertFalse( is_protected_meta( $key ) ); + } + + public function unprotected_data() { + $unprotected_keys = array( + array( 'singleword' ), + array( 'two_words' ), + array( 'ąŌ_not_so_protected_meta' ), + ); + + for ( $i = 32, $max = 94; $i <= $max; $i ++ ) { + $unprotected_keys[] = array( chr( $i ) . '_wp_attachment' ); + } + for ( $i = 96, $max = 126; $i <= $max; $i ++ ) { + $unprotected_keys[] = array( chr( $i ) . '_wp_attachment' ); + } + + return $unprotected_keys; + } + +} diff --git a/tests/phpunit/tests/multisite/site.php b/tests/phpunit/tests/multisite/site.php index bbe99df22d..c14f20e23b 100644 --- a/tests/phpunit/tests/multisite/site.php +++ b/tests/phpunit/tests/multisite/site.php @@ -474,6 +474,36 @@ if ( is_multisite() ) : remove_action( 'make_ham_blog', array( $this, '_action_counter_cb' ), 10 ); } + function test_content_from_spam_blog_is_not_available() { + $spam_blog_id = self::factory()->blog->create(); + switch_to_blog( $spam_blog_id ); + $post_data = array( + 'post_title' => 'Hello World!', + 'post_content' => 'Hello world content', + ); + $post_id = self::factory()->post->create( $post_data ); + $post = get_post( $post_id ); + $spam_permalink = site_url() . '/?p=' . $post->ID; + $spam_embed_url = get_post_embed_url( $post_id ); + + restore_current_blog(); + $this->assertNotEmpty( $spam_permalink ); + $this->assertEquals( $post_data['post_title'], $post->post_title ); + + update_blog_status( $spam_blog_id, 'spam', 1 ); + + $post_id = self::factory()->post->create( + array( + 'post_content' => "\n $spam_permalink \n", + ) + ); + $post = get_post( $post_id ); + $content = apply_filters( 'the_content', $post->post_content ); + + $this->assertNotContains( $post_data['post_title'], $content ); + $this->assertNotContains( "src=\"{$spam_embed_url}#?", $content ); + } + function test_update_blog_status_make_spam_blog_action() { global $test_action_counter; $test_action_counter = 0;