diff --git a/src/wp-includes/customize/class-wp-customize-custom-css-setting.php b/src/wp-includes/customize/class-wp-customize-custom-css-setting.php
index aab0e475304ea..0d8324e367a3a 100644
--- a/src/wp-includes/customize/class-wp-customize-custom-css-setting.php
+++ b/src/wp-includes/customize/class-wp-customize-custom-css-setting.php
@@ -144,35 +144,6 @@ public function value() {
return $value;
}
- /**
- * Validate a received value for being valid CSS.
- *
- * Checks for imbalanced braces, brackets, and comments.
- * Notifications are rendered when the customizer state is saved.
- *
- * @since 4.7.0
- * @since 4.9.0 Checking for balanced characters has been moved client-side via linting in code editor.
- * @since 5.9.0 Renamed `$css` to `$value` for PHP 8 named parameter support.
- *
- * @param string $value CSS to validate.
- * @return true|WP_Error True if the input was validated, otherwise WP_Error.
- */
- public function validate( $value ) {
- // Restores the more descriptive, specific name for use within this method.
- $css = $value;
-
- $validity = new WP_Error();
-
- if ( preg_match( '#?\w+#', $css ) ) {
- $validity->add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) );
- }
-
- if ( ! $validity->has_errors() ) {
- $validity = parent::validate( $css );
- }
- return $validity;
- }
-
/**
* Store the CSS setting value in the custom_css custom post type for the stylesheet.
*
diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php
index 0ff915cbe4263..bb6498f23dbae 100644
--- a/src/wp-includes/theme.php
+++ b/src/wp-includes/theme.php
@@ -2144,6 +2144,13 @@ function wp_update_custom_css_post( $css, $args = array() ) {
// Update post if it already exists, otherwise create a new one.
$post = wp_get_custom_css_post( $args['stylesheet'] );
+
+ // Remove KSES HTML filters to prevent CSS mangling.
+ $priority = has_filter( 'content_save_pre', 'wp_filter_post_kses' );
+ if ( false !== $priority ) {
+ remove_filter( 'content_save_pre', 'wp_filter_post_kses', $priority );
+ }
+
if ( $post ) {
$post_data['ID'] = $post->ID;
$r = wp_update_post( wp_slash( $post_data ), true );
@@ -2163,6 +2170,10 @@ function wp_update_custom_css_post( $css, $args = array() ) {
}
}
+ if ( false !== $priority ) {
+ add_filter( 'content_save_pre', 'wp_filter_post_kses', $priority );
+ }
+
if ( is_wp_error( $r ) ) {
return $r;
}
diff --git a/tests/phpunit/tests/customize/custom-css-setting.php b/tests/phpunit/tests/customize/custom-css-setting.php
index 65cc3f717fe59..e478f73a41c3f 100644
--- a/tests/phpunit/tests/customize/custom-css-setting.php
+++ b/tests/phpunit/tests/customize/custom-css-setting.php
@@ -268,6 +268,27 @@ public function test_get_custom_css_post_queries_after_failed_lookup() {
$this->assertSame( get_num_queries(), $queries_before );
}
+ /**
+ * Ensure that dangerous STYLE tag contents do not break HTML output.
+ *
+ * @ticket 64418
+ */
+ public function test_wp_custom_css_cb_escapes_dangerous_html() {
+ wp_update_custom_css_post(
+ '*::before { content: ""; }',
+ array(
+ 'stylesheet' => $this->setting->stylesheet,
+ )
+ );
+ $output = get_echo( 'wp_custom_css_cb' );
+ $expected = <<<'HTML'
+
+HTML;
+ $this->assertEqualHTML( $expected, $output );
+ }
+
/**
* Test that wp_update_custom_css_post() updates the 'custom_css_post_id' theme mod.
*
@@ -373,29 +394,4 @@ public function filter_update_custom_css_data( $data, $args ) {
$data['post_title'] = 'Ignored';
return $data;
}
-
- /**
- * Tests that validation errors are caught appropriately.
- *
- * Note that the $validity \WP_Error object must be reset each time
- * as it picks up the Errors and passes them to the next assertion.
- *
- * @covers WP_Customize_Custom_CSS_Setting::validate
- */
- public function test_validate() {
-
- // Empty CSS throws no errors.
- $result = $this->setting->validate( '' );
- $this->assertTrue( $result );
-
- // Basic, valid CSS throws no errors.
- $basic_css = 'body { background: #f00; } h1.site-title { font-size: 36px; } a:hover { text-decoration: none; } input[type="text"] { padding: 1em; }';
- $result = $this->setting->validate( $basic_css );
- $this->assertTrue( $result );
-
- // Check for markup.
- $unclosed_comment = $basic_css . '';
- $result = $this->setting->validate( $unclosed_comment );
- $this->assertArrayHasKey( 'illegal_markup', $result->errors );
- }
}