Skip to content

Commit dffabc7

Browse files
committed
Security: Backport the WordPress 7.1.3 security fixes to the 7.0 branch.
- Export: Ensure post IDs are integers when building a WXR export. - REST API: Require both capabilities to change a post's sticky status. - Query: Prevent exposure of comments on unviewable posts. - Posts, Post Types: Fire dynamic status transition hooks only for registered statuses and post types. - Administration: Treat contextual help tab links as selectors. - Embeds: Remove support for Imgur oEmbeds. - HTTP: Improve relative path resolution in `WP_Http::make_absolute_url()`. Merges r64127, r64128, r64129, r64130, r64131, r64132, r64133 to the 7.0 branch. Props xknown, vortfu, marcs0h, lancewillett, jeremyfelt, jorbin, peterwilsoncc, ehtis, westonruter, jonsurrell, rfaile313, johnbillion. git-svn-id: https://develop.svn.wordpress.org/branches/7.0@64141 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 21a88de commit dffabc7

7 files changed

Lines changed: 119 additions & 103 deletions

File tree

‎src/js/_enqueues/admin/common.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ $('.contextual-help-tabs').on( 'click', 'a', function(e) {
695695
$('.contextual-help-tabs .active').removeClass('active');
696696
link.parent('li').addClass('active');
697697

698-
panel = $( link.attr('href') );
698+
panel = $( document ).find( link.attr('href') );
699699

700700
// Panels.
701701
$('.help-tab-content').not( panel ).removeClass('active').hide();

‎src/wp-admin/includes/export.php‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,15 @@ function export_wp( $args = array() ) {
183183
)
184184
);
185185

186+
// Cast thumbnail IDs to integers to prevent second-order SQL injection via user-controlled meta values.
187+
$thumbnails_ids = array_filter( array_map( 'absint', $thumbnails_ids ) );
188+
186189
$additional_ids = array_merge( $additional_ids, $attachment_ids, $thumbnails_ids );
187190
}
188191

189-
// Merge the additional IDs back with the original post IDs after processing all posts
190-
$post_ids = array_unique( array_merge( $post_ids, $additional_ids ) );
192+
// Merge the additional IDs back with the original post IDs after processing all posts.
193+
// Cast to integers as defense-in-depth, since $additional_ids may include values sourced from postmeta.
194+
$post_ids = array_unique( array_map( 'absint', array_merge( $post_ids, $additional_ids ) ) );
191195
}
192196

