From 9ef796ac1fd868d689202f0637a40364d79560bf Mon Sep 17 00:00:00 2001 From: davidbaumwald Date: Thu, 12 Oct 2023 14:22:19 +0000 Subject: [PATCH] Grouped backports to the 4.1 branch. - Comments: Prevent users who can not see a post from seeing comments on it. - Shortcodes: Restrict ajax handler for media shortcode. - Prevent unintended behavior when certain objects are unserialized. Merges [56835], [56836], and [56838] to the 4.1 branch. Props xknown, jorbin, joehoyle, peterwilsoncc, ehtis, tykoted, antpb. Built from https://develop.svn.wordpress.org/branches/4.1@56850 git-svn-id: http://core.svn.wordpress.org/branches/4.1@56362 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/includes/ajax-actions.php | 36 +++++++++++++++-- .../includes/class-wp-comments-list-table.php | 13 ++++++ wp-admin/includes/class-wp-list-table.php | 14 +++++++ wp-admin/includes/dashboard.php | 14 ++++++- wp-includes/class-wp-theme.php | 34 ++++++++++++++++ wp-includes/media.php | 22 ++++++++++ wp-includes/shortcodes.php | 40 ++++++++++++++++++- 7 files changed, 166 insertions(+), 7 deletions(-) diff --git a/wp-admin/includes/ajax-actions.php b/wp-admin/includes/ajax-actions.php index ac889a0fba..e198e8f75d 100644 --- a/wp-admin/includes/ajax-actions.php +++ b/wp-admin/includes/ajax-actions.php @@ -828,7 +828,7 @@ function wp_ajax_get_tagcloud() { if ( ! $tax ) { wp_die( 0 ); } - + if ( ! current_user_can( $tax->cap->assign_terms ) ) { wp_die( -1 ); } @@ -2767,8 +2767,36 @@ function wp_ajax_parse_media_shortcode() { wp_send_json_error(); } - setup_postdata( $post ); - $shortcode = do_shortcode( wp_unslash( $_POST['shortcode'] ) ); + $shortcode = wp_unslash( $_POST['shortcode'] ); + + // Only process previews for media related shortcodes: + $found_shortcodes = get_shortcode_tags_in_content( $shortcode ); + $media_shortcodes = array( + 'audio', + 'embed', + 'playlist', + 'video', + 'gallery', + ); + + $other_shortcodes = array_diff( $found_shortcodes, $media_shortcodes ); + + if ( ! empty( $other_shortcodes ) ) { + wp_send_json_error(); + } + + if ( ! empty( $_POST['post_ID'] ) ) { + $post = get_post( (int) $_POST['post_ID'] ); + } + + // the embed shortcode requires a post + if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) { + if ( in_array( 'embed', $found_shortcodes, true ) ) { + wp_send_json_error(); + } + } else { + setup_postdata( $post ); + } if ( empty( $shortcode ) ) { wp_send_json_error( array( @@ -2835,7 +2863,7 @@ function wp_ajax_destroy_sessions() { $message = __( 'You are now logged out everywhere else.' ); } else { $sessions->destroy_all(); - /* translators: 1: User's display name. */ + /* translators: 1: User's display name. */ $message = sprintf( __( '%s has been logged out.' ), $user->display_name ); } diff --git a/wp-admin/includes/class-wp-comments-list-table.php b/wp-admin/includes/class-wp-comments-list-table.php index 35a193f1ce..8cf724f487 100644 --- a/wp-admin/includes/class-wp-comments-list-table.php +++ b/wp-admin/includes/class-wp-comments-list-table.php @@ -362,6 +362,19 @@ class WP_Comments_List_Table extends WP_List_Table { $this->user_can = current_user_can( 'edit_comment', $comment->comment_ID ); + $edit_post_cap = $post ? 'edit_post' : 'edit_posts'; + if ( + current_user_can( $edit_post_cap, $comment->comment_post_ID ) || + ( + empty( $post->post_password ) && + current_user_can( 'read_post', $comment->comment_post_ID ) + ) + ) { + // The user has access to the post + } else { + return false; + } + echo ""; $this->single_row_columns( $comment ); echo "\n"; diff --git a/wp-admin/includes/class-wp-list-table.php b/wp-admin/includes/class-wp-list-table.php index a90b4b8c67..a0b2861f6b 100644 --- a/wp-admin/includes/class-wp-list-table.php +++ b/wp-admin/includes/class-wp-list-table.php @@ -573,6 +573,20 @@ class WP_List_Table { protected function comments_bubble( $post_id, $pending_comments ) { $pending_phrase = sprintf( __( '%s pending' ), number_format( $pending_comments ) ); + $post_object = get_post( $post_id ); + $edit_post_cap = $post_object ? 'edit_post' : 'edit_posts'; + if ( + current_user_can( $edit_post_cap, $post_id ) || + ( + empty( $post_object->post_password ) && + current_user_can( 'read_post', $post_id ) + ) + ) { + // The user has access to the post and thus can see comments + } else { + return false; + } + if ( $pending_comments ) echo ''; diff --git a/wp-admin/includes/dashboard.php b/wp-admin/includes/dashboard.php index 591ed933bd..ed45a155fb 100644 --- a/wp-admin/includes/dashboard.php +++ b/wp-admin/includes/dashboard.php @@ -778,8 +778,18 @@ function wp_dashboard_recent_comments( $total_items = 5 ) { echo '

