Make WordPress Core

Changeset 63077


Ignore:
Timestamp:
08/06/2026 05:32:00 PM (5 weeks ago)
Author:
desrosj
Message:

Security: Backport the WordPress 7.0.3 security fixes to the 6.9 branch.

  • Users: Ensure a proper email address is used before sending email confirmations.
  • Formatting: Prevent stack overflow in safecss_filter_attr.
  • EmojI: Ensure that the emoji settings come from a script element.
  • Multisite: Enforce the active signup policy for existing users.
  • HTTP API: Improve compliance with IPv4 Special-Purpose Address Space.
  • Users: Prevent Usernames from mangling HTML
  • Comments: Exclude notes from comment feed queries.
  • Canonical: Only redirect for publicly viewable post types.
  • Administration: When wp_is_large_user_count(), ensure that the post author is always added to author dropdown.
  • Editor: Fix output of Post Date.
  • Editor: Ensure Content block tags always match available options.

Merges [63059],[63060],[63061],[63062],[63063],[63064],[63065],[63067],[62804] to the 6.9 branch.

Props xknown, westonruter, jeremyfelt, peterwilsoncc, paulkevan, lucasbustamante, jorbin, desrosj, vortfu, dmsnell, jonsurrell, davidbinda, johnbillion, ehtis, batmoo, lancewillett, wildworks, mukesh27, odkdn1, khokansardar, isabel_brison, bernhard-reiter, tyxla, aduth, talldanwp.

Location:
branches/6.9
Files:
17 edited

Legend:

