Make WordPress Core

Opened 4 weeks ago

Last modified 4 weeks ago

#65871 new defect (bug)

KSES: the filter property is allowlisted but every filter function value is dropped by safecss_filter_attr()

Reported by: bejignesh Owned by:
Priority: normal Milestone: Awaiting Review
Component: Formatting Version: 5.9
Severity: normal Keywords: has-patch has-unit-tests
Cc: Focuses:

Description

Follow-up to #65832.

filter has been in the safecss_filter_attr() property allowlist since [52049] (#54336),
and [55564] (#57780) made url() values survive. The CSS filter functions were never added
to the function-stripping regular expression at wp-includes/kses.php:3018, so a declaration
using one still contains a ( when it reaches the check on line 3044 and is dropped in full.

Measured against trunk at [63180], PHP 8.3:

filter: blur(5px)                          => dropped
filter: brightness(0.4)                    => dropped
filter: contrast(200%)                     => dropped
filter: drop-shadow(16px 16px 20px blue)   => dropped
filter: grayscale(50%)                     => dropped
filter: hue-rotate(90deg)                  => kept
filter: invert(75%)                        => dropped
filter: opacity(25%)                       => dropped
filter: saturate(30%)                      => dropped
filter: sepia(60%)                         => dropped
filter: blur(5px) brightness(0.4)          => dropped
filter: url(#svg-blur)                     => kept
filter: none                               => kept

The one function that survives shows this is a gap rather than a policy. [63180] added the
transform functions, including rotate, and \brotate( matches inside hue-rotate(90deg)
because the hyphen is a word boundary. The function is stripped from the test string by
accident, leaving filter: hue- there, and the declaration passes. Nothing was decided
about hue-rotate specifically, and blur() is no more or less safe than it is.

This is the same "allowlisted in name only" state #65832 described for the SVG presentation
properties, with the same effect: for a user without unfiltered_html the property is
unusable in its canonical form.

Scope

backdrop-filter is not in the allowlist at all, so it is out of scope here.
The colour functions rgb(), rgba(), hsl(), color-mix() and friends are also dropped
everywhere, but that is #56391 and is not touched by this.

Patch adds the ten filter functions to the same alternation [63180] extended, and covers
each of them plus filter: none, filter: url(), a javascript: url and the unrelated
opacity property in the existing data provider.

Change History (1)

This ticket was mentioned in PR #13039 on WordPress/wordpress-develop by @bejignesh.


4 weeks ago
#1

  • Keywords has-patch has-unit-tests added

filter has been in the safecss_filter_attr() property allowlist since [52049], and
[55564] made url() values survive, but the CSS filter functions were never added to the
function-stripping expression at wp-includes/kses.php:3018. A declaration using one still
contains a ( when it reaches the check below it, so it is dropped in full and the property
is unusable in its canonical form for anyone without unfiltered_html.

Measured on trunk at [63180], PHP 8.3:

Declaration Before After
filter: blur(5px) dropped kept
filter: brightness(0.4) dropped kept
filter: contrast(200%) dropped kept
filter: drop-shadow(16px 16px 20px blue) dropped kept
filter: grayscale(50%) dropped kept
filter: hue-rotate(90deg) kept kept
filter: invert(75%) dropped kept
filter: opacity(25%) dropped kept
filter: saturate(30%) dropped kept
filter: sepia(60%) dropped kept
filter: blur(5px) brightness(0.4) dropped kept
filter: url(#svg-blur) kept kept
filter: none kept kept

hue-rotate() already passed, but not by design: [63180] added rotate for the transform
functions and \brotate( matches inside hue-rotate(90deg) because the hyphen is a word
boundary, leaving filter: hue- in the test string. It is now matched by its own name.

This is the same change [63180] made for the transform and basic shape functions. The value
is still re-emitted from the original declaration, url() values still go through
wp_kses_bad_protocol(), and the test string check is unchanged, so the security model is
the same.

Not in scope: backdrop-filter is not allowlisted at all, and the colour functions rgb(),
rgba(), hsl() and color-mix() are dropped everywhere, which is #56391.

## Testing

12 of the new cases fail on trunk without the kses.php change:

1) Tests_Kses::test_safecss_filter_attr with data set #138 ('filter: blur(5px)', 'filter: blur(5px)')
...
FAILURES!
Tests: 163, Assertions: 163, Failures: 12.

With the patch the full kses group is green, 487 tests / 1562 assertions, and phpcs
passes on both changed files.

## Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: Drafting this description and a first pass at the test cases. I measured the
before and after behaviour myself, confirmed the tests fail without the patch, ran the
kses group and phpcs, and I take responsibility for the change.

Note: See TracTickets for help on using tickets.