' . __( 'Comments' ) . '

'; echo '
'; - foreach ( $comments as $comment ) - _wp_dashboard_recent_comments_row( $comment ); + foreach ( $comments as $comment ) { + $comment_post = get_post( $comment->comment_post_ID ); + if ( + current_user_can( 'edit_post', $comment->comment_post_ID ) || + ( + empty( $comment_post->post_password ) && + current_user_can( 'read_post', $comment->comment_post_ID ) + ) + ) { + _wp_dashboard_recent_comments_row( $comment ); + } + } echo '
'; if ( current_user_can('edit_posts') ) diff --git a/wp-includes/class-wp-theme.php b/wp-includes/class-wp-theme.php index fd73f6ff8f..43b8cef104 100644 --- a/wp-includes/class-wp-theme.php +++ b/wp-includes/class-wp-theme.php @@ -476,6 +476,28 @@ final class WP_Theme implements ArrayAccess { return isset( $this->parent ) ? $this->parent : false; } + /** + * Perform reinitialization tasks. + * + * Prevents a callback from being injected during unserialization of an object. + * + * @return void + */ + public function __wakeup() { + if ( $this->parent && ! $this->parent instanceof self ) { + throw new UnexpectedValueException(); + } + if ( $this->headers && ! is_array( $this->headers ) ) { + throw new UnexpectedValueException(); + } + foreach ( $this->headers as $value ) { + if ( ! is_string( $value ) ) { + throw new UnexpectedValueException(); + } + } + $this->headers_sanitized = array(); + } + /** * Adds theme data to cache. * @@ -1232,4 +1254,16 @@ final class WP_Theme implements ArrayAccess { // Don't mark up; Do translate. return strnatcasecmp( $a->display( 'Name', false, true ), $b->display( 'Name', false, true ) ); } + + private static function _check_headers_property_has_correct_type( $headers ) { + if ( ! is_array( $headers ) ) { + return false; + } + foreach ( $headers as $key => $value ) { + if ( ! is_string( $key ) || ! is_string( $value ) ) { + return false; + } + } + return true; + } } diff --git a/wp-includes/media.php b/wp-includes/media.php index 81e2f59b35..83eb541940 100644 --- a/wp-includes/media.php +++ b/wp-includes/media.php @@ -974,11 +974,24 @@ function gallery_shortcode( $attr ) { $attachments[$val->ID] = $_attachments[$key]; } } elseif ( ! empty( $atts['exclude'] ) ) { + $post_parent_id = $id; $attachments = get_children( array( 'post_parent' => $id, 'exclude' => $atts['exclude'], 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $atts['order'], 'orderby' => $atts['orderby'] ) ); } else { + $post_parent_id = $id; $attachments = get_children( array( 'post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $atts['order'], 'orderby' => $atts['orderby'] ) ); } + if ( ! empty( $post_parent_id ) ) { + $post_parent = get_post( $post_parent_id ); + + // terminate the shortcode execution if user cannot read the post or password-protected + if ( + ( ! is_post_publicly_viewable( $post_parent->ID ) && ! current_user_can( 'read_post', $post_parent->ID ) ) + || post_password_required( $post_parent ) ) { + return ''; + } + } + if ( empty( $attachments ) ) { return ''; } @@ -1268,6 +1281,15 @@ function wp_playlist_shortcode( $attr ) { $attachments = get_children( $args ); } + if ( ! empty( $args['post_parent'] ) ) { + $post_parent = get_post( $id ); + + // terminate the shortcode execution if user cannot read the post or password-protected + if ( ! current_user_can( 'read_post', $post_parent->ID ) || post_password_required( $post_parent ) ) { + return ''; + } + } + if ( empty( $attachments ) ) { return ''; } diff --git a/wp-includes/shortcodes.php b/wp-includes/shortcodes.php index 091f4a5f8f..c0d429068c 100644 --- a/wp-includes/shortcodes.php +++ b/wp-includes/shortcodes.php @@ -170,7 +170,45 @@ function has_shortcode( $content, $tag ) { } /** - * Search content for shortcodes and filter shortcodes through their hooks. + * Returns a list of registered shortcode names found in the given content. + * + * Example usage: + * + * get_shortcode_tags_in_content( '[audio src="file.mp3"][/audio] [foo] [gallery ids="1,2,3"]' ); + * // array( 'audio', 'gallery' ) + * + * @since 6.3.2 + * + * @param string $content The content to check. + * @return string[] An array of registered shortcode names found in the content. + */ +function get_shortcode_tags_in_content( $content ) { + if ( false === strpos( $content, '[' ) ) { + return array(); + } + + preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER ); + if ( empty( $matches ) ) { + return array(); + } + + $tags = array(); + foreach ( $matches as $shortcode ) { + $tags[] = $shortcode[2]; + + if ( ! empty( $shortcode[5] ) ) { + $deep_tags = get_shortcode_tags_in_content( $shortcode[5] ); + if ( ! empty( $deep_tags ) ) { + $tags = array_merge( $tags, $deep_tags ); + } + } + } + + return $tags; +} + +/** + * Searches content for shortcodes and filter shortcodes through their hooks. * * If there are no shortcode tags defined, then the content will be returned * without any filtering. This might cause issues when plugins are disabled but