Make WordPress Core

Changeset 64144


Ignore:
Timestamp:
10/06/2026 03:02:34 PM (3 weeks ago)
Author:
jorbin
Message:

Security: Backport the WordPress 7.1.3 security fixes to the 6.7 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 6.7 branch.

Props xknown, vortfu, marcs0h, lancewillett, jeremyfelt, jorbin, peterwilsoncc, ehtis, westonruter, jonsurrell, rfaile313, johnbillion.

Location:
branches/6.7/src
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • branches/6.7/src/js/_enqueues/admin/common.js

    r59759 r64144  
    690690        link.parent('li').addClass('active');
    691691
    692         panel = $( link.attr('href') );
     692        panel = $( document ).find( link.attr('href') );
    693693
    694694        // Panels.
  • branches/6.7/src/wp-admin/includes/export.php

    r58009 r64144  
    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
    … …  
    589593                // Fetch 20 posts at a time rather than loading the entire table into memory.
    590594                while ( $next_posts = array_splice( $post_ids, 0, 20 ) ) {
    591                         $where = 'WHERE ID IN (' . implode( ',', $next_posts ) . ')';
    592                         $posts = $wpdb->get_results( "SELECT * FROM {$wpdb->posts} $where" );
     595                        // Re-sanitize immediately before use, as defense-in-depth against the IDs being interpolated directly into SQL below.
     596                        $next_posts = array_map( 'absint', $next_posts );
     597                        $where      = 'WHERE ID IN (' . implode( ',', $next_posts ) . ')';
     598                        $posts      = $wpdb->get_results( "SELECT * FROM {$wpdb->posts} $where" );
    593599
    594600                        // Begin Loop.
  • branches/6.7/src/wp-includes/class-wp-http.php

    r59009 r64144  
    10181018                        // Strip all /path/../ out of the path.
    10191019                        while ( strpos( $path, '../' ) > 1 ) {
    1020                                 $path = preg_replace( '![^/]+/\.\./!', '', $path );
     1020                                $path = preg_replace( '![^/]+/\.\./!', '', $path, -1, $segment_replacement_count );
     1021                                if ( 0 === $segment_replacement_count ) {
     1022                                        break;
     1023                                }
    10211024                        }
    10221025
  • branches/6.7/src/wp-includes/class-wp-oembed.php

    r58362 r64144  
    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 ),
    … …  
    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   |
    … …  
    215213                 * | Meetup.com   | meetu.ps             | 3.9.0     | 6.0.1     |
    216214                 * | SlideShare   | slideshare.net       | 3.5.0     | 6.6.0     |
     215                 * | Imgur        | imgur.com            | 3.9.0     | 7.1.3     |
    217216                 *
    218217                 * @see wp_oembed_add_provider()
  • branches/6.7/src/wp-includes/class-wp-query.php

    r58180 r64144  
    34023402                }
    34033403
    3404                 if ( ! empty( $this->posts ) && $this->is_comment_feed && $this->is_singular ) {
    3405                         /** This filter is documented in wp-includes/query.php */
    3406                         $cjoin = apply_filters_ref_array( 'comment_feed_join', array( '', &$this ) );
    3407 
    3408                         /** This filter is documented in wp-includes/query.php */
    3409                         $cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1'", &$this ) );
    3410 
    3411                         /** This filter is documented in wp-includes/query.php */
    3412                         $cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( '', &$this ) );
    3413                         $cgroupby = ( ! empty( $cgroupby ) ) ? 'GROUP BY ' . $cgroupby : '';
    3414 
    3415                         /** This filter is documented in wp-includes/query.php */
    3416                         $corderby = apply_filters_ref_array( 'comment_feed_orderby', array( 'comment_date_gmt DESC', &$this ) );
    3417                         $corderby = ( ! empty( $corderby ) ) ? 'ORDER BY ' . $corderby : '';
    3418 
    3419                         /** This filter is documented in wp-includes/query.php */
    3420                         $climits = apply_filters_ref_array( 'comment_feed_limits', array( 'LIMIT ' . get_option( 'posts_per_rss' ), &$this ) );
    3421 
    3422                         $comments_request = "SELECT {$wpdb->comments}.comment_ID FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits";
    3423 
    3424                         $comment_key          = md5( $comments_request );
    3425                         $comment_last_changed = wp_cache_get_last_changed( 'comment' );
    3426 
    3427                         $comment_cache_key = "comment_feed:$comment_key:$comment_last_changed";
    3428                         $comment_ids       = wp_cache_get( $comment_cache_key, 'comment-queries' );
    3429                         if ( false === $comment_ids ) {
    3430                                 $comment_ids = $wpdb->get_col( $comments_request );
    3431                                 wp_cache_add( $comment_cache_key, $comment_ids, 'comment-queries' );
    3432                         }
    3433                         _prime_comment_caches( $comment_ids );
    3434 
    3435                         // Convert to WP_Comment.
    3436                         /** @var WP_Comment[] */
    3437                         $this->comments      = array_map( 'get_comment', $comment_ids );
    3438                         $this->comment_count = count( $this->comments );
    3439                 }
    3440 
    34413404                // Check post status to determine if post should be displayed.
    34423405                if ( ! empty( $this->posts ) && ( $this->is_single || $this->is_page ) ) {
    … …  
    34973460                }
    34983461
     3462                if ( ! empty( $this->posts ) && $this->is_comment_feed && $this->is_singular ) {
     3463                        /** This filter is documented in wp-includes/query.php */
     3464                        $cjoin = apply_filters_ref_array( 'comment_feed_join', array( '', &$this ) );
     3465
     3466                        /** This filter is documented in wp-includes/query.php */
     3467                        $cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1'", &$this ) );
     3468
     3469                        /** This filter is documented in wp-includes/query.php */
     3470                        $cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( '', &$this ) );
     3471                        $cgroupby = ( ! empty( $cgroupby ) ) ? 'GROUP BY ' . $cgroupby : '';
     3472
     3473                        /** This filter is documented in wp-includes/query.php */
     3474                        $corderby = apply_filters_ref_array( 'comment_feed_orderby', array( 'comment_date_gmt DESC', &$this ) );
     3475                        $corderby = ( ! empty( $corderby ) ) ? 'ORDER BY ' . $corderby : '';
     3476
     3477                        /** This filter is documented in wp-includes/query.php */
     3478                        $climits = apply_filters_ref_array( 'comment_feed_limits', array( 'LIMIT ' . get_option( 'posts_per_rss' ), &$this ) );
     3479
     3480                        $comments_request = "SELECT {$wpdb->comments}.comment_ID FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits";
     3481
     3482                        $comment_key          = md5( $comments_request );
     3483                        $comment_last_changed = wp_cache_get_last_changed( 'comment' );
     3484
     3485                        $comment_cache_key = "comment_feed:$comment_key:$comment_last_changed";
     3486                        $comment_ids       = wp_cache_get( $comment_cache_key, 'comment-queries' );
     3487                        if ( false === $comment_ids ) {
     3488                                $comment_ids = $wpdb->get_col( $comments_request );
     3489                                wp_cache_add( $comment_cache_key, $comment_ids, 'comment-queries' );
     3490                        }
     3491                        _prime_comment_caches( $comment_ids );
     3492
     3493                        // Convert to WP_Comment.
     3494                        /** @var WP_Comment[] */
     3495                        $this->comments      = array_map( 'get_comment', $comment_ids );
     3496                        $this->comment_count = count( $this->comments );
     3497                }
     3498
    34993499                // Put sticky posts at the top of the posts array.
    35003500                $sticky_posts = get_option( 'sticky_posts' );
  • branches/6.7/src/wp-includes/post.php

    r59161 r64144  
    56905690        do_action( 'transition_post_status', $new_status, $old_status, $post );
    56915691
    5692         /**
    5693          * Fires when a post is transitioned from one status to another.
    5694          *
    5695          * The dynamic portions of the hook name, `$new_status` and `$old_status`,
    5696          * refer to the old and new post statuses, respectively.
    5697          *
    5698          * Possible hook names include:
    5699          *
    5700          *  - `draft_to_publish`
    5701          *  - `publish_to_trash`
    5702          *  - `pending_to_draft`
    5703          *
    5704          * @since 2.3.0
    5705          *
    5706          * @param WP_Post $post Post object.
    5707          */
    5708         do_action( "{$old_status}_to_{$new_status}", $post );
    5709 
    5710         /**
    5711          * Fires when a post is transitioned from one status to another.
    5712          *
    5713          * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
    5714          * refer to the new post status and post type, respectively.
    5715          *
    5716          * Possible hook names include:
    5717          *
    5718          *  - `draft_post`
    5719          *  - `future_post`
    5720          *  - `pending_post`
    5721          *  - `private_post`
    5722          *  - `publish_post`
    5723          *  - `trash_post`
    5724          *  - `draft_page`
    5725          *  - `future_page`
    5726          *  - `pending_page`
    5727          *  - `private_page`
    5728          *  - `publish_page`
    5729          *  - `trash_page`
    5730          *  - `publish_attachment`
    5731          *  - `trash_attachment`
    5732          *
    5733          * Please note: When this action is hooked using a particular post status (like
    5734          * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
    5735          * first transitioned to that status from something else, as well as upon
    5736          * subsequent post updates (old and new status are both the same).
    5737          *
    5738          * Therefore, if you are looking to only fire a callback when a post is first
    5739          * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
    5740          *
    5741          * @since 2.3.0
    5742          * @since 5.9.0 Added `$old_status` parameter.
    5743          *
    5744          * @param int     $post_id    Post ID.
    5745          * @param WP_Post $post       Post object.
    5746          * @param string  $old_status Old post status.
    5747          */
    5748         do_action( "{$new_status}_{$post->post_type}", $post->ID, $post, $old_status );
     5692        $new_status_object = get_post_status_object( $new_status );
     5693        $old_status_valid  = get_post_status_object( $old_status ) || 'new' === $old_status;
     5694        $post_type_object  = get_post_type_object( $post->post_type );
     5695
     5696        if ( $new_status_object && $old_status_valid ) {
     5697                /**
     5698                 * Fires when a post is transitioned from one status to another.
     5699                 *
     5700                 * The dynamic portions of the hook name, `$new_status` and `$old_status`,
     5701                 * refer to the old and new post statuses, respectively.
     5702                 *
     5703                 * Possible hook names include:
     5704                 *
     5705                 *  - `draft_to_publish`
     5706                 *  - `publish_to_trash`
     5707                 *  - `pending_to_draft`
     5708                 *
     5709                 * @since 2.3.0
     5710                 *
     5711                 * @param WP_Post $post Post object.
     5712                 */
     5713                do_action( "{$old_status}_to_{$new_status}", $post );
     5714        }
     5715
     5716        if ( $new_status_object && $post_type_object ) {
     5717                /**
     5718                 * Fires when a post is transitioned from one status to another.
     5719                 *
     5720                 * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
     5721                 * refer to the new post status and post type, respectively.
     5722                 *
     5723                 * Possible hook names include:
     5724                 *
     5725                 *  - `draft_post`
     5726                 *  - `future_post`
     5727                 *  - `pending_post`
     5728                 *  - `private_post`
     5729                 *  - `publish_post`
     5730                 *  - `trash_post`
     5731                 *  - `draft_page`
     5732                 *  - `future_page`
     5733                 *  - `pending_page`
     5734                 *  - `private_page`
     5735                 *  - `publish_page`
     5736                 *  - `trash_page`
     5737                 *  - `publish_attachment`
     5738                 *  - `trash_attachment`
     5739                 *
     5740                 * Please note: When this action is hooked using a particular post status (like
     5741                 * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
     5742                 * first transitioned to that status from something else, as well as upon
     5743                 * subsequent post updates (old and new status are both the same).
     5744                 *
     5745                 * Therefore, if you are looking to only fire a callback when a post is first
     5746                 * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
     5747                 *
     5748                 * @since 2.3.0
     5749                 * @since 5.9.0 Added `$old_status` parameter.
     5750                 *
     5751                 * @param int     $post_id    Post ID.
     5752                 * @param WP_Post $post       Post object.
     5753                 * @param string  $old_status Old post status.
     5754                 */
     5755                do_action( "{$new_status}_{$post->post_type}", $post->ID, $post, $old_status );
     5756        }
    57495757}
    57505758
  • branches/6.7/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php

    r60819 r64144  
    676676                }
    677677
    678                 if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
     678                if ( ! empty( $request['sticky'] ) && ( ! current_user_can( $post_type->cap->edit_others_posts ) || ! current_user_can( $post_type->cap->publish_posts ) ) ) {
    679679                        return new WP_Error(
    680680                                'rest_cannot_assign_sticky',
    … …  
    887887                }
    888888
    889                 if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
     889                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 ) ) ) {
    890890                        return new WP_Error(
    891891                                'rest_cannot_assign_sticky',
Note: See TracChangeset for help on using the changeset viewer.