193197
/*
@@ -597,8 +601,10 @@ function wxr_filter_postmeta( $return_me, $meta_key ) {
597601

598602
// Fetch 20 posts at a time rather than loading the entire table into memory.
599603
while ( $next_posts = array_splice( $post_ids, 0, 20 ) ) {
600-
$where = 'WHERE ID IN (' . implode( ',', $next_posts ) . ')';
601-
$posts = $wpdb->get_results( "SELECT * FROM {$wpdb->posts} $where" );
604+
// Re-sanitize immediately before use, as defense-in-depth against the IDs being interpolated directly into SQL below.
605+
$next_posts = array_map( 'absint', $next_posts );
606+
$where = 'WHERE ID IN (' . implode( ',', $next_posts ) . ')';
607+
$posts = $wpdb->get_results( "SELECT * FROM {$wpdb->posts} $where" );
602608

603609
// Begin Loop.
604610
foreach ( $posts as $post ) {

‎src/wp-includes/class-wp-http.php‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,10 @@ public static function make_absolute_url( $maybe_relative_path, $url ) {
10231023

10241024
// Strip all /path/../ out of the path.
10251025
while ( strpos( $path, '../' ) > 1 ) {
1026-
$path = preg_replace( '![^/]+/\.\./!', '', $path );
1026+
$path = preg_replace( '![^/]+/\.\./!', '', $path, -1, $segment_replacement_count );
1027+
if ( 0 === $segment_replacement_count ) {
1028+
break;
1029+
}
10271030
}
10281031

10291032
// Strip any final leading ../ from the path.

‎src/wp-includes/class-wp-oembed.php‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ public function __construct() {
7676
'#https?://(www\.)?twitter\.com/i/moments/.*#i' => array( 'https://publish.twitter.com/oembed', true ),
7777
'#https?://(www\.)?soundcloud\.com/.*#i' => array( 'https://soundcloud.com/oembed', true ),
7878
'#https?://(open|play)\.spotify\.com/.*#i' => array( 'https://embed.spotify.com/oembed/', true ),
79-
'#https?://(.+\.)?imgur\.com/.*#i' => array( 'https://api.imgur.com/oembed', true ),
8079
'#https?://(www\.)?issuu\.com/.+/docs/.+#i' => array( 'https://issuu.com/oembed_wp', true ),
8180
'#https?://(www\.)?mixcloud\.com/.*#i' => array( 'https://app.mixcloud.com/oembed/', true ),
8281
'#https?://(www\.|embed\.)?ted\.com/talks/.*#i' => array( 'https://www.ted.com/services/v1/oembed.{format}', true ),
@@ -151,7 +150,6 @@ public function __construct() {
151150
* | Dailymotion | dai.ly | 3.6.0 |
152151
* | Flickr | flic.kr | 3.6.0 |
153152
* | Spotify | spotify.com | 3.6.0 |
154-
* | Imgur | imgur.com | 3.9.0 |
155153
* | Animoto | animoto.com | 4.0.0 |
156154
* | Animoto | video214.com | 4.0.0 |
157155
* | Issuu | issuu.com | 4.0.0 |
@@ -216,6 +214,7 @@ public function __construct() {
216214
* | Meetup.com | meetu.ps | 3.9.0 | 6.0.1 |
217215
* | SlideShare | slideshare.net | 3.5.0 | 6.6.0 |
218216
* | Screencast | screencast.com | 4.8.0 | 6.8.2 |
217+
* | Imgur | imgur.com | 3.9.0 | 7.1.3 |
219218
*
220219
* @see wp_oembed_add_provider()
221220
*

‎src/wp-includes/class-wp-query.php‎

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,43 +3472,6 @@ public function get_posts() {
34723472
$this->posts = apply_filters_ref_array( 'posts_results', array( $this->posts, &$this ) );
34733473
}
34743474

3475-
if ( ! empty( $this->posts ) && $this->is_comment_feed && $this->is_singular ) {
3476-
/** This filter is documented in wp-includes/class-wp-query.php */
3477-
$cjoin = apply_filters_ref_array( 'comment_feed_join', array( '', &$this ) );
3478-
3479-
/** This filter is documented in wp-includes/class-wp-query.php */
3480-
$cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note'", &$this ) );
3481-
3482-
/** This filter is documented in wp-includes/class-wp-query.php */
3483-
$cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( '', &$this ) );
3484-
$cgroupby = ( ! empty( $cgroupby ) ) ? 'GROUP BY ' . $cgroupby : '';
3485-
3486-
/** This filter is documented in wp-includes/class-wp-query.php */
3487-
$corderby = apply_filters_ref_array( 'comment_feed_orderby', array( 'comment_date_gmt DESC', &$this ) );
3488-
$corderby = ( ! empty( $corderby ) ) ? 'ORDER BY ' . $corderby : '';
3489-
3490-
/** This filter is documented in wp-includes/class-wp-query.php */
3491-
$climits = apply_filters_ref_array( 'comment_feed_limits', array( 'LIMIT ' . get_option( 'posts_per_rss' ), &$this ) );
3492-
3493-
$comments_request = "SELECT {$wpdb->comments}.comment_ID FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits";
3494-
3495-
$comment_key = md5( $comments_request );
3496-
$comment_last_changed = wp_cache_get_last_changed( 'comment' );
3497-
3498-
$comment_cache_key = "comment_feed:$comment_key";
3499-
$comment_ids = wp_cache_get_salted( $comment_cache_key, 'comment-queries', $comment_last_changed );
3500-
if ( false === $comment_ids ) {
3501-
$comment_ids = $wpdb->get_col( $comments_request );
3502-
wp_cache_set_salted( $comment_cache_key, $comment_ids, 'comment-queries', $comment_last_changed );
3503-
}
3504-
_prime_comment_caches( $comment_ids );
3505-
3506-
// Convert to WP_Comment.
3507-
/** @var WP_Comment[] */
3508-
$this->comments = array_map( 'get_comment', $comment_ids );
3509-
$this->comment_count = count( $this->comments );
3510-
}
3511-
35123475
// Check post status to determine if post should be displayed.
35133476
if ( ! empty( $this->posts ) && ( $this->is_single || $this->is_page ) ) {
35143477
$status = get_post_status( $this->posts[0] );
@@ -3567,6 +3530,43 @@ public function get_posts() {
35673530
}
35683531
}
35693532

3533+
if ( ! empty( $this->posts ) && $this->is_comment_feed && $this->is_singular ) {
3534+
/** This filter is documented in wp-includes/class-wp-query.php */
3535+
$cjoin = apply_filters_ref_array( 'comment_feed_join', array( '', &$this ) );
3536+
3537+
/** This filter is documented in wp-includes/class-wp-query.php */
3538+
$cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note'", &$this ) );
3539+
3540+
/** This filter is documented in wp-includes/class-wp-query.php */
3541+
$cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( '', &$this ) );
3542+
$cgroupby = ( ! empty( $cgroupby ) ) ? 'GROUP BY ' . $cgroupby : '';
3543+
3544+
/** This filter is documented in wp-includes/class-wp-query.php */
3545+
$corderby = apply_filters_ref_array( 'comment_feed_orderby', array( 'comment_date_gmt DESC', &$this ) );
3546+
$corderby = ( ! empty( $corderby ) ) ? 'ORDER BY ' . $corderby : '';
3547+
3548+
/** This filter is documented in wp-includes/class-wp-query.php */
3549+
$climits = apply_filters_ref_array( 'comment_feed_limits', array( 'LIMIT ' . get_option( 'posts_per_rss' ), &$this ) );
3550+
3551+
$comments_request = "SELECT {$wpdb->comments}.comment_ID FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits";
3552+
3553+
$comment_key = md5( $comments_request );
3554+
$comment_last_changed = wp_cache_get_last_changed( 'comment' );
3555+
3556+
$comment_cache_key = "comment_feed:$comment_key";
3557+
$comment_ids = wp_cache_get_salted( $comment_cache_key, 'comment-queries', $comment_last_changed );
3558+
if ( false === $comment_ids ) {
3559+
$comment_ids = $wpdb->get_col( $comments_request );
3560+
wp_cache_set_salted( $comment_cache_key, $comment_ids, 'comment-queries', $comment_last_changed );
3561+
}
3562+
_prime_comment_caches( $comment_ids );
3563+
3564+
// Convert to WP_Comment.
3565+
/** @var WP_Comment[] */
3566+
$this->comments = array_map( 'get_comment', $comment_ids );
3567+
$this->comment_count = count( $this->comments );
3568+
}
3569+
35703570
// Put sticky posts at the top of the posts array.
35713571
$sticky_posts = get_option( 'sticky_posts' );
35723572
if ( $this->is_home && $page <= 1 && is_array( $sticky_posts ) && ! empty( $sticky_posts ) && ! $query_vars['ignore_sticky_posts'] ) {

‎src/wp-includes/post.php‎

Lines changed: 64 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5824,63 +5824,71 @@ function wp_transition_post_status( $new_status, $old_status, $post ) {
58245824
*/
58255825
do_action( 'transition_post_status', $new_status, $old_status, $post );
58265826

5827-
/**
5828-
* Fires when a post is transitioned from one status to another.
5829-
*
5830-
* The dynamic portions of the hook name, `$new_status` and `$old_status`,
5831-
* refer to the old and new post statuses, respectively.
5832-
*
5833-
* Possible hook names include:
5834-
*
5835-
* - `draft_to_publish`
5836-
* - `publish_to_trash`
5837-
* - `pending_to_draft`
5838-
*
5839-
* @since 2.3.0
5840-
*
5841-
* @param WP_Post $post Post object.
5842-
*/
5843-
do_action( "{$old_status}_to_{$new_status}", $post );
5827+
$new_status_object = get_post_status_object( $new_status );
5828+
$old_status_valid = get_post_status_object( $old_status ) || 'new' === $old_status;
5829+
$post_type_object = get_post_type_object( $post->post_type );
58445830

5845-
/**
5846-
* Fires when a post is transitioned from one status to another.
5847-
*
5848-
* The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
5849-
* refer to the new post status and post type, respectively.
5850-
*
5851-
* Possible hook names include:
5852-
*
5853-
* - `draft_post`
5854-
* - `future_post`
5855-
* - `pending_post`
5856-
* - `private_post`
5857-
* - `publish_post`
5858-
* - `trash_post`
5859-
* - `draft_page`
5860-
* - `future_page`
5861-
* - `pending_page`
5862-
* - `private_page`
5863-
* - `publish_page`
5864-
* - `trash_page`
5865-
* - `publish_attachment`
5866-
* - `trash_attachment`
5867-
*
5868-
* Please note: When this action is hooked using a particular post status (like
5869-
* 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
5870-
* first transitioned to that status from something else, as well as upon
5871-
* subsequent post updates (old and new status are both the same).
5872-
*
5873-
* Therefore, if you are looking to only fire a callback when a post is first
5874-
* transitioned to a status, use the {@see 'transition_post_status'} hook instead.
5875-
*
5876-
* @since 2.3.0
5877-
* @since 5.9.0 Added `$old_status` parameter.
5878-
*
5879-
* @param int $post_id Post ID.
5880-
* @param WP_Post $post Post object.
5881-
* @param string $old_status Old post status.
5882-
*/
5883-
do_action( "{$new_status}_{$post->post_type}", $post->ID, $post, $old_status );
5831+
if ( $new_status_object && $old_status_valid ) {
5832+
/**
5833+
* Fires when a post is transitioned from one status to another.
5834+
*
5835+
* The dynamic portions of the hook name, `$new_status` and `$old_status`,
5836+
* refer to the old and new post statuses, respectively.
5837+
*
5838+
* Possible hook names include:
5839+
*
5840+
* - `draft_to_publish`
5841+
* - `publish_to_trash`
5842+
* - `pending_to_draft`
5843+
*
5844+
* @since 2.3.0
5845+
*
5846+
* @param WP_Post $post Post object.
5847+
*/
5848+
do_action( "{$old_status}_to_{$new_status}", $post );
5849+
}
5850+
5851+
if ( $new_status_object && $post_type_object ) {
5852+
/**
5853+
* Fires when a post is transitioned from one status to another.
5854+
*
5855+
* The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
5856+
* refer to the new post status and post type, respectively.
5857+
*
5858+
* Possible hook names include:
5859+
*
5860+
* - `draft_post`
5861+
* - `future_post`
5862+
* - `pending_post`
5863+
* - `private_post`
5864+
* - `publish_post`
5865+
* - `trash_post`
5866+
* - `draft_page`
5867+
* - `future_page`
5868+
* - `pending_page`
5869+
* - `private_page`
5870+
* - `publish_page`
5871+
* - `trash_page`
5872+
* - `publish_attachment`
5873+
* - `trash_attachment`
5874+
*
5875+
* Please note: When this action is hooked using a particular post status (like
5876+
* 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
5877+
* first transitioned to that status from something else, as well as upon
5878+
* subsequent post updates (old and new status are both the same).
5879+
*
5880+
* Therefore, if you are looking to only fire a callback when a post is first
5881+
* transitioned to a status, use the {@see 'transition_post_status'} hook instead.
5882+
*
5883+
* @since 2.3.0
5884+
* @since 5.9.0 Added `$old_status` parameter.
5885+
*
5886+
* @param int $post_id Post ID.
5887+
* @param WP_Post $post Post object.
5888+
* @param string $old_status Old post status.
5889+
*/
5890+
do_action( "{$new_status}_{$post->post_type}", $post->ID, $post, $old_status );
5891+
}
58845892
}
58855893

58865894
/**

‎src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ public function create_item_permissions_check( $request ) {
703703
);
704704
}
705705

706-
if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
706+
if ( ! empty( $request['sticky'] ) && ( ! current_user_can( $post_type->cap->edit_others_posts ) || ! current_user_can( $post_type->cap->publish_posts ) ) ) {
707707
return new WP_Error(
708708
'rest_cannot_assign_sticky',
709709
__( 'Sorry, you are not allowed to make posts sticky.' ),
@@ -914,7 +914,7 @@ public function update_item_permissions_check( $request ) {
914914
);
915915
}
916916

917-
if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
917+
if ( isset( $request['sticky'] ) && is_sticky( $post->ID ) !== (bool) $request['sticky'] && ( ! current_user_can( $post_type->cap->edit_others_posts ) || ! current_user_can( $post_type->cap->publish_posts ) ) ) {
918918
return new WP_Error(
919919
'rest_cannot_assign_sticky',
920920
__( 'Sorry, you are not allowed to make posts sticky.' ),

0 commit comments

Comments
 (0)