Cherry pick #351 and #363 into trunk (#50075)

* XSS vulnerability on various unescaped attributes

* escape Product Filter Stock status

* escape more blocks

* escape more blocks

* escape more blocks

* add changelog

* update changelog

* Fix misplaced json encode flags

* Add escaping to the block names

* Update changelog message

---------

Co-authored-by: Luigi Teschio <gigitux@gmail.com>
Co-authored-by: roykho <roykho77@gmail.com>
This commit is contained in:
nigeljamesstevenson 2024-07-29 15:29:34 +01:00 committed by GitHub
parent 61d9b63f67
commit 77318c4a67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 61 additions and 49 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Enhance escaping for block attributes

View File

@ -167,8 +167,8 @@ class ProductButton extends AbstractBlock {
);
$div_directives = '
data-wc-interactive=\'' . wp_json_encode( $interactive, JSON_NUMERIC_CHECK ) . '\'
data-wc-context=\'' . wp_json_encode( $context, JSON_NUMERIC_CHECK ) . '\'
data-wc-interactive=\'' . wp_json_encode( $interactive, JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) . '\'
data-wc-context=\'' . wp_json_encode( $context, JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) . '\'
';
$button_directives = '

View File

@ -181,7 +181,7 @@ class ProductCollection extends AbstractBlock {
'data-wc-navigation-id',
'wc-product-collection-' . $this->parsed_block['attrs']['queryId']
);
$p->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-collection' ) ) );
$p->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-collection' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
$p->set_attribute(
'data-wc-context',
wp_json_encode(
@ -193,7 +193,7 @@ class ProductCollection extends AbstractBlock {
// This way we avoid prefetching when the page loads.
'isPrefetchNextOrPreviousLink' => false,
),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
)
);
$block_content = $p->get_updated_html();
@ -295,7 +295,7 @@ class ProductCollection extends AbstractBlock {
'class_name' => $class_name,
)
) ) {
$processor->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-collection' ) ) );
$processor->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-collection' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
$processor->set_attribute( 'data-wc-on--click', 'actions.navigate' );
$processor->set_attribute( 'data-wc-key', $key_prefix . '--' . esc_attr( wp_rand() ) );

View File

@ -115,8 +115,8 @@ final class ProductFilter extends AbstractBlock {
}
$attributes_data = array(
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ) ),
'data-wc-context' => wp_json_encode( array( 'hasSelectedFilter' => $has_selected_filter ) ),
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-wc-context' => wp_json_encode( array( 'hasSelectedFilter' => $has_selected_filter ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'class' => 'wc-block-product-filters',
);

View File

