diff --git a/src/js/_enqueues/wp/embed.js b/src/js/_enqueues/wp/embed.js index fa2934f379..58ae034f49 100644 --- a/src/js/_enqueues/wp/embed.js +++ b/src/js/_enqueues/wp/embed.js @@ -49,6 +49,7 @@ var iframes = document.querySelectorAll( 'iframe[data-secret="' + data.secret + '"]' ), blockquotes = document.querySelectorAll( 'blockquote[data-secret="' + data.secret + '"]' ), + allowedProtocols = new RegExp( '^https?:$', 'i' ), i, source, height, sourceURL, targetURL; for ( i = 0; i < blockquotes.length; i++ ) { @@ -84,6 +85,11 @@ sourceURL.href = source.getAttribute( 'src' ); targetURL.href = data.value; + /* Only follow link if the protocol is in the allow list. */ + if ( ! allowedProtocols.test( targetURL.protocol ) ) { + continue; + } + /* Only continue if link hostname matches iframe's hostname. */ if ( targetURL.host === sourceURL.host ) { if ( document.activeElement === source ) { diff --git a/src/js/media/views/frame/video-details.js b/src/js/media/views/frame/video-details.js index 2dbb22f774..6a0d604202 100644 --- a/src/js/media/views/frame/video-details.js +++ b/src/js/media/views/frame/video-details.js @@ -106,6 +106,7 @@ VideoDetails = MediaDetails.extend(/** @lends wp.media.view.MediaFrame.VideoDeta wp.ajax.send( 'set-attachment-thumbnail', { data : { + _ajax_nonce: wp.media.view.settings.nonce.setAttachmentThumbnail, urls: urls, thumbnail_id: attachment.get( 'id' ) } diff --git a/src/wp-admin/about.php b/src/wp-admin/about.php index 96dd83c22c..2907251717 100644 --- a/src/wp-admin/about.php +++ b/src/wp-admin/about.php @@ -45,6 +45,26 @@ require_once ABSPATH . 'wp-admin/admin-header.php'; <div class="about__section changelog"> <div class="column"> <h2><?php _e( 'Maintenance and Security Releases' ); ?></h2> + <p> + <?php + printf( + __( '<strong>Version %s</strong> addressed some security issues.' ), + '6.1.2' + ); + ?> + <?php + printf( + /* translators: %s: HelpHub URL. */ + __( 'For more information, see <a href="%s">the release notes</a>.' ), + sprintf( + /* translators: %s: WordPress version. */ + esc_url( __( 'https://wordpress.org/support/wordpress-version/version-%s/' ) ), + sanitize_title( '6.1.2' ) + ) + ); + ?> + </p> + <p> <?php printf( diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index 6ca6a0217a..4af19ff6bb 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/wp-admin/includes/ajax-actions.php @@ -2771,6 +2771,10 @@ function wp_ajax_set_attachment_thumbnail() { wp_send_json_error(); } + if ( false === check_ajax_referer( 'set-attachment-thumbnail', '_ajax_nonce', false ) ) { + wp_send_json_error(); + } + $post_ids = array(); // For each URL, try to find its corresponding post ID. foreach ( $_POST['urls'] as $url ) { diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 19f3b5fb14..2f2c7ad1d0 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -240,9 +240,7 @@ function get_the_block_template_html() { $content = do_blocks( $content ); $content = wptexturize( $content ); $content = convert_smilies( $content ); - $content = shortcode_unautop( $content ); $content = wp_filter_content_tags( $content ); - $content = do_shortcode( $content ); $content = str_replace( ']]>', ']]>', $content ); // Wrap block template in .wp-site-blocks to allow for specific descendant styles diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 29e375ab89..3327ea0b02 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -794,6 +794,10 @@ function serialize_blocks( $blocks ) { function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) { $result = ''; + if ( false !== strpos( $text, '<!--' ) && false !== strpos( $text, '--->' ) ) { + $text = preg_replace_callback( '%<!--(.*?)--->%', '_filter_block_content_callback', $text ); + } + $blocks = parse_blocks( $text ); foreach ( $blocks as $block ) { $block = filter_block_kses( $block, $allowed_html, $allowed_protocols ); @@ -803,6 +807,19 @@ function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols return $result; } +/** + * Callback used for regular expression replacement in filter_block_content(). + * + * @private + * @since 6.2.1 + * + * @param array $matches Array of preg_replace_callback matches. + * @return string Replacement string. + */ +function _filter_block_content_callback( $matches ) { + return '<!--' . rtrim( $matches[1], '-' ) . '-->'; +} + /** * Filters and sanitizes a parsed block to remove non-allowable HTML * from block attribute values. diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 2866bfce17..95f62c5d6b 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -2432,6 +2432,29 @@ function sanitize_html_class( $class, $fallback = '' ) { return apply_filters( 'sanitize_html_class', $sanitized, $class, $fallback ); } +/** + * Strips out all characters not allowed in a locale name. + * + * @since 6.2.1 + * + * @param string $locale_name The locale name to be sanitized. + * @return string The sanitized value. + */ +function sanitize_locale_name( $locale_name ) { + // Limit to A-Z, a-z, 0-9, '_', '-'. + $sanitized = preg_replace( '/[^A-Za-z0-9_-]/', '', $locale_name ); + + /** + * Filters a sanitized locale name string. + * + * @since 6.2.1 + * + * @param string $sanitized The sanitized locale name. + * @param string $locale_name The locale name before sanitization. + */ + return apply_filters( 'sanitize_locale_name', $sanitized, $locale_name ); +} + /** * Converts lone & characters into `&` (a.k.a. `&`) * diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index c5148a02e8..331ea1d8f3 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -149,9 +149,9 @@ function determine_locale() { $wp_lang = ''; if ( ! empty( $_GET['wp_lang'] ) ) { - $wp_lang = sanitize_text_field( $_GET['wp_lang'] ); + $wp_lang = sanitize_locale_name( wp_unslash( $_GET['wp_lang'] ) ); } elseif ( ! empty( $_COOKIE['wp_lang'] ) ) { - $wp_lang = sanitize_text_field( $_COOKIE['wp_lang'] ); + $wp_lang = sanitize_locale_name( wp_unslash( $_COOKIE['wp_lang'] ) ); } if ( ! empty( $wp_lang ) && ! empty( $GLOBALS['pagenow'] ) && 'wp-login.php' === $GLOBALS['pagenow'] ) { diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 804a34f6ac..0b05552e7a 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -4516,7 +4516,8 @@ function wp_enqueue_media( $args = array() ) { /** This filter is documented in wp-admin/includes/media.php */ 'captions' => ! apply_filters( 'disable_captions', '' ), 'nonce' => array( - 'sendToEditor' => wp_create_nonce( 'media-send-to-editor' ), + 'sendToEditor' => wp_create_nonce( 'media-send-to-editor' ), + 'setAttachmentThumbnail' => wp_create_nonce( 'set-attachment-thumbnail' ), ), 'post' => array( 'id' => 0, diff --git a/src/wp-includes/version.php b/src/wp-includes/version.php index b6a5b45572..9c53216ef3 100644 --- a/src/wp-includes/version.php +++ b/src/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.1.2-alpha-54847-src'; +$wp_version = '6.1.2-src'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. diff --git a/tests/phpunit/tests/ajax/Attachments.php b/tests/phpunit/tests/ajax/Attachments.php index 91d6943c99..b294c32170 100644 --- a/tests/phpunit/tests/ajax/Attachments.php +++ b/tests/phpunit/tests/ajax/Attachments.php @@ -103,4 +103,95 @@ class Tests_Ajax_Attachments extends WP_Ajax_UnitTestCase { $this->assertTrue( $response['success'] ); $this->assertSame( $expected, $response['data'] ); } + + public function test_wp_ajax_set_attachment_thumbnail_success() { + // Become an administrator. + $post = $_POST; + $user_id = self::factory()->user->create( + array( + 'role' => 'administrator', + 'user_login' => 'user_36578_administrator', + 'user_email' => 'user_36578_administrator@example.com', + ) + ); + wp_set_current_user( $user_id ); + $_POST = array_merge( $_POST, $post ); + + // Upload the attachment itself. + $filename = DIR_TESTDATA . '/uploads/small-audio.mp3'; + $contents = file_get_contents( $filename ); + + $upload = wp_upload_bits( wp_basename( $filename ), null, $contents ); + $attachment = $this->_make_attachment( $upload ); + + // Upload the thumbnail. + $filename = DIR_TESTDATA . '/images/waffles.jpg'; + $contents = file_get_contents( $filename ); + + $upload = wp_upload_bits( wp_basename( $filename ), null, $contents ); + $thumbnail = $this->_make_attachment( $upload ); + + // Set up a default request. + $_POST['_ajax_nonce'] = wp_create_nonce( 'set-attachment-thumbnail' ); + $_POST['thumbnail_id'] = $thumbnail; + $_POST['urls'] = array( wp_get_attachment_url( $attachment ) ); + + // Make the request. + try { + $this->_handleAjax( 'set-attachment-thumbnail' ); + } catch ( WPAjaxDieContinueException $e ) { + unset( $e ); + } + + // Get the response. + $response = json_decode( $this->_last_response, true ); + + // Ensure everything is correct. + $this->assertTrue( $response['success'] ); + } + + public function test_wp_ajax_set_attachment_thumbnail_missing_nonce() { + // Become an administrator. + $post = $_POST; + $user_id = self::factory()->user->create( + array( + 'role' => 'administrator', + 'user_login' => 'user_36578_administrator', + 'user_email' => 'user_36578_administrator@example.com', + ) + ); + wp_set_current_user( $user_id ); + $_POST = array_merge( $_POST, $post ); + + // Upload the attachment itself. + $filename = DIR_TESTDATA . '/uploads/small-audio.mp3'; + $contents = file_get_contents( $filename ); + + $upload = wp_upload_bits( wp_basename( $filename ), null, $contents ); + $attachment = $this->_make_attachment( $upload ); + + // Upload the thumbnail. + $filename = DIR_TESTDATA . '/images/waffles.jpg'; + $contents = file_get_contents( $filename ); + + $upload = wp_upload_bits( wp_basename( $filename ), null, $contents ); + $thumbnail = $this->_make_attachment( $upload ); + + // Set up a default request. + $_POST['thumbnail_id'] = $thumbnail; + $_POST['urls'] = array( wp_get_attachment_url( $attachment ) ); + + // Make the request. + try { + $this->_handleAjax( 'set-attachment-thumbnail' ); + } catch ( WPAjaxDieContinueException $e ) { + unset( $e ); + } + + // Get the response. + $response = json_decode( $this->_last_response, true ); + + // Check that success is false without sending nonce. + $this->assertFalse( $response['success'] ); + } } diff --git a/tests/phpunit/tests/formatting/sanitizeLocaleName.php b/tests/phpunit/tests/formatting/sanitizeLocaleName.php new file mode 100644 index 0000000000..cd22acbf2c --- /dev/null +++ b/tests/phpunit/tests/formatting/sanitizeLocaleName.php @@ -0,0 +1,49 @@ +<?php + +/** + * @group formatting + * + * @covers ::sanitize_locale_name + */ +class Tests_Formatting_SanitizeLocaleName extends WP_UnitTestCase { + /** + * @dataProvider data_sanitize_locale_name_returns_non_empty_string + */ + public function test_sanitize_locale_name_returns_non_empty_string( $expected, $input ) { + $this->assertSame( $expected, sanitize_locale_name( $input ) ); + } + + public function data_sanitize_locale_name_returns_non_empty_string() { + return array( + // array( expected, input ) + array( 'en_US', 'en_US' ), + array( 'en', 'en' ), + array( 'fr_FR', 'fr_FR' ), + array( 'fr_FR', 'fr_FR' ), + array( 'fr_FR-e2791ba830489d23043be8650a22a22b', 'fr_FR-e2791ba830489d23043be8650a22a22b' ), + array( '-fr_FRmo', '-fr_FR.mo' ), + array( '12324', '$12324' ), + array( '4124FRRa', '/4124$$$%%FRRa' ), + array( 'FR', '<FR' ), + array( 'FR_FR', 'FR_FR' ), + array( '--__', '--__' ), + ); + } + + /** + * @dataProvider data_sanitize_locale_name_returns_empty_string + */ + public function test_sanitize_locale_name_returns_empty_string( $input ) { + $this->assertSame( '', sanitize_locale_name( $input ) ); + } + + public function data_sanitize_locale_name_returns_empty_string() { + return array( + // array( input ) + array( '$<>' ), + array( '/$$$%%\\)' ), + array( '....' ), + array( '@///' ), + ); + } +}