KSES: Allow attributes to be restricted via callbacks.

Add callback validation to HTML tag attributes for increased flexibility over an array of values only.

In `object` tags, validate the `data` attribute via a callback to ensure it is a PDF and matches the `type` attribute. This prevents mime type mismatches in browsers.

Follow up to [51963].

Props Pento, dd32, swissspidy, xknown, peterwilsoncc.
Fixes #54261.



git-svn-id: https://develop.svn.wordpress.org/trunk@52304 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Peter Wilson 2021-12-02 00:54:03 +00:00
parent 77059b1723
commit 5fa8d73b1b
2 changed files with 90 additions and 21 deletions

View File

@ -272,7 +272,10 @@ if ( ! CUSTOM_TAGS ) {
'xml:lang' => true,
),
'object' => array(
'data' => true,
'data' => array(
'required' => true,
'value_callback' => '_wp_kses_allow_pdf_objects',
),
'type' => array(
'required' => true,
'values' => array( 'application/pdf' ),
@ -1661,6 +1664,17 @@ function wp_kses_check_attr_val( $value, $vless, $checkname, $checkvalue ) {
$ok = false;
}
break;
case 'value_callback':
/*
* The value_callback check is used when you want to make sure that the attribute
* value is accepted by the callback function.
*/
if ( ! call_user_func( $checkvalue, $value ) ) {
$ok = false;
}
break;
} // End switch.
return $ok;
@ -2566,3 +2580,34 @@ function _wp_add_global_attributes( $value ) {
return $value;
}
/**
* Helper function to check if this is a safe PDF URL.
*
* @since 5.9.0
* @access private
* @ignore
*
* @param string $url The URL to check.
* @return bool True if the URL is safe, false otherwise.
*/
function _wp_kses_allow_pdf_objects( $value ) {
// We're not interested in URLs that contain query strings or fragments.
if ( strpos( $value, '?' ) !== false || strpos( $value, '#' ) !== false ) {
return false;
}
// If it doesn't have a PDF extension, it's not safe.
if ( 0 !== substr_compare( $value, '.pdf', -4, 4, true ) ) {
return false;
}
// If the URL host matches the current site's media URL, it's safe.
$upload_info = wp_upload_dir( null, false );
$upload_host = wp_parse_url( $upload_info['url'], PHP_URL_HOST );
if ( 0 === strpos( $value, "http://$upload_host/" ) || 0 === strpos( $value, "https://$upload_host/" ) ) {
return true;
}
return false;
}

View File

@ -1517,59 +1517,83 @@ EOF;
function data_wp_kses_object_tag_allowed() {
return array(
'valid value for type' => array(
'<object type="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object type="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object type="application/pdf" data="https://example.org/foo.pdf" />',
'<object type="application/pdf" data="https://example.org/foo.pdf" />',
),
'invalid value for type' => array(
'<object type="application/exe" data="https://wordpress.org/foo.exe" />',
'<object type="application/exe" data="https://example.org/foo.exe" />',
'',
),
'multiple type attributes, last invalid' => array(
'<object type="application/pdf" type="application/exe" data="https://wordpress.org/foo.pdf" />',
'<object type="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object type="application/pdf" type="application/exe" data="https://example.org/foo.pdf" />',
'<object type="application/pdf" data="https://example.org/foo.pdf" />',
),
'multiple type attributes, first uppercase, last invalid' => array(
'<object TYPE="application/pdf" type="application/exe" data="https://wordpress.org/foo.pdf" />',
'<object TYPE="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object TYPE="application/pdf" type="application/exe" data="https://example.org/foo.pdf" />',
'<object TYPE="application/pdf" data="https://example.org/foo.pdf" />',
),
'multiple type attributes, last upper case and invalid' => array(
'<object type="application/pdf" TYPE="application/exe" data="https://wordpress.org/foo.pdf" />',
'<object type="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object type="application/pdf" TYPE="application/exe" data="https://example.org/foo.pdf" />',
'<object type="application/pdf" data="https://example.org/foo.pdf" />',
),
'multiple type attributes, first invalid' => array(
'<object type="application/exe" type="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object type="application/exe" type="application/pdf" data="https://example.org/foo.pdf" />',
'',
),
'multiple type attributes, first upper case and invalid' => array(
'<object TYPE="application/exe" type="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object TYPE="application/exe" type="application/pdf" data="https://example.org/foo.pdf" />',
'',
),
'multiple type attributes, first invalid, last uppercase' => array(
'<object type="application/exe" TYPE="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object type="application/exe" TYPE="application/pdf" data="https://example.org/foo.pdf" />',
'',
),
'multiple object tags, last invalid' => array(
'<object type="application/pdf" data="https://wordpress.org/foo.pdf" /><object type="application/exe" data="https://wordpress.org/foo.exe" />',
'<object type="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object type="application/pdf" data="https://example.org/foo.pdf" /><object type="application/exe" data="https://example.org/foo.exe" />',
'<object type="application/pdf" data="https://example.org/foo.pdf" />',
),
'multiple object tags, first invalid' => array(
'<object type="application/exe" data="https://wordpress.org/foo.exe" /><object type="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object type="application/pdf" data="https://wordpress.org/foo.pdf" />',
'<object type="application/exe" data="https://example.org/foo.exe" /><object type="application/pdf" data="https://example.org/foo.pdf" />',
'<object type="application/pdf" data="https://example.org/foo.pdf" />',
),
'type attribute with partially incorrect value' => array(
'<object type="application/pdfa" data="https://wordpress.org/foo.pdf" />',
'<object type="application/pdfa" data="https://example.org/foo.pdf" />',
'',
),
'type attribute with empty value' => array(
'<object type="" data="https://wordpress.org/foo.pdf" />',
'<object type="" data="https://example.org/foo.pdf" />',
'',
),
'type attribute with no value' => array(
'<object type data="https://wordpress.org/foo.pdf" />',
'<object type data="https://example.org/foo.pdf" />',
'',
),
'no type attribute' => array(
'<object data="https://wordpress.org/foo.pdf" />',
'<object data="https://example.org/foo.pdf" />',
'',
),
'different protocol in url' => array(
'<object type="application/pdf" data="http://example.org/foo.pdf" />',
'<object type="application/pdf" data="http://example.org/foo.pdf" />',
),
'query string on url' => array(
'<object type="application/pdf" data="https://example.org/foo.pdf?lol=.pdf" />',
'',
),
'fragment on url' => array(
'<object type="application/pdf" data="https://example.org/foo.pdf#lol.pdf" />',
'',
),
'wrong extension' => array(
'<object type="application/pdf" data="https://example.org/foo.php" />',
'',
),
'protocol relative url' => array(
'<object type="application/pdf" data="//example.org/foo.pdf" />',
'',
),
'relative url' => array(
'<object type="application/pdf" data="/cat/foo.pdf" />',
'',
),
);