Version in base suite: 6.8.7+dfsg1-0+deb13u1
Base version: wordpress_6.8.7+dfsg1-0+deb13u1
Target version: wordpress_6.8.10+dfsg1-0+deb13u1
Base file: /srv/ftp-master.debian.org/ftp/pool/main/w/wordpress/wordpress_6.8.7+dfsg1-0+deb13u1.dsc
Target file: /srv/ftp-master.debian.org/policy/pool/main/w/wordpress/wordpress_6.8.10+dfsg1-0+deb13u1.dsc
debian/changelog | 30 +
wp-admin/about.php | 60 ++
wp-admin/includes/ajax-actions.php | 24
wp-admin/includes/class-custom-image-header.php | 19
wp-admin/includes/media.php | 2
wp-admin/includes/plugin.php | 8
wp-admin/includes/post.php | 14
wp-admin/js/theme.js | 4
wp-admin/js/theme.min.js | 2
wp-includes/block-template-utils.php | 17
wp-includes/class-wp-image-editor-imagick.php | 260 +++++++++-
wp-includes/class-wp-xmlrpc-server.php | 27 +
wp-includes/customize/class-wp-customize-header-image-setting.php | 170 ++++++
wp-includes/formatting.php | 2
wp-includes/html-api/class-wp-html-tag-processor.php | 2
wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php | 71 ++
wp-includes/template.php | 78 ++-
wp-includes/theme.php | 10
wp-includes/version.php | 2
19 files changed, 749 insertions(+), 53 deletions(-)
dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpeieyxsnc/wordpress_6.8.7+dfsg1-0+deb13u1.dsc: no acceptable signature found
dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpeieyxsnc/wordpress_6.8.10+dfsg1-0+deb13u1.dsc: no acceptable signature found
diff -Nru wordpress-6.8.7+dfsg1/debian/changelog wordpress-6.8.10+dfsg1/debian/changelog
--- wordpress-6.8.7+dfsg1/debian/changelog 2026-08-07 08:13:22.000000000 +0000
+++ wordpress-6.8.10+dfsg1/debian/changelog 2026-09-23 10:30:44.000000000 +0000
@@ -1,9 +1,37 @@
+wordpress (6.8.10+dfsg1-0+deb13u1) trixie-security; urgency=high
+
+ * Three security updates
+ * 6.8.10 backport of security release 7.1.2 Closes: #114873
+ CVE-2026-87902: Unauthenticated path traversal in page-template resolution
+ leading to conditional RCE.
+ * 6.8.9 backport of security release 7.1.1 Closes: #1148368
+ Stored XSS in wpautop() allows an unauthenticated visitor to inject
+ script (subject to comment approval). CVE-2026-93485
+ API: set_modifiable_text() allows breaking out of a comment via
+ abrupt-closing sequences.
+ Stored XSS in some themes that support custom headers.
+ Specially crafted URLs can automatically install and preview an
+ inactive theme from WordPress.org. (Click2Shell)
+ Site Administrator can network-activate an installed Network-only plugin.
+ Authenticated Path Traversal in WP REST Templates Controller.
+ XML-RPC can be used to publish customize_changeset posts that bypass
+ checks for edit_css.
+ Contributor+ Arbitrary Post Overwrite.
+ Missing read_post check in attachment_submitbox_metadata() leaks a
+ private parent-post title.
+ Missing Authorization leads to Draft/Pending Post Slug Disclosure by
+ Contributor+.
+ Comments, including notes, can be reparented by any authenticated user.
+ * 6.8.8 backport of security release 7.0.4
+ Fixes: CVE-2026-65640 Closes: #1144955
+
+ -- Craig Small
+ Version %s addressed some security issues.' ), + '6.8.9' + ); + ?> + the release notes.' ), + sprintf( + /* translators: %s: WordPress version. */ + esc_url( __( 'https://wordpress.org/support/wordpress-version/version-%s/' ) ), + sanitize_title( '6.8.9' ) + ) + ); + ?> +
++ Version %s addressed some security issues.' ), + '6.8.8' + ); + ?> + the release notes.' ), + sprintf( + /* translators: %s: WordPress version. */ + esc_url( __( 'https://wordpress.org/support/wordpress-version/version-%s/' ) ), + sanitize_title( '6.8.8' ) + ) + ); + ?> +
++ Version %s addressed some security issues.' ), '6.8.7' ); diff -Nru wordpress-6.8.7+dfsg1/wp-admin/includes/ajax-actions.php wordpress-6.8.10+dfsg1/wp-admin/includes/ajax-actions.php --- wordpress-6.8.7+dfsg1/wp-admin/includes/ajax-actions.php 2024-10-25 20:26:20.107352000 +0000 +++ wordpress-6.8.10+dfsg1/wp-admin/includes/ajax-actions.php 2026-09-17 18:21:54.640900000 +0000 @@ -2032,6 +2032,13 @@ function wp_ajax_get_permalink() { check_ajax_referer( 'getpermalink', 'getpermalinknonce' ); $post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0; + if ( ! $post_id ) { + // Bypass call to get_preview_post_link() for unspecified post ID. + wp_die( '' ); + } + if ( ! current_user_can( 'edit_post', $post_id ) ) { + wp_die( -1 ); + } wp_die( get_preview_post_link( $post_id ) ); } @@ -2043,8 +2050,15 @@ function wp_ajax_sample_permalink() { check_ajax_referer( 'samplepermalink', 'samplepermalinknonce' ); $post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0; - $title = isset( $_POST['new_title'] ) ? $_POST['new_title'] : ''; - $slug = isset( $_POST['new_slug'] ) ? $_POST['new_slug'] : null; + if ( ! $post_id ) { + // Bypass call to get_sample_permalink_html() for unspecified post ID. + wp_die( '' ); + } + if ( ! current_user_can( 'edit_post', $post_id ) ) { + wp_die( -1 ); + } + $title = isset( $_POST['new_title'] ) ? $_POST['new_title'] : ''; + $slug = isset( $_POST['new_slug'] ) ? $_POST['new_slug'] : null; wp_die( get_sample_permalink_html( $post_id, $title, $slug ) ); } @@ -4578,6 +4592,12 @@ wp_send_json_error( $status ); } + // A network-only plugin is activated for the entire network. + if ( is_multisite() && is_network_only_plugin( $status['plugin'] ) && ! current_user_can( 'manage_network_plugins' ) ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to activate this plugin.' ); + wp_send_json_error( $status ); + } + if ( is_plugin_active( $status['plugin'] ) ) { $status['errorMessage'] = sprintf( /* translators: %s: Plugin name. */ diff -Nru wordpress-6.8.7+dfsg1/wp-admin/includes/class-custom-image-header.php wordpress-6.8.10+dfsg1/wp-admin/includes/class-custom-image-header.php --- wordpress-6.8.7+dfsg1/wp-admin/includes/class-custom-image-header.php 2025-02-08 15:53:17.750427000 +0000 +++ wordpress-6.8.10+dfsg1/wp-admin/includes/class-custom-image-header.php 2026-09-17 18:21:54.640900000 +0000 @@ -572,7 +572,7 @@ $header_image_style .= 'height:' . $custom_header->height . 'px;'; } ?> -
'+a.error+'
'+a.error+'
(
is wrapped with a, move it inside the
. - $text = preg_replace( '|]*)>|i', '', $text ); + $text = preg_replace( '!
"\']|"[^"]*"|\'[^\']*\')*)>!i', '', $text ); // If an opening or closing block element tag is preceded by an opening', '', $text ); $text = str_replace( '
tag, remove it. diff -Nru wordpress-6.8.7+dfsg1/wp-includes/html-api/class-wp-html-tag-processor.php wordpress-6.8.10+dfsg1/wp-includes/html-api/class-wp-html-tag-processor.php --- wordpress-6.8.7+dfsg1/wp-includes/html-api/class-wp-html-tag-processor.php 2026-03-10 16:37:37.009287000 +0000 +++ wordpress-6.8.10+dfsg1/wp-includes/html-api/class-wp-html-tag-processor.php 2026-09-17 18:21:54.640900000 +0000 @@ -3706,7 +3706,7 @@ self::COMMENT_AS_HTML_COMMENT === $this->comment_type ) { // Check if the text could close the comment. - if ( 1 === preg_match( '/--!?>/', $plaintext_content ) ) { + if ( 1 === preg_match( '/^-?>|--!?>/', $plaintext_content ) ) { return false; } diff -Nru wordpress-6.8.7+dfsg1/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php wordpress-6.8.10+dfsg1/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php --- wordpress-6.8.7+dfsg1/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php 2025-03-11 14:19:20.051252000 +0000 +++ wordpress-6.8.10+dfsg1/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php 2026-09-17 18:21:54.640900000 +0000 @@ -781,6 +781,7 @@ * Checks if a given REST request has access to update a comment. * * @since 4.7.0 + * @since 7.1.0 Target post permissions are checked when a comment's parent post is changed. * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, error object otherwise. @@ -799,6 +800,22 @@ ); } + /* + * check_edit_permission() above only establishes that the comment may be + * edited where it currently sits, because 'edit_comment' maps to 'edit_post' + * on the comment's current parent. When the parent is being changed, the new + * parent has to be authorized as well. Without this, a user holding + * edit_comment on their own comment could reparent it onto any post, + * including posts they can neither read nor edit. + */ + if ( isset( $request['post'] ) && (int) $request['post'] !== (int) $comment->comment_post_ID ) { + $target_check = $this->check_target_post_permission( (int) $request['post'] ); + + if ( is_wp_error( $target_check ) ) { + return $target_check; + } + } + return true; } @@ -1928,4 +1945,58 @@ */ return '' !== $check['comment_content']; } + + /** + * Checks that a post can receive a comment from the current user. + * + * Used when changing the parent post of an existing comment, so that + * attaching a comment to a post is authorized the same way whichever + * path it arrives by. + * + * @since 7.1.0 + * + * @param int $post_id Target post ID. + * @return true|WP_Error True if the post can receive the comment, error object otherwise. + */ + protected function check_target_post_permission( $post_id ) { + if ( ! $post_id ) { + return new WP_Error( + 'rest_comment_invalid_post_id', + __( 'Sorry, you are not allowed to create this comment without a post.' ), + array( 'status' => 403 ) + ); + } + + /* + * A comment needs either comment moderation rights or edit access to the + * post, which is what check_edit_permission() grants on the post a comment + * is moving away from. Requiring the same at the destination means both + * ends of a move are authorized alike. + */ + if ( ! current_user_can( 'moderate_comments' ) && ! current_user_can( 'edit_post', $post_id ) ) { + return new WP_Error( + 'rest_cannot_edit', + __( 'Sorry, you are not allowed to edit this comment.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + $post = get_post( $post_id ); + + if ( ! $post ) { + return new WP_Error( + 'rest_comment_invalid_post_id', + __( 'Sorry, you are not allowed to create this comment without a post.' ), + array( 'status' => 403 ) + ); + } + + /* + * The create-time draft and comments-open rules are deliberately not applied + * here, because moderators move comments onto posts whose discussion has + * closed and onto drafts today. Enforcing them would break that without + * blocking anything the capability check above already permits. + */ + return true; + } } diff -Nru wordpress-6.8.7+dfsg1/wp-includes/template.php wordpress-6.8.10+dfsg1/wp-includes/template.php --- wordpress-6.8.7+dfsg1/wp-includes/template.php 2024-02-21 19:26:08.320857000 +0000 +++ wordpress-6.8.10+dfsg1/wp-includes/template.php 2026-09-22 12:43:47.816954000 +0000 @@ -486,7 +486,7 @@ } if ( $pagename ) { $pagename_decoded = urldecode( $pagename ); - if ( $pagename_decoded !== $pagename ) { + if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) { $templates[] = "page-{$pagename_decoded}.php"; } $templates[] = "page-{$pagename}.php"; @@ -695,6 +695,67 @@ } /** + * Determines whether a template found by locate_template() may be loaded. + * + * @since 7.1.2 + * @access private + * + * @global string $wp_stylesheet_path Path to current theme's stylesheet directory. + * @global string $wp_template_path Path to current theme's template directory. + * + * @param string $path Path to an existing template file. + * @return bool Whether the template may be loaded. + */ +function _wp_is_template_path_allowed( $path ) { + global $wp_stylesheet_path, $wp_template_path; + + // A file path that exists and does not contain `..` is allowed. + if ( 0 === preg_match( '#(?:^|/)\.\.[. ]*(?:/|$)#', wp_normalize_path( $path ) ) ) { + return true; + } + + // Resolve the true location of the requested file for later comparison. + $real_path = realpath( $path ); + + if ( false === $real_path ) { + return false; + } + + $real_path = trailingslashit( wp_normalize_path( $real_path ) ); + + $directories = array( + $wp_stylesheet_path, + $wp_template_path, + ABSPATH . WPINC . '/theme-compat', + ); + + // If a theme is in a subdirectory, accept templates from its direct parent directory. + if ( str_contains( get_stylesheet(), '/' ) ) { + $directories[] = dirname( $wp_stylesheet_path ); + } + + // If a parent theme is in a subdirectory, accept templates from its direct parent directory. + if ( str_contains( get_template(), '/' ) ) { + $directories[] = dirname( $wp_template_path ); + } + + foreach ( $directories as $directory ) { + $real_directory = realpath( $directory ); + + if ( false === $real_directory ) { + continue; + } + + // The true location of the requested file must be inside one of the allowed directories. + if ( str_starts_with( $real_path, trailingslashit( wp_normalize_path( $real_directory ) ) ) ) { + return true; + } + } + + return false; +} + +/** * Retrieves the name of the highest priority template file that exists. * * Searches in the stylesheet directory before the template directory and @@ -703,6 +764,7 @@ * * @since 2.7.0 * @since 5.5.0 The `$args` parameter was added. + * @since 7.1.2 A template name containing `..` is only located if it resolves inside the theme. * * @global string $wp_stylesheet_path Path to current theme's stylesheet directory. * @global string $wp_template_path Path to current theme's template directory. @@ -730,13 +792,17 @@ continue; } if ( file_exists( $wp_stylesheet_path . '/' . $template_name ) ) { - $located = $wp_stylesheet_path . '/' . $template_name; - break; + $candidate = $wp_stylesheet_path . '/' . $template_name; } elseif ( $is_child_theme && file_exists( $wp_template_path . '/' . $template_name ) ) { - $located = $wp_template_path . '/' . $template_name; - break; + $candidate = $wp_template_path . '/' . $template_name; } elseif ( file_exists( ABSPATH . WPINC . '/theme-compat/' . $template_name ) ) { - $located = ABSPATH . WPINC . '/theme-compat/' . $template_name; + $candidate = ABSPATH . WPINC . '/theme-compat/' . $template_name; + } else { + continue; + } + + if ( _wp_is_template_path_allowed( $candidate ) ) { + $located = $candidate; break; } } diff -Nru wordpress-6.8.7+dfsg1/wp-includes/theme.php wordpress-6.8.10+dfsg1/wp-includes/theme.php --- wordpress-6.8.7+dfsg1/wp-includes/theme.php 2025-03-18 12:30:25.301830000 +0000 +++ wordpress-6.8.10+dfsg1/wp-includes/theme.php 2026-09-17 18:21:54.640900000 +0000 @@ -1547,6 +1547,7 @@ * Gets the header image data. * * @since 3.4.0 + * @since 7.1.1 The `width` and `height` are cast to non-negative integers. * * @global array $_wp_default_headers * @@ -1585,7 +1586,14 @@ 'height' => get_theme_support( 'custom-header', 'height' ), 'video' => get_theme_support( 'custom-header', 'video' ), ); - return (object) wp_parse_args( $data, $default ); + + if ( ! is_array( $data ) && ! is_object( $data ) ) { + $data = array(); + } + $header = (object) wp_parse_args( $data, $default ); + $header->width = absint( $header->width ); + $header->height = absint( $header->height ); + return $header; } /** diff -Nru wordpress-6.8.7+dfsg1/wp-includes/version.php wordpress-6.8.10+dfsg1/wp-includes/version.php --- wordpress-6.8.7+dfsg1/wp-includes/version.php 2026-08-06 18:10:34.000000000 +0000 +++ wordpress-6.8.10+dfsg1/wp-includes/version.php 2026-09-22 13:00:22.000000000 +0000 @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.8.7'; +$wp_version = '6.8.10'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.