On the 16th of May 2023, WordPress.org released a security update and recommended users to update their sites as soon as possible. This WordPress core 6.2.1 security release addresses 5 different security vulnerabilities which affects multiple WordPress core versions.
For many, WordPress automatically updates the core to the latest version. Check if your WordPress version is 6.2.1 – if not, update immediately!
Cross-Site Request Forgery
See more from Patchstack database
The underlying vulnerable code located in the wp_ajax_set_attachment_thumbnail
function:
function wp_ajax_set_attachment_thumbnail() {
if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
wp_send_json_error();
}
$thumbnail_id = (int) $_POST['thumbnail_id'];
if ( empty( $thumbnail_id ) ) {
wp_send_json_error();
}
$post_ids = array();
// For each URL, try to find its corresponding post ID.
foreach ( $_POST['urls'] as $url ) {
$post_id = attachment_url_to_postid( $url );
if ( ! empty( $post_id ) ) {
$post_ids[] = $post_id;
}
}
if ( empty( $post_ids ) ) {
wp_send_json_error();
}
$success = 0;
// For each found attachment, set its thumbnail.
foreach ( $post_ids as $post_id ) {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
continue;
}
if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
$success++;
}
}
if ( 0 === $success ) {
wp_send_json_error();
} else {
wp_send_json_success();
}
wp_send_json_error();
}
The ajax action already implemented a permission check using current_user_can
function, but the function still lack of nonce check, resulting a CSRF issue.
Potential impact
This allows unauthenticated users to update an image thumbnail by tricking privileged user to do an action such as clicking a link.
The patch
Since the main issue is lack of nonce check, implementing a nonce check fixes the issue. The patch can be seen below:

Unauthenticated Shortcode Execution
See more from Patchstack database
The underlying vulnerable code exist in the get_the_block_template_html
function:
function get_the_block_template_html() {
global $_wp_current_template_content;
global $wp_embed;
if ( ! $_wp_current_template_content ) {
if ( is_user_logged_in() ) {
return '<h1>' . esc_html__( 'No matching template found' ) . '</h1>';
}
return;
}
$content = $wp_embed->run_shortcode( $_wp_current_template_content );
$content = $wp_embed->autoembed( $content );
$content = do_blocks( $content );
$content = wptexturize( $content );
$content = convert_smilies( $content );
$content = shortcode_unautop( $content );
$content = wp_filter_content_tags( $content, 'template' );
$content = do_shortcode( $content );
$content = str_replace( ']]>', ']]>', $content );
// Wrap block template in .wp-site-blocks to allow for specific descendant styles
// (e.g. `.wp-site-blocks > *`).
return '<div class="wp-site-blocks">' . $content . '</div>';
}
The function is used to process user-generated content on the block theme. The function could generate an arbitrary shortcode supplied by unauthenticated user via comment or other content.
Potential impact
This allows unauthenticated users to arbitrarily generated any shortcode feature available on the target WordPress site, which generally could only be performed by authenticated user such as Contributor role. This issue in general have a minimum impact but could be chained with another vulnerability in the targeted shortcode to increase the impact.
The patch
Since the main issue is the code tries to generata arbitrary shortcode from the supplied content, removing the shortcode generation process fixes the issue. The patch can be seen below:

Block Attributes Improper Sanitization
See more from Patchstack database
The underlying vulnerable code located in the filter_block_content
function:
function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) {
$result = '';
$blocks = parse_blocks( $text );
foreach ( $blocks as $block ) {
$block = filter_block_kses( $block, $allowed_html, $allowed_protocols );
$result .= serialize_block( $block );
}
return $result;
}
The function is used to filter block content generated by a user with Contributor role and higher. The sanitization process lacks proper sanitization on the block comments, resulting in a user being able to embed arbitrary content by utilizing the HTML comment.
Potential impact
This allows authenticated user with Contributor role and higher to embed arbitrary content and could potentially lead to Cross Site Scripting if chained with another vulnerability.
The patch
Since the main issue is lack of the sanitization process, implementing more robust sanitization check using regex fixes the issue. The patch can be seen below:

Stored Cross-Site Scripting
See more from Patchstack database
The underlying vulnerable code located in the receiveEmbedMessage
JS function:
window.wp.receiveEmbedMessage = function( e ) {
var data = e.data;
if ( ! data ) {
return;
}
if ( ! ( data.secret || data.message || data.value ) ) {
return;
}
if ( /[^a-zA-Z0-9]/.test( data.secret ) ) {
return;
}
var iframes = document.querySelectorAll( 'iframe[data-secret="' + data.secret + '"]' ),
blockquotes = document.querySelectorAll( 'blockquote[data-secret="' + data.secret + '"]' ),
i, source, height, sourceURL, targetURL;
for ( i = 0; i < blockquotes.length; i++ ) {
blockquotes[ i ].style.display = 'none';
}
for ( i = 0; i < iframes.length; i++ ) {
source = iframes[ i ];
if ( e.source !== source.contentWindow ) {
continue;
}
source.removeAttribute( 'style' );
/* Resize the iframe on request. */
if ( 'height' === data.message ) {
height = parseInt( data.value, 10 );
if ( height > 1000 ) {
height = 1000;
} else if ( ~~height < 200 ) {
height = 200;
}
source.height = height;
}
/* Link to a specific URL on request. */
if ( 'link' === data.message ) {
sourceURL = document.createElement( 'a' );
targetURL = document.createElement( 'a' );
sourceURL.href = source.getAttribute( 'src' );
targetURL.href = data.value;
/* Only continue if link hostname matches iframe's hostname. */
if ( targetURL.host === sourceURL.host ) {
if ( document.activeElement === source ) {
window.top.location.href = data.value;
}
}
}
}
};
The JS function lack of protocol validation check when processing Open Embed auto discovery. The function will act as a receiver and will store the message to the data
variable. If the data.message
value is set to a link, the code will construct a hyperlink via the <a>
element to the targetURL
variable. The code then will assign the targetURL.href
value with the data.value
. Since there is no validation check on the content of data.value
, XSS is possible since the value in the end is set as the window.top.location.href
value.
Potential impact
This allows authenticated user with Contributor role and higher to inject a script into WordPress page via the Open Embed message and could result from stealing sensitive information to potentially privilege escalation on the WordPress site.
The patch
Since the main issue is lack of sanitization on the href value, implementing a protocol check fixes the issue. The patch can be seen below:

Directory Traversal
See more from Patchstack database
The underlying vulnerable code located in the determine_locale
function:
function determine_locale() {
/**
* Filters the locale for the current request prior to the default determination process.
*
* Using this filter allows to override the default logic, effectively short-circuiting the function.
*
* @since 5.0.0
*
* @param string|null $locale The locale to return and short-circuit. Default null.
*/
$determined_locale = apply_filters( 'pre_determine_locale', null );
if ( ! empty( $determined_locale ) && is_string( $determined_locale ) ) {
return $determined_locale;
}
$determined_locale = get_locale();
if ( is_admin() ) {
$determined_locale = get_user_locale();
}
if ( isset( $_GET['_locale'] ) && 'user' === $_GET['_locale'] && wp_is_json_request() ) {
$determined_locale = get_user_locale();
}
$wp_lang = '';
if ( ! empty( $_GET['wp_lang'] ) ) {
$wp_lang = sanitize_text_field( $_GET['wp_lang'] );
} elseif ( ! empty( $_COOKIE['wp_lang'] ) ) {
$wp_lang = sanitize_text_field( $_COOKIE['wp_lang'] );
}
if ( ! empty( $wp_lang ) && ! empty( $GLOBALS['pagenow'] ) && 'wp-login.php' === $GLOBALS['pagenow'] ) {
$determined_locale = $wp_lang;
}
/**
* Filters the locale for the current request.
*
* @since 5.0.0
*
* @param string $locale The locale.
*/
return apply_filters( 'determine_locale', $determined_locale );
}
The function acts as a handler to determines the current locale desired for the request. The locale value could be constructed by $_GET['wp_lang']
or $_COOKIE['wp_lang']
. But the code only uses sanitize_text_field
function to sanitize the value. This function is not sufficient enough to protect against directory traversal attacks, resulting in an unauthenticated user being able to access and load arbitrary locale files.
Potential impact
This allows unauthenticated users to access and load arbitrary locale files. If the user is able to somehow upload a crafted local file to the WordPress site, this issue could potentially lead to Cross Site Scripting.
The patch
Since the main issue is lack of proper sanitization check, implementing a simple regex validation fixes the issue. The patch can be seen below:

Thanks to security contributors!
Credit to the researchers and developers who contribute to making WordPress (and a large portion of the web) more secure: Liam Gladdy (WP Engine), John Blackbourn (WordPress security team), Jakub Żoczek (Securitum), Ramuel Gall (Wordfence).
See the official WordPress.org announcement: https://wordpress.org/news/2023/05/wordpress-6-2-1-maintenance-security-release/