Unmodified
Added
Removed
  • branches/6.9

  • branches/6.9/package-lock.json

    r61944 r63077  
    1919                                "@wordpress/block-directory": "5.33.11",
    2020                                "@wordpress/block-editor": "15.6.9",
    21                                 "@wordpress/block-library": "9.33.10",
     21                                "@wordpress/block-library": "9.33.11",
    2222                                "@wordpress/block-serialization-default-parser": "5.33.1",
    2323                                "@wordpress/blocks": "15.6.3",
     
    69346934                },
    69356935                "node_modules/@wordpress/block-library": {
    6936                         "version": "9.33.10",
    6937                         "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-9.33.10.tgz",
    6938                         "integrity": "sha512-9qrBXTc1BR539L78KuMCZ7PHt4US822NSKKVNcgVTkPH2qHyUDvr6t680HRHCI7VYWq1NXp9teZo7PEZNVfhVA==",
     6936                        "version": "9.33.11",
     6937                        "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-9.33.11.tgz",
     6938                        "integrity": "sha512-csWoGTw1MpzQW4xjFVrddP689TmLKRr0dgr/u1gBXdd2rmmSDvvh4eEcZTZhpM6f87oaemLZiOG3mRxBT5avdg==",
    69396939                        "license": "GPL-2.0-or-later",
    69406940                        "dependencies": {
  • branches/6.9/package.json

    r61944 r63077  
    8585                "@wordpress/block-directory": "5.33.11",
    8686                "@wordpress/block-editor": "15.6.9",
    87                 "@wordpress/block-library": "9.33.10",
     87                "@wordpress/block-library": "9.33.11",
    8888                "@wordpress/block-serialization-default-parser": "5.33.1",
    8989                "@wordpress/blocks": "15.6.3",
  • branches/6.9/src/js/_enqueues/admin/inline-edit-post.js

    r59134 r63077  
    360360
    361361                        // The post author no longer has edit capabilities, so we need to add them to the list of authors.
    362                         $(':input[name="post_author"]', editRow).prepend('<option value="' + $('.post_author', rowData).text() + '">' + $('#post-' + id + ' .author').text() + '</option>');
     362                        $(':input[name="post_author"]', editRow).prepend(
     363                                new Option(
     364                                        $('#post-' + id + ' .author').text(),
     365                                        $('.post_author', rowData).text()
     366                                )
     367                        );
    363368                }
    364369                if ( $( ':input[name="post_author"] option', editRow ).length === 1 ) {
  • branches/6.9/src/js/_enqueues/lib/emoji-loader.js

    r61134 r63077  
    1717 */
    1818
    19 const settings = /** @type {WPEmojiSettings} */ (
    20         JSON.parse( document.getElementById( 'wp-emoji-settings' ).textContent )
    21 );
     19const selector = 'script#wp-emoji-settings';
     20const script = document.querySelector( selector );
     21if ( ! ( script instanceof HTMLScriptElement ) ) {
     22        throw new Error( `Element missing: ${ selector }`);
     23}
     24const settings = /** @type {WPEmojiSettings} */ ( JSON.parse( script.text ) );
    2225
    2326// For compatibility with other scripts that read from this global, in particular wp-includes/js/wp-emoji.js (source file: js/_enqueues/wp/emoji.js).
  • branches/6.9/src/wp-admin/includes/user.php

    r59896 r63077  
    4545        }
    4646
     47        $errors = new WP_Error();
     48
    4749        $pass1 = '';
    4850        $pass2 = '';
     
    7981
    8082        if ( isset( $_POST['email'] ) ) {
    81                 $user->user_email = sanitize_text_field( wp_unslash( $_POST['email'] ) );
     83                $maybe_email = wp_unslash( $_POST['email'] );
     84                if ( is_string( $maybe_email ) && is_email( $maybe_email ) ) {
     85                        $user->user_email = $maybe_email;
     86                } else {
     87                        $errors->add( 'invalid_email', __( '<strong>Error:</strong> The email address is not correct.' ), array( 'form-field' => 'email' ) );
     88                }
    8289        }
    8390        if ( isset( $_POST['url'] ) ) {
     
    145152                $user->use_ssl = 1;
    146153        }
    147 
    148         $errors = new WP_Error();
    149154
    150155        /* checking that username has been typed */
  • branches/6.9/src/wp-includes/blocks/post-content.php

    r61009 r63077  
    5757        $tag_name = 'div';
    5858
    59         if ( ! empty( $attributes['tagName'] ) && tag_escape( $attributes['tagName'] ) === $attributes['tagName'] ) {
    60                 $tag_name = $attributes['tagName'];
     59        if ( isset( $attributes['tagName'] ) && is_string( $attributes['tagName'] ) ) {
     60                /**
     61                 * The allowed tag names match the options offered in the editor.
     62                 *
     63                 * @see packages/block-library/src/post-content/edit.js
     64                 */
     65                $allowed_tag_names   = array( 'div', 'main', 'section', 'article' );
     66                $normalized_tag_name = strtolower( $attributes['tagName'] );
     67
     68                if ( in_array( $normalized_tag_name, $allowed_tag_names, true ) ) {
     69                        $tag_name = $normalized_tag_name;
     70                }
    6171        }
    6272
  • branches/6.9/src/wp-includes/blocks/post-date.php

    r61009 r63077  
    8383
    8484        if ( isset( $attributes['isLink'] ) && $attributes['isLink'] && isset( $block->context['postId'] ) ) {
    85                 $formatted_date = sprintf( '<a href="%1s">%2s</a>', get_the_permalink( $block->context['postId'] ), $formatted_date );
     85                $formatted_date = sprintf( '<a href="%1$s">%2$s</a>', esc_url( get_the_permalink( $block->context['postId'] ) ), esc_html( $formatted_date ) );
     86        } else {
     87                $formatted_date = esc_html( $formatted_date );
    8688        }
    8789
     
    8991                '<div %1$s><time datetime="%2$s">%3$s</time></div>',
    9092                $wrapper_attributes,
    91                 $unformatted_date,
     93                esc_attr( $unformatted_date ),
    9294                $formatted_date
    9395        );
  • branches/6.9/src/wp-includes/canonical.php

    r61136 r63077  
    986986                                        return false;
    987987                                }
    988                                 $where .= " AND post_type IN ('" . join( "', '", esc_sql( get_query_var( 'post_type' ) ) ) . "')";
     988                                $where .= " AND post_type IN ('" . join( "', '", esc_sql( $post_types ) ) . "')";
    989989                        } else {
    990990                                if ( ! in_array( get_query_var( 'post_type' ), $publicly_viewable_post_types, true ) ) {
  • branches/6.9/src/wp-includes/class-wp-query.php

    r62775 r63077  
    28202820                        if ( $this->is_archive || $this->is_search ) {
    28212821                                $cjoin    = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID ) $join ";
    2822                                 $cwhere   = "WHERE comment_approved = '1' $where";
     2822                                $cwhere   = "WHERE comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note' $where";
    28232823                                $cgroupby = "{$wpdb->comments}.comment_id";
    28242824                        } else { // Other non-singular, e.g. front.
    28252825                                $cjoin    = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID )";
    2826                                 $cwhere   = "WHERE ( post_status = 'publish' OR ( post_status = 'inherit' AND post_type = 'attachment' ) ) AND comment_approved = '1'";
     2826                                $cwhere   = "WHERE ( post_status = 'publish' OR ( post_status = 'inherit' AND post_type = 'attachment' ) ) AND comment_approved = '1' AND {$wpdb->comments}.comment_type != 'note'";
    28272827                                $cgroupby = '';
    28282828                        }
     
    34823482
    34833483                        /** This filter is documented in wp-includes/query.php */
    3484                         $cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1'", &$this ) );
     3484                        $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 ) );
    34853485
    34863486                        /** This filter is documented in wp-includes/query.php */
  • branches/6.9/src/wp-includes/class-wp-script-modules.php

    r61550 r63077  
    873873                         * Example:
    874874                         *
    875                          *     const dataContainer = document.getElementById( 'wp-script-module-data-MyScriptModuleID' );
     875                         *     const dataContainer = document.querySelector( 'script[id="wp-script-module-data-MyScriptModuleID"]' );
    876876                         *     let data = {};
    877                          *     if ( dataContainer ) {
     877                         *     if ( dataContainer instanceof HTMLScriptElement ) {
    878878                         *         try {
    879                          *             data = JSON.parse( dataContainer.textContent );
     879                         *             data = JSON.parse( dataContainer.text );
    880880                         *         } catch {}
    881881                         *     }
  • branches/6.9/src/wp-includes/http.php

    r60652 r63077  
    600600                if ( $ip ) {
    601601                        $parts = array_map( 'intval', explode( '.', $ip ) );
    602                         if ( 127 === $parts[0] || 10 === $parts[0] || 0 === $parts[0]
    603                                 || ( 172 === $parts[0] && 16 <= $parts[1] && 31 >= $parts[1] )
    604                                 || ( 192 === $parts[0] && 168 === $parts[1] )
     602
     603                        /*
     604                         * These IP address ranges are not considered valid external hosts for HTTP requests.
     605                         *
     606                         * If the host resolves to an IP address in these ranges, the request will be rejected unless the 'http_request_host_is_external' filter allows it.
     607                         *
     608                         * References:
     609                         *
     610                         * - IPv4 Special-Purpose Address Space: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
     611                         * - IPv4 Multicast Address Assignments: https://www.rfc-editor.org/rfc/rfc5771.html
     612                         */
     613                        if ( 127 === $parts[0] || 10 === $parts[0] || 0 === $parts[0]          // 127.0.0.0/8 (loopback), 10.0.0.0/8 (private), 0.0.0.0/8 (this network).
     614                                || ( 172 === $parts[0] && 16 <= $parts[1] && 31 >= $parts[1] )     // 172.16.0.0/12 (private).
     615                                || ( 192 === $parts[0] && 168 === $parts[1] )                      // 192.168.0.0/16 (private).
     616                                || ( 192 === $parts[0] && 0 === $parts[1] && 0 === $parts[2] )     // 192.0.0.0/24 (IETF protocol assignments).
     617                                || ( 192 === $parts[0] && 0 === $parts[1] && 2 === $parts[2] )     // 192.0.2.0/24 (TEST-NET-1).
     618                                || ( 192 === $parts[0] && 88 === $parts[1] && 99 === $parts[2] )   // 192.88.99.0/24 (6to4 relay anycast).
     619                                || ( 198 === $parts[0] && 51 === $parts[1] && 100 === $parts[2] )  // 198.51.100.0/24 (TEST-NET-2).
     620                                || ( 203 === $parts[0] && 0 === $parts[1] && 113 === $parts[2] )   // 203.0.113.0/24 (TEST-NET-3).
     621                                || ( 169 === $parts[0] && 254 === $parts[1] )                      // 169.254.0.0/16 (link-local and cloud metadata).
     622                                || ( 100 === $parts[0] && 64 <= $parts[1] && 127 >= $parts[1] )    // 100.64.0.0/10 (CGNAT).
     623                                || ( 198 === $parts[0] && 18 <= $parts[1] && 19 >= $parts[1] )     // 198.18.0.0/15 (benchmarking).
     624                                || ( 224 <= $parts[0] && 239 >= $parts[0] )                        // 224.0.0.0/4 (multicast).
     625                                || 240 <= $parts[0]                                                // 240.0.0.0/4 (reserved, includes 255.255.255.255 broadcast).
    605626                        ) {
    606627                                // If host appears local, reject unless specifically allowed.
  • branches/6.9/src/wp-includes/kses.php

    r61893 r63077  
    29272927                        );
    29282928
     2929                        // Bail if the recursive function stripping hit a PCRE error (e.g. stack/backtrack limit).
     2930                        if ( null === $css_test_string ) {
     2931                                continue;
     2932                        }
     2933
    29292934                        /*
    29302935                         * Disallow CSS containing \ ( & } = or comments, except for within url(), var(), calc(), etc.
    29312936                         * which were removed from the test string above.
    29322937                         */
    2933                         $allow_css = ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string );
     2938                        $allow_css = 0 === preg_match( '%[\\\(&=}]|/\*%', $css_test_string );
    29342939
    29352940                        /**
  • branches/6.9/src/wp-includes/user.php

    r61137 r63077  
    187187                                /* translators: %s: User name. */
    188188                                __( '<strong>Error:</strong> The username <strong>%s</strong> is not registered on this site. If you are unsure of your username, try your email address instead.' ),
    189                                 $username
     189                                esc_html( $username )
    190190                        )
    191191                );
     
    214214                                /* translators: %s: User name. */
    215215                                __( '<strong>Error:</strong> The password you entered for the username %s is incorrect.' ),
    216                                 '<strong>' . $username . '</strong>'
     216                                '<strong>' . esc_html( $username ) . '</strong>'
    217217                        ) .
    218218                        ' <a href="' . wp_lostpassword_url() . '">' .
     
    297297                                /* translators: %s: Email address. */
    298298                                __( '<strong>Error:</strong> The password you entered for the email address %s is incorrect.' ),
    299                                 '<strong>' . $email . '</strong>'
     299                                '<strong>' . esc_html( $email ) . '</strong>'
    300300                        ) .
    301301                        ' <a href="' . wp_lostpassword_url() . '">' .
     
    35293529                                /* translators: %s: Link to the login page. */
    35303530                                __( '<strong>Error:</strong> This email address is already registered. <a href="%s">Log in</a> with this address or choose another one.' ),
    3531                                 wp_login_url()
     3531                                esc_url( wp_login_url() )
    35323532                        )
    35333533                );
     
    35773577                                /* translators: %s: Admin email address. */
    35783578                                __( '<strong>Error:</strong> Could not register you&hellip; please contact the <a href="mailto:%s">site admin</a>!' ),
    3579                                 get_option( 'admin_email' )
     3579                                esc_attr( get_option( 'admin_email' ) )
    35803580                        )
    35813581                );
     
    38003800 * @since 3.0.0
    38013801 * @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific.
     3802 * @since 7.0.3 Added the `$user_id` parameter, which is sent with the `personal_options_update` action.
     3803 *
     3804 * @param int $user_id Optional. The ID of the user whose email is being changed. Defaults to `$_POST['user_id']` if set, otherwise 0.
    38023805 *
    38033806 * @global WP_Error $errors WP_Error object.
    38043807 */
    3805 function send_confirmation_on_profile_email() {
     3808function send_confirmation_on_profile_email( $user_id = 0 ) {
    38063809        global $errors;
     3810
     3811        // Maintain backward compatibility for those relying on a check based on $_POST['user_id'].
     3812        if ( ! $user_id && isset( $_POST['user_id'] ) ) {
     3813                $user_id = absint( $_POST['user_id'] );
     3814        }
    38073815
    38083816        $current_user = wp_get_current_user();
     
    38113819        }
    38123820
    3813         if ( $current_user->ID !== (int) $_POST['user_id'] ) {
     3821        if ( 0 === $current_user->ID || $current_user->ID !== (int) $user_id ) {
    38143822                return false;
    38153823        }
     
    38253833                        );
    38263834
     3835                        $_POST['email'] = addslashes( $current_user->user_email );
    38273836                        return;
    38283837                }
     
    38383847                        delete_user_meta( $current_user->ID, '_new_email' );
    38393848
     3849                        $_POST['email'] = addslashes( $current_user->user_email );
    38403850                        return;
    38413851                }
  • branches/6.9/src/wp-login.php

    r61085 r63077  
    12221222                                        /* translators: %s: Link to the login page. */
    12231223                                        __( 'Check your email for the confirmation link, then visit the <a href="%s">login page</a>.' ),
    1224                                         wp_login_url()
     1224                                        esc_url( wp_login_url() )
    12251225                                ),
    12261226                                'message'
     
    12321232                                        /* translators: %s: Link to the login page. */
    12331233                                        __( 'Registration complete. Please check your email, then visit the <a href="%s">login page</a>.' ),
    1234                                         wp_login_url()
     1234                                        esc_url( wp_login_url() )
    12351235                                ),
    12361236                                'message'
  • branches/6.9/src/wp-signup.php

    r59960 r63077  
    997997                        break;
    998998                case 'gimmeanotherblog':
    999                         validate_another_blog_signup();
     999                        if ( 'all' === $active_signup || 'blog' === $active_signup ) {
     1000                                validate_another_blog_signup();
     1001                        } else {
     1002                                _e( 'Site registration has been disabled.' );
     1003                        }
    10001004                        break;
    10011005                case 'default':
  • branches/6.9/tests/phpunit/tests/query/commentFeed.php

    r55745 r63077  
    8484
    8585        /**
     86         * @ticket 65613
     87         */
     88        public function test_main_comment_feed_should_exclude_notes(): void {
     89                $note_id = self::factory()->comment->create(
     90                        array(
     91                                'comment_post_ID'  => self::$post_ids[0],
     92                                'comment_type'     => 'note',
     93                                'comment_approved' => '1',
     94                        )
     95                );
     96
     97                $q = new WP_Query();
     98                $q->query(
     99                        array(
     100                                'withcomments' => 1,
     101                                'feed'         => 'comments-rss',
     102                        )
     103                );
     104
     105                $this->assertTrue( $q->is_comment_feed() );
     106                $this->assertFalse( $q->is_singular() );
     107
     108                $comment_ids = array_map( 'intval', wp_list_pluck( $q->comments, 'comment_ID' ) );
     109                $this->assertNotContains( $note_id, $comment_ids, 'Comments feed should not include notes.' );
     110                $this->assertSame( 15, $q->comment_count, 'Comments feed should include all regular comments.' );
     111        }
     112
     113        /**
     114         * @ticket 65613
     115         */
     116        public function test_archive_comment_feed_should_exclude_notes(): void {
     117                $note_id = self::factory()->comment->create(
     118                        array(
     119                                'comment_post_ID'  => self::$post_ids[0],
     120                                'comment_type'     => 'note',
     121                                'comment_approved' => '1',
     122                        )
     123                );
     124
     125                $q = new WP_Query();
     126                $q->query(
     127                        array(
     128                                'withcomments' => 1,
     129                                'feed'         => 'comments-rss',
     130                                'year'         => (int) get_the_date( 'Y', self::$post_ids[0] ),
     131                        )
     132                );
     133
     134                $this->assertTrue( $q->is_comment_feed() );
     135                $this->assertTrue( $q->is_archive() );
     136
     137                $comment_ids = array_map( 'intval', wp_list_pluck( $q->comments, 'comment_ID' ) );
     138                $this->assertNotContains( $note_id, $comment_ids, 'Archive comments feed should not include notes.' );
     139                $this->assertSame( 15, $q->comment_count, 'Archive comments feed should include all regular comments.' );
     140        }
     141
     142        /**
     143         * @ticket 65613
     144         */
     145        public function test_single_comment_feed_should_exclude_notes(): void {
     146                $post = get_post( self::$post_ids[0] );
     147                $this->assertInstanceOf( WP_Post::class, $post );
     148
     149                $note_id = self::factory()->comment->create(
     150                        array(
     151                                'comment_post_ID'  => $post->ID,
     152                                'comment_type'     => 'note',
     153                                'comment_approved' => '1',
     154                        )
     155                );
     156
     157                $q = new WP_Query();
     158                $q->query(
     159                        array(
     160                                'withcomments' => 1,
     161                                'feed'         => 'comments-rss',
     162                                'post_type'    => $post->post_type,
     163                                'name'         => $post->post_name,
     164                        )
     165                );
     166
     167                $this->assertTrue( $q->is_comment_feed() );
     168                $this->assertTrue( $q->is_singular() );
     169
     170                $comment_ids = array_map( 'intval', wp_list_pluck( $q->comments, 'comment_ID' ) );
     171                $this->assertNotContains( $note_id, $comment_ids, 'Singular comments feed should not include notes.' );
     172                $this->assertSame( 5, $q->comment_count, 'Singular comments feed should include all regular comments.' );
     173        }
     174
     175        /**
    86176         * @ticket 36904
    87177         */
Note: See TracChangeset for help on using the changeset viewer.