Skip to content

Commit 923c683

Browse files
committed
Grouped backports to the 4.5 branch.
- Comments: Prevent users who can not see a post from seeing comments on it. - Shortcodes: Restrict media shortcode ajax to certain type. - REST API: Ensure no-cache headers are sent when methods are overridden. - Prevent unintended behavior when certain objects are unserialized. Merges [56834], [56835], [56836], and [56838] to the 4.5 branch. Props xknown, jorbin, joehoyle, timothyblynjacobs, peterwilsoncc, ehtis, tykoted, antpb, rmccue. git-svn-id: https://develop.svn.wordpress.org/branches/4.5@56857 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 83de016 commit 923c683

File tree

9 files changed

+173
-20
lines changed

9 files changed

+173
-20
lines changed

src/wp-admin/includes/ajax-actions.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2987,13 +2987,29 @@ function wp_ajax_parse_media_shortcode() {
29872987

29882988
$shortcode = wp_unslash( $_POST['shortcode'] );
29892989

2990+
// Only process previews for media related shortcodes:
2991+
$found_shortcodes = get_shortcode_tags_in_content( $shortcode );
2992+
$media_shortcodes = array(
2993+
'audio',
2994+
'embed',
2995+
'playlist',
2996+
'video',
2997+
'gallery',
2998+
);
2999+
3000+
$other_shortcodes = array_diff( $found_shortcodes, $media_shortcodes );
3001+
3002+
if ( ! empty( $other_shortcodes ) ) {
3003+
wp_send_json_error();
3004+
}
3005+
29903006
if ( ! empty( $_POST['post_ID'] ) ) {
29913007
$post = get_post( (int) $_POST['post_ID'] );
29923008
}
29933009

29943010
// the embed shortcode requires a post
29953011
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
2996-
if ( 'embed' === $shortcode ) {
3012+
if ( in_array( 'embed', $found_shortcodes, true ) ) {
29973013
wp_send_json_error();
29983014
}
29993015
} else {

src/wp-admin/includes/class-wp-comments-list-table.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,19 @@ public function single_row( $item ) {
495495
}
496496
$this->user_can = current_user_can( 'edit_comment', $comment->comment_ID );
497497

498+
$edit_post_cap = $post ? 'edit_post' : 'edit_posts';
499+
if (
500+
current_user_can( $edit_post_cap, $comment->comment_post_ID ) ||
501+
(
502+
empty( $post->post_password ) &&
503+
current_user_can( 'read_post', $comment->comment_post_ID )
504+
)
505+
) {
506+
// The user has access to the post
507+
} else {
508+
return false;
509+
}
510+
498511
echo "<tr id='comment-$comment->comment_ID' class='$the_comment_class'>";
499512
$this->single_row_columns( $comment );
500513
echo "</tr>\n";

src/wp-admin/includes/class-wp-list-table.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,20 @@ protected function comments_bubble( $post_id, $pending_comments ) {
655655
$approved_phrase = sprintf( _n( '%s approved comment', '%s approved comments', $approved_comments ), $approved_comments_number );
656656
$pending_phrase = sprintf( _n( '%s pending comment', '%s pending comments', $pending_comments ), $pending_comments_number );
657657

658-
// No comments at all.
658+
$post_object = get_post( $post_id );
659+
$edit_post_cap = $post_object ? 'edit_post' : 'edit_posts';
660+
if (
661+
current_user_can( $edit_post_cap, $post_id ) ||
662+
(
663+
empty( $post_object->post_password ) &&
664+
current_user_can( 'read_post', $post_id )
665+
)
666+
) {
667+
// The user has access to the post and thus can see comments
668+
} else {
669+
return false;
670+
}
671+
659672
if ( ! $approved_comments && ! $pending_comments ) {
660673
printf( '<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
661674
__( 'No comments' )

src/wp-admin/includes/dashboard.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -906,8 +906,18 @@ function wp_dashboard_recent_comments( $total_items = 5 ) {
906906
echo '<h3>' . __( 'Recent Comments' ) . '</h3>';
907907

908908
echo '<ul id="the-comment-list" data-wp-lists="list:comment">';
909-
foreach ( $comments as $comment )
910-
_wp_dashboard_recent_comments_row( $comment );
909+
foreach ( $comments as $comment ) {
910+
$comment_post = get_post( $comment->comment_post_ID );
911+
if (
912+
current_user_can( 'edit_post', $comment->comment_post_ID ) ||
913+
(
914+
empty( $comment_post->post_password ) &&
915+
current_user_can( 'read_post', $comment->comment_post_ID )
916+
)
917+
) {
918+
_wp_dashboard_recent_comments_row( $comment );
919+
}
920+
}
911921
echo '</ul>';
912922

913923
if ( current_user_can( 'edit_posts' ) ) {

src/wp-includes/class-wp-theme.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,28 @@ public function parent() {
527527
return isset( $this->parent ) ? $this->parent : false;
528528
}
529529

530+
/**
531+
* Perform reinitialization tasks.
532+
*
533+
* Prevents a callback from being injected during unserialization of an object.
534+
*
535+
* @return void
536+
*/
537+
public function __wakeup() {
538+
if ( $this->parent && ! $this->parent instanceof self ) {
539+
throw new UnexpectedValueException();
540+
}
541+
if ( $this->headers && ! is_array( $this->headers ) ) {
542+
throw new UnexpectedValueException();
543+
}
544+
foreach ( $this->headers as $value ) {
545+
if ( ! is_string( $value ) ) {
546+
throw new UnexpectedValueException();
547+
}
548+
}
549+
$this->headers_sanitized = array();
550+
}
551+
530552
/**
531553
* Adds theme data to cache.
532554
*
@@ -1371,4 +1393,16 @@ private static function _name_sort_i18n( $a, $b ) {
13711393
// Don't mark up; Do translate.
13721394
return strnatcasecmp( $a->display( 'Name', false, true ), $b->display( 'Name', false, true ) );
13731395
}
1396+
1397+
private static function _check_headers_property_has_correct_type( $headers ) {
1398+
if ( ! is_array( $headers ) ) {
1399+
return false;
1400+
}
1401+
foreach ( $headers as $key => $value ) {
1402+
if ( ! is_string( $key ) || ! is_string( $value ) ) {
1403+
return false;
1404+
}
1405+
}
1406+
return true;
1407+
}
13741408
}

src/wp-includes/media.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,11 +1633,24 @@ function gallery_shortcode( $attr ) {
16331633
$attachments[$val->ID] = $_attachments[$key];
16341634
}
16351635
} elseif ( ! empty( $atts['exclude'] ) ) {
1636+
$post_parent_id = $id;
16361637
$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'] ) );
16371638
} else {
1639+
$post_parent_id = $id;
16381640
$attachments = get_children( array( 'post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $atts['order'], 'orderby' => $atts['orderby'] ) );
16391641
}
16401642

1643+
if ( ! empty( $post_parent_id ) ) {
1644+
$post_parent = get_post( $post_parent_id );
1645+
1646+
// terminate the shortcode execution if user cannot read the post or password-protected
1647+
if (
1648+
( ! is_post_publicly_viewable( $post_parent->ID ) && ! current_user_can( 'read_post', $post_parent->ID ) )
1649+
|| post_password_required( $post_parent ) ) {
1650+
return '';
1651+
}
1652+
}
1653+
16411654
if ( empty( $attachments ) ) {
16421655
return '';
16431656
}
@@ -1937,6 +1950,15 @@ function wp_playlist_shortcode( $attr ) {
19371950
$attachments = get_children( $args );
19381951
}
19391952

1953+
if ( ! empty( $args['post_parent'] ) ) {
1954+
$post_parent = get_post( $id );
1955+
1956+
// terminate the shortcode execution if user cannot read the post or password-protected
1957+
if ( ! current_user_can( 'read_post', $post_parent->ID ) || post_password_required( $post_parent ) ) {
1958+
return '';
1959+
}
1960+
}
1961+
19401962
if ( empty( $attachments ) ) {
19411963
return '';
19421964
}

src/wp-includes/rest-api.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ function rest_cookie_check_errors( $result ) {
592592
$result = wp_verify_nonce( $nonce, 'wp_rest' );
593593

594594
if ( ! $result ) {
595+
add_filter( 'rest_send_nocache_headers', '__return_true', 20 );
595596
return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
596597
}
597598

src/wp-includes/rest-api/class-wp-rest-server.php

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -237,21 +237,7 @@ public function serve_request( $path = null ) {
237237
$this->send_header( 'Access-Control-Allow-Headers', 'Authorization' );
238238

239239
/**
240-
* Send nocache headers on authenticated requests.
241-
*
242-
* @since 4.4.0
243-
*
244-
* @param bool $rest_send_nocache_headers Whether to send no-cache headers.
245-
*/
246-
$send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );
247-
if ( $send_no_cache_headers ) {
248-
foreach ( wp_get_nocache_headers() as $header => $header_value ) {
249-
$this->send_header( $header, $header_value );
250-
}
251-
}
252-
253-
/**
254-
* Filter whether the REST API is enabled.
240+
* Filters whether the REST API is enabled.
255241
*
256242
* @since 4.4.0
257243
*
@@ -314,10 +300,12 @@ public function serve_request( $path = null ) {
314300
* $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
315301
* header.
316302
*/
303+
$method_overridden = false;
317304
if ( isset( $_GET['_method'] ) ) {
318305
$request->set_method( $_GET['_method'] );
319306
} elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
320307
$request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] );
308+
$method_overridden = true;
321309
}
322310

323311
$result = $this->check_authentication();
@@ -376,6 +364,24 @@ public function serve_request( $path = null ) {
376364
*/
377365
$served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this );
378366

367+
/**
368+
* Filters whether to send nocache headers on a REST API request.
369+
*
370+
* @since 4.4.0
371+
* @since 6.x.x Moved the block to catch the filter added on rest_cookie_check_errors() from rest-api.php
372+
*
373+
* @param bool $rest_send_nocache_headers Whether to send no-cache headers.
374+
*/
375+
$send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );
376+
377+
// send no cache headers if the $send_no_cache_headers is true
378+
// OR if the HTTP_X_HTTP_METHOD_OVERRIDE is used but resulted a 4xx response code.
379+
if ( $send_no_cache_headers || ( true === $method_overridden && strpos( $code, '4' ) === 0 ) ) {
380+
foreach ( wp_get_nocache_headers() as $header => $header_value ) {
381+
$this->send_header( $header, $header_value );
382+
}
383+
}
384+
379385
if ( ! $served ) {
380386
if ( 'HEAD' === $request->get_method() ) {
381387
return null;

src/wp-includes/shortcodes.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,45 @@ function has_shortcode( $content, $tag ) {
185185
}
186186

187187
/**
188-
* Search content for shortcodes and filter shortcodes through their hooks.
188+
* Returns a list of registered shortcode names found in the given content.
189+
*
190+
* Example usage:
191+
*
192+
* get_shortcode_tags_in_content( '[audio src="file.mp3"][/audio] [foo] [gallery ids="1,2,3"]' );
193+
* // array( 'audio', 'gallery' )
194+
*
195+
* @since 6.3.2
196+
*
197+
* @param string $content The content to check.
198+
* @return string[] An array of registered shortcode names found in the content.
199+
*/
200+
function get_shortcode_tags_in_content( $content ) {
201+
if ( false === strpos( $content, '[' ) ) {
202+
return array();
203+
}
204+
205+
preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER );
206+
if ( empty( $matches ) ) {
207+
return array();
208+
}
209+
210+
$tags = array();
211+
foreach ( $matches as $shortcode ) {
212+
$tags[] = $shortcode[2];
213+
214+
if ( ! empty( $shortcode[5] ) ) {
215+
$deep_tags = get_shortcode_tags_in_content( $shortcode[5] );
216+
if ( ! empty( $deep_tags ) ) {
217+
$tags = array_merge( $tags, $deep_tags );
218+
}
219+
}
220+
}
221+
222+
return $tags;
223+
}
224+
225+
/**
226+
* Searches content for shortcodes and filter shortcodes through their hooks.
189227
*
190228
* If there are no shortcode tags defined, then the content will be returned
191229
* without any filtering. This might cause issues when plugins are disabled but

0 commit comments

Comments
 (0)