Changeset 61527
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/customize/class-wp-customize-custom-css-setting.php
r60215 r61527 154 154 * @since 4.9.0 Checking for balanced characters has been moved client-side via linting in code editor. 155 155 * @since 5.9.0 Renamed `$css` to `$value` for PHP 8 named parameter support. 156 * @since 7.0.0 Only restricts contents which risk prematurely closing the STYLE element, 157 * either through a STYLE end tag or a prefix of one which might become a 158 * full end tag when combined with the contents of other styles. 159 * 160 * @see WP_REST_Global_Styles_Controller::validate_custom_css() 156 161 * 157 162 * @param string $value CSS to validate. … … 164 169 $validity = new WP_Error(); 165 170 166 if ( preg_match( '#</?\w+#', $css ) ) { 167 $validity->add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) ); 171 $length = strlen( $css ); 172 for ( 173 $at = strcspn( $css, '<' ); 174 $at < $length; 175 $at += strcspn( $css, '<', ++$at ) 176 ) { 177 $remaining_strlen = $length - $at; 178 /** 179 * Custom CSS text is expected to render inside an HTML STYLE element. 180 * A STYLE closing tag must not appear within the CSS text because it 181 * would close the element prematurely. 182 * 183 * The text must also *not* end with a partial closing tag (e.g., `<`, 184 * `</`, … `</style`) because subsequent styles which are concatenated 185 * could complete it, forming a valid `</style>` tag. 186 * 187 * Example: 188 * 189 * $style_a = 'p { font-weight: bold; </sty'; 190 * $style_b = 'le> gotcha!'; 191 * $combined = "{$style_a}{$style_b}"; 192 * 193 * $style_a = 'p { font-weight: bold; </style'; 194 * $style_b = 'p > b { color: red; }'; 195 * $combined = "{$style_a}\n{$style_b}"; 196 * 197 * Note how in the second example, both of the style contents are benign 198 * when analyzed on their own. The first style was likely the result of 199 * improper truncation, while the second is perfectly sound. It was only 200 * through concatenation that these two styles combined to form content 201 * that would have broken out of the containing STYLE element, thus 202 * corrupting the page and potentially introducing security issues. 203 * 204 * @see https://html.spec.whatwg.org/multipage/parsing.html#rawtext-end-tag-name-state 205 */ 206 $possible_style_close_tag = 0 === substr_compare( 207 $css, 208 '</style', 209 $at, 210 min( 7, $remaining_strlen ), 211 true 212 ); 213 if ( $possible_style_close_tag ) { 214 if ( $remaining_strlen < 8 ) { 215 $validity->add( 216 'illegal_markup', 217 sprintf( 218 /* translators: %s is the CSS that was provided. */ 219 __( 'The CSS must not end in "%s".' ), 220 esc_html( substr( $css, $at ) ) 221 ) 222 ); 223 break; 224 } 225 226 if ( 1 === strspn( $css, " \t\f\r\n/>", $at + 7, 1 ) ) { 227 $validity->add( 228 'illegal_markup', 229 sprintf( 230 /* translators: %s is the CSS that was provided. */ 231 __( 'The CSS must not contain "%s".' ), 232 esc_html( substr( $css, $at, 8 ) ) 233 ) 234 ); 235 break; 236 } 237 } 168 238 } 169 239 -
trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php
r61486 r61527 674 674 * either through a STYLE end tag or a prefix of one which might become a 675 675 * full end tag when combined with the contents of other styles. 676 * 677 * @see WP_Customize_Custom_CSS_Setting::validate() 676 678 * 677 679 * @param string $css CSS to validate. … … 708 710 * when analyzed on their own. The first style was likely the result of 709 711 * improper truncation, while the second is perfectly sound. It was only 710 * through concatenation that these two s cripts combined to form content712 * through concatenation that these two styles combined to form content 711 713 * that would have broken out of the containing STYLE element, thus 712 714 * corrupting the page and potentially introducing security issues. -
trunk/tests/phpunit/tests/customize/custom-css-setting.php
r60253 r61527 376 376 377 377 /** 378 * Ensure that dangerous STYLE tag contents do not break HTML output. 379 * 380 * @ticket 64418 381 * @covers ::wp_update_custom_css_post 382 * @covers ::wp_custom_css_cb 383 */ 384 public function test_wp_custom_css_cb_escapes_dangerous_html() { 385 wp_update_custom_css_post( 386 '*::before { content: "</style><script>alert(1)</script>"; }', 387 array( 388 'stylesheet' => $this->setting->stylesheet, 389 ) 390 ); 391 $output = get_echo( 'wp_custom_css_cb' ); 392 $expected = 393 <<<'HTML' 394 <style id="wp-custom-css"> 395 *::before { content: "\3c\2fstyle><script>alert(1)</script>"; } 396 </style> 397 398 HTML; 399 $this->assertEqualHTML( $expected, $output ); 400 } 401 402 /** 378 403 * Tests that validation errors are caught appropriately. 379 404 * … … 383 408 * @covers WP_Customize_Custom_CSS_Setting::validate 384 409 */ 385 public function test_validate() { 386 410 public function test_validate_basic_css() { 387 411 // Empty CSS throws no errors. 388 412 $result = $this->setting->validate( '' ); … … 394 418 $this->assertTrue( $result ); 395 419 396 // Check for markup.420 // Check for illegal closing STYLE tag. 397 421 $unclosed_comment = $basic_css . '</style>'; 398 422 $result = $this->setting->validate( $unclosed_comment ); 399 423 $this->assertArrayHasKey( 'illegal_markup', $result->errors ); 400 424 } 425 426 /** 427 * @ticket 64418 428 * @covers WP_Customize_Custom_CSS_Setting::validate 429 */ 430 public function test_validate_accepts_css_property_at_rule() { 431 $css = 432 <<<'CSS' 433 @property --animate { 434 syntax: "<custom-ident>"; 435 inherits: true; 436 initial-value: false; 437 } 438 CSS; 439 $this->assertTrue( $this->setting->validate( $css ) ); 440 } 441 442 /** 443 * @ticket 64418 444 * @covers ::wp_update_custom_css_post 445 * @covers ::wp_custom_css_cb 446 */ 447 public function test_save_and_print_property_at_rule() { 448 $css = 449 <<<'CSS' 450 @property --animate { 451 syntax: "<custom-ident>"; 452 inherits: true; 453 initial-value: false; 454 } 455 CSS; 456 wp_update_custom_css_post( $css, array( 'stylesheet' => $this->setting->stylesheet ) ); 457 $output = get_echo( 'wp_custom_css_cb' ); 458 $expected = "<style id='wp-custom-css'>\n{$css}\n</style>\n"; 459 $this->assertEqualHTML( $expected, $output ); 460 } 461 462 /** 463 * @dataProvider data_custom_css_disallowed 464 * 465 * @ticket 64418 466 * @covers WP_Customize_Custom_CSS_Setting::validate 467 */ 468 public function test_validate_prevents( $css, $expected_error_message ) { 469 $result = $this->setting->validate( $css ); 470 $this->assertWPError( $result ); 471 $this->assertSame( $expected_error_message, $result->get_error_message() ); 472 } 473 474 /** 475 * Data provider. 476 * 477 * @return array<string, string[]> 478 */ 479 public static function data_custom_css_disallowed(): array { 480 return array( 481 'style close tag' => array( 'css…</style>…css', 'The CSS must not contain "</style>".' ), 482 'style close tag upper case' => array( '</STYLE>', 'The CSS must not contain "</STYLE>".' ), 483 'style close tag mixed case' => array( '</sTyLe>', 'The CSS must not contain "</sTyLe>".' ), 484 'style close tag in comment' => array( '/*</style>*/', 'The CSS must not contain "</style>".' ), 485 'style close tag (/)' => array( '</style/', 'The CSS must not contain "</style/".' ), 486 'style close tag (\t)' => array( "</style\t", "The CSS must not contain \"</style\t\"." ), 487 'style close tag (\f)' => array( "</style\f", "The CSS must not contain \"</style\f\"." ), 488 'style close tag (\r)' => array( "</style\r", "The CSS must not contain \"</style\r\"." ), 489 'style close tag (\n)' => array( "</style\n", "The CSS must not contain \"</style\n\"." ), 490 'style close tag (" ")' => array( '</style ', 'The CSS must not contain "</style ".' ), 491 'truncated "<"' => array( '<', 'The CSS must not end in "<".' ), 492 'truncated "</"' => array( '</', 'The CSS must not end in "</".' ), 493 'truncated "</s"' => array( '</s', 'The CSS must not end in "</s".' ), 494 'truncated "</ST"' => array( '</ST', 'The CSS must not end in "</ST".' ), 495 'truncated "</sty"' => array( '</sty', 'The CSS must not end in "</sty".' ), 496 'truncated "</STYL"' => array( '</STYL', 'The CSS must not end in "</STYL".' ), 497 'truncated "</stYle"' => array( '</stYle', 'The CSS must not end in "</stYle".' ), 498 ); 499 } 401 500 }
Note: See TracChangeset
for help on using the changeset viewer.