@ -55,8 +55,8 @@ final class ProductFilterActive extends AbstractBlock {
$wrapper_attributes = get_block_wrapper_attributes(
array(
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ) ),
'data-wc-context' => wp_json_encode( $context ),
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-wc-context' => wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-has-filter' => empty( $active_filters ) ? 'no' : 'yes',
)
);
@ -185,7 +185,7 @@ final class ProductFilterActive extends AbstractBlock {
private function get_html_attributes( $attributes ) {
return array_reduce(
array_keys( $attributes ),
function( $acc, $key ) use ( $attributes ) {
function ( $acc, $key ) use ( $attributes ) {
$acc .= sprintf( ' %1$s="%2$s"', esc_attr( $key ), esc_attr( $attributes[ $key ] ) );
return $acc;
},
@ -227,7 +227,7 @@ final class ProductFilterActive extends AbstractBlock {
return array_filter(
$url_query_params,
function( $key ) use ( $filter_param_keys ) {
function ( $key ) use ( $filter_param_keys ) {
return in_array( $key, $filter_param_keys, true );
},
ARRAY_FILTER_USE_KEY

View File

@ -134,7 +134,8 @@ final class ProductFilterAttribute extends AbstractBlock {
'value' => $term,
'attributeSlug' => $product_attribute,
'queryType' => get_query_var( "query_type_{$product_attribute}" ),
)
),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
),
),
);
@ -173,7 +174,7 @@ final class ProductFilterAttribute extends AbstractBlock {
'<div %s></div>',
get_block_wrapper_attributes(
array(
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ) ),
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-has-filter' => 'no',
)
),
@ -225,8 +226,8 @@ final class ProductFilterAttribute extends AbstractBlock {
'<div %1$s>%2$s%3$s</div>',
get_block_wrapper_attributes(
array(
'data-wc-context' => wp_json_encode( $context ),
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ) ),
'data-wc-context' => wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-has-filter' => 'yes',
)
),

View File

@ -44,7 +44,7 @@ final class ProductFilterPrice extends AbstractBlock {
public function get_filter_query_param_keys( $filter_param_keys, $url_param_keys ) {
$price_param_keys = array_filter(
$url_param_keys,
function( $param ) {
function ( $param ) {
return self::MIN_PRICE_QUERY_VAR === $param || self::MAX_PRICE_QUERY_VAR === $param;
}
);
@ -141,8 +141,13 @@ final class ProductFilterPrice extends AbstractBlock {
) = $attributes;
$wrapper_attributes = array(
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ) ),
'data-wc-context' => wp_json_encode( $data ),
'data-wc-interactive' => wp_json_encode(
array(
'namespace' => $this->get_full_block_name(),
),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP,
),
'data-wc-context' => wp_json_encode( $data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-has-filter' => 'no',
);

View File

@ -47,7 +47,7 @@ final class ProductFilterRating extends AbstractBlock {
public function get_filter_query_param_keys( $filter_param_keys, $url_param_keys ) {
$rating_param_keys = array_filter(
$url_param_keys,
function( $param ) {
function ( $param ) {
return self::RATING_FILTER_QUERY_VAR === $param;
}
);
@ -84,8 +84,8 @@ final class ProductFilterRating extends AbstractBlock {
/* translators: %d is the rating value. */
'title' => sprintf( __( 'Rated %d out of 5', 'woocommerce' ), $rating ),
'attributes' => array(
'data-wc-on--click' => "{$this->get_full_block_name()}::actions.removeFilter",
'data-wc-context' => "{$this->get_full_block_name()}::" . wp_json_encode( array( 'value' => $rating ) ),
'data-wc-on--click' => esc_attr( "{$this->get_full_block_name()}::actions.removeFilter" ),
'data-wc-context' => esc_attr( "{$this->get_full_block_name()}::" ) . wp_json_encode( array( 'value' => $rating ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
),
);
},

View File

@ -45,7 +45,7 @@ final class ProductFilterStockStatus extends AbstractBlock {
public function get_filter_query_param_keys( $filter_param_keys, $url_param_keys ) {
$stock_param_keys = array_filter(
$url_param_keys,
function( $param ) {
function ( $param ) {
return self::STOCK_STATUS_QUERY_VAR === $param;
}
);
@ -81,12 +81,12 @@ final class ProductFilterStockStatus extends AbstractBlock {
$action_namespace = $this->get_full_block_name();
$active_stock_statuses = array_map(
function( $status ) use ( $stock_status_options, $action_namespace ) {
function ( $status ) use ( $stock_status_options, $action_namespace ) {
return array(
'title' => $stock_status_options[ $status ],
'attributes' => array(
'data-wc-on--click' => "$action_namespace::actions.removeFilter",
'data-wc-context' => "$action_namespace::" . wp_json_encode( array( 'value' => $status ) ),
'data-wc-context' => "$action_namespace::" . wp_json_encode( array( 'value' => $status ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
),
);
},
@ -168,7 +168,7 @@ final class ProductFilterStockStatus extends AbstractBlock {
$list_items = array_values(
array_map(
function( $item ) use ( $stock_statuses, $show_counts, $selected_stock_statuses ) {
function ( $item ) use ( $stock_statuses, $show_counts, $selected_stock_statuses ) {
$label = $show_counts ? $stock_statuses[ $item['status'] ] . ' (' . $item['count'] . ')' : $stock_statuses[ $item['status'] ];
return array(
'label' => $label,
@ -183,13 +183,13 @@ final class ProductFilterStockStatus extends AbstractBlock {
$selected_items = array_values(
array_filter(
$list_items,
function( $item ) use ( $selected_stock_statuses ) {
function ( $item ) use ( $selected_stock_statuses ) {
return in_array( $item['value'], $selected_stock_statuses, true );
}
)
);
$data_directive = wp_json_encode( array( 'namespace' => $this->get_full_block_name() ) );
$data_directive = wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP );
ob_start();
?>
@ -255,7 +255,7 @@ final class ProductFilterStockStatus extends AbstractBlock {
return array_filter(
$data,
function( $stock_count ) {
function ( $stock_count ) {
return $stock_count['count'] > 0;
}
);

View File

@ -59,7 +59,7 @@ class ProductGallery extends AbstractBlock {
$html = array_reduce(
$parsed_template,
function( $carry, $item ) {
function ( $carry, $item ) {
return $carry . render_block( $item );
},
''
@ -134,7 +134,7 @@ class ProductGallery extends AbstractBlock {
$p = new \WP_HTML_Tag_Processor( $html );
if ( $p->next_tag() ) {
$p->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ) ) );
$p->set_attribute( 'data-wc-interactive', wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
$p->set_attribute(
'data-wc-context',
wp_json_encode(
@ -147,7 +147,8 @@ class ProductGallery extends AbstractBlock {
'mouseIsOverPreviousOrNextButton' => false,
'productId' => $product_id,
'elementThatTriggeredDialogOpening' => null,
)
),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
)
);

View File

@ -98,7 +98,7 @@ class ProductGalleryLargeImage extends AbstractBlock {
'{content}' => $content,
'{directives}' => array_reduce(
array_keys( $directives ),
function( $carry, $key ) use ( $directives ) {
function ( $carry, $key ) use ( $directives ) {
return $carry . ' ' . $key . '="' . esc_attr( $directives[ $key ] ) . '"';
},
''
@ -143,7 +143,7 @@ class ProductGalleryLargeImage extends AbstractBlock {
);
$main_image_with_wrapper = array_map(
function( $main_image_element ) {
function ( $main_image_element ) {
return "<li class='wc-block-product-gallery-large-image__wrapper'>" . $main_image_element . '</li>';
},
$main_images
@ -151,7 +151,6 @@ class ProductGalleryLargeImage extends AbstractBlock {
$visible_main_image = array_shift( $main_images );
return array( $visible_main_image, $main_image_with_wrapper );
}
/**
@ -187,8 +186,8 @@ class ProductGalleryLargeImage extends AbstractBlock {
);
return array(
'data-wc-interactive' => wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ) ),
'data-wc-context' => wp_json_encode( $context, JSON_NUMERIC_CHECK ),
'data-wc-interactive' => wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-wc-context' => wp_json_encode( $context, JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-wc-on--mousemove' => 'actions.startZoom',
'data-wc-on--mouseleave' => 'actions.resetZoom',
);

View File

@ -129,7 +129,7 @@ class ProductGalleryLargeImageNextPrevious extends AbstractBlock {
'{next_button}' => $next_button,
'{alignment_class}' => $alignment_class,
'{position_class}' => $position_class,
'{data_wc_interactive}' => wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ), JSON_NUMERIC_CHECK ),
'{data_wc_interactive}' => wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ), JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
)
);
}
@ -188,7 +188,6 @@ class ProductGalleryLargeImageNextPrevious extends AbstractBlock {
$this->get_class_suffix( $context ),
$icon_path
);
}
/**
@ -229,6 +228,5 @@ class ProductGalleryLargeImageNextPrevious extends AbstractBlock {
$this->get_class_suffix( $context ),
$icon_path
);
}
}

View File

@ -74,7 +74,7 @@ class ProductGalleryPager extends AbstractBlock {
</div>',
$wrapper_attributes,
$html,
wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ) )
wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP )
);
}
return '';
@ -127,7 +127,10 @@ class ProductGalleryPager extends AbstractBlock {
$p->set_attribute(
'data-wc-context',
wp_json_encode(
array( 'imageId' => strval( $product_gallery_image_id ) ),
array(
'imageId' => strval( $product_gallery_image_id ),
),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP,
)
);
$p->set_attribute(

View File

@ -168,7 +168,7 @@ class ProductGalleryThumbnails extends AbstractBlock {
}
}
$thumbnails_count++;
++$thumbnails_count;
}
return sprintf(
@ -178,7 +178,7 @@ class ProductGalleryThumbnails extends AbstractBlock {
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classes_and_styles['styles'] ),
$html,
wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ) )
wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP )
);
}
}

View File

@ -29,12 +29,12 @@ class CheckboxList {
$checkbox_list_context = array( 'items' => $items );
$on_change = $props['on_change'] ?? '';
$namespace = wp_json_encode( array( 'namespace' => 'woocommerce/interactivity-checkbox-list' ) );
$namespace = wp_json_encode( array( 'namespace' => 'woocommerce/interactivity-checkbox-list' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP );
ob_start();
?>
<div data-wc-interactive='<?php echo esc_attr( $namespace ); ?>'>
<div data-wc-context='<?php echo esc_attr( wp_json_encode( $checkbox_list_context ) ); ?>' >
<div data-wc-context='<?php echo wp_json_encode( $checkbox_list_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ); ?>' >
<div class="wc-block-stock-filter style-list">
<ul class="wc-block-components-checkbox-list">
<?php foreach ( $items as $item ) { ?>

View File

@ -35,12 +35,12 @@ class Dropdown {
);
$action = $props['action'] ?? '';
$namespace = wp_json_encode( array( 'namespace' => 'woocommerce/interactivity-dropdown' ) );
$namespace = wp_json_encode( array( 'namespace' => 'woocommerce/interactivity-dropdown' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP );
ob_start();
?>
<div data-wc-interactive='<?php echo esc_attr( $namespace ); ?>'>
<div class="wc-interactivity-dropdown" data-wc-on--click="actions.toggleIsOpen" data-wc-context='<?php echo esc_attr( wp_json_encode( $dropdown_context ) ); ?>' >
<div class="wc-interactivity-dropdown" data-wc-on--click="actions.toggleIsOpen" data-wc-context='<?php echo wp_json_encode( $dropdown_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ); ?>' >
<div class="wc-interactivity-dropdown__dropdown" tabindex="-1" >
<div class="wc-interactivity-dropdown__dropdown-selection" id="options-dropdown" tabindex="0" aria-haspopup="listbox">
<span class="wc-interactivity-dropdown__placeholder" data-wc-text="state.placeholderText">
@ -118,7 +118,7 @@ class Dropdown {
data-wc-class--is-selected="state.isSelected"
class="components-form-token-field__suggestion"
data-wc-bind--aria-selected="state.isSelected"
data-wc-context='<?php echo wp_json_encode( $context ); ?>'
data-wc-context='<?php echo wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ); ?>'
>
<?php echo $item['label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>

View File

@ -59,7 +59,8 @@ class ProductGalleryUtils {
wp_json_encode(
array(
'imageId' => $product_gallery_image_id,
)
),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
)
);