diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 92f866b15daef..6bdd8a7abfc2a 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -2906,6 +2906,16 @@ function safecss_filter_attr( $css, $deprecated = '' ) { 'list-style', 'list-style-image', + + // SVG presentation properties that accept url() references. + 'clip-path', + 'fill', + 'marker', + 'marker-end', + 'marker-mid', + 'marker-start', + 'mask', + 'stroke', ); /* @@ -3004,7 +3014,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) { * Nested functions and parentheses are also removed, so long as the parentheses are balanced. */ $css_test_string = preg_replace( - '/\b(?:var|calc|min|max|minmax|clamp|repeat)(\((?:[^()]|(?1))*\))/', + '/\b(?:var|calc|min|max|minmax|clamp|repeat|rotate|rotateX|rotateY|rotateZ|rotate3d|translate|translateX|translateY|translateZ|translate3d|scale|scaleX|scaleY|scaleZ|scale3d|skew|skewX|skewY|matrix|matrix3d|perspective|inset|circle|ellipse|polygon|path)(\((?:[^()]|(?1))*\))/', '', $css_test_string ); diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index f560d88403524..02dc7768097f4 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -1224,6 +1224,7 @@ public function test_wp_kses_attr_no_attributes_allowed_with_false() { * @ticket 64414 * @ticket 65457 * @ticket 64974 + * @ticket 65832 * * @dataProvider data_safecss_filter_attr * @@ -1766,6 +1767,100 @@ public function data_safecss_filter_attr() { 'css' => 'text-anchor: middle', 'expected' => 'text-anchor: middle', ), + // SVG transform functions (ticket #65832). + array( + 'css' => 'transform: rotate(45deg)', + 'expected' => 'transform: rotate(45deg)', + ), + array( + 'css' => 'transform: translate(10px, 20px)', + 'expected' => 'transform: translate(10px, 20px)', + ), + array( + 'css' => 'transform: scale(1.5)', + 'expected' => 'transform: scale(1.5)', + ), + array( + 'css' => 'transform: matrix(1, 0, 0, 1, 10, 20)', + 'expected' => 'transform: matrix(1, 0, 0, 1, 10, 20)', + ), + array( + 'css' => 'transform: skewX(30deg)', + 'expected' => 'transform: skewX(30deg)', + ), + array( + 'css' => 'transform: skewY(30deg)', + 'expected' => 'transform: skewY(30deg)', + ), + // Multiple transform functions chained. + array( + 'css' => 'transform: rotate(45deg) scale(1.5)', + 'expected' => 'transform: rotate(45deg) scale(1.5)', + ), + // transform: none is unchanged (regression control). + array( + 'css' => 'transform: none', + 'expected' => 'transform: none', + ), + // SVG clip-path shape functions (ticket #65832). + array( + 'css' => 'clip-path: inset(10px)', + 'expected' => 'clip-path: inset(10px)', + ), + array( + 'css' => 'clip-path: circle(50%)', + 'expected' => 'clip-path: circle(50%)', + ), + array( + 'css' => 'clip-path: ellipse(25% 40% at 50% 50%)', + 'expected' => 'clip-path: ellipse(25% 40% at 50% 50%)', + ), + array( + 'css' => 'clip-path: polygon(50% 0%, 100% 100%, 0% 100%)', + 'expected' => 'clip-path: polygon(50% 0%, 100% 100%, 0% 100%)', + ), + // SVG url() references for allowlisted properties (ticket #65832). + array( + 'css' => 'clip-path: url(#myClipper)', + 'expected' => 'clip-path: url(#myClipper)', + ), + array( + 'css' => 'fill: url(#gradient1)', + 'expected' => 'fill: url(#gradient1)', + ), + array( + 'css' => 'mask: url(#myMask)', + 'expected' => 'mask: url(#myMask)', + ), + array( + 'css' => 'marker-start: url(#arrowStart)', + 'expected' => 'marker-start: url(#arrowStart)', + ), + array( + 'css' => 'marker-end: url(#arrowEnd)', + 'expected' => 'marker-end: url(#arrowEnd)', + ), + array( + 'css' => 'marker-mid: url(#arrowMid)', + 'expected' => 'marker-mid: url(#arrowMid)', + ), + array( + 'css' => 'marker: url(#marker1)', + 'expected' => 'marker: url(#marker1)', + ), + array( + 'css' => 'stroke: url(#strokeGradient)', + 'expected' => 'stroke: url(#strokeGradient)', + ), + // Disallow javascript: URLs in SVG url() references (security regression). + array( + 'css' => 'fill: url(javascript:alert(1))', + 'expected' => '', + ), + array( + 'css' => 'clip-path: url(javascript:alert(1))', + 'expected' => '', + ), ); }