Cross-Site Scripting (XSS)
Introduction
This article covers cases of possible XSS on WordPress. This includes improper user input handling inside of the plugin/theme which can be used to inject arbitrary JS code to the front-end of the WordPress site.
Editor+/Admin+ Stored XSS
In WordPress security space, we usually mark vulnerability with this format <MINIMUM_ROLE_REQUIRED_TO_EXPLOIT>+ <VULNERABILITY_TYPE>
. In this case, Editor+ or Admin+ Stored XSS would mean an XSS that can be exploited by the user that has a minimum of Editor or Administrator role.
WordPress has a default capability or permission called unfiltered_html
. The unfiltered_html
permission is a security feature in WordPress that prevents users from using iframes and code snippets in WordPress posts, plus also more advanced code such as Javascript. By default, the unfiltered_html
permission is only given to Super Admins (in Multi-Site configuration), Administrator, and Editor roles.
Based on the above condition, we may encounter a false positive when testing for Editor+/Admin+ XSS. We need to disable the unfiltered_html
permission if we want to test for XSS on the Editor+/Admin+ role. One of the ways of doing this is via the DISALLOW_UNFILTERED_HTML
option. We can set the option to true
inside of the wp-config.php
file:
Contributor+ Stored XSS
Currently, this particular XSS type is very popular in the WordPress security space. This is because there are a lot of features or cases that could result in this type of XSS. In this section, we will cover possible cases for this XSS type.
The attack vector of this particular issue lies where the Contributor+ role user creates a drafted post that could contain an XSS payload and it can be triggered on other users such as the Administrator user when the user tries to preview the post created by the Contributor+ role user.
Shortcode XSS
WordPress has a feature called Shortcode. Shortcodes are a powerful and versatile feature that lets users add dynamic content and functionality to the website without needing any coding skills. WordPress Shortcodes are essentially small code snippets that can be inserted into posts, pages, widgets, or theme files to display a specific function or content.
An example shortcode for a gallery looks like this:
Shortcodes can also be used with additional attributes
as the following example shows:
Plugin and theme developers can register their custom Shortcodes using add_shortcode
function such as:
The above example code is vulnerable to a Shortcode XSS via the $atts["class"]
value. By default, the value of the attribute on Shortcode doesn’t escape characters like single-quote and double-quote but it will sanitize a supplied HTML tag. This leads to a DOM-based XSS if the supplied Shortcode attribute is not properly sanitized and placed inside of an HTML tag attribute.
In this example, the $atts["url"]
value is not vulnerable because it is already sanitized with esc_url
function which will escape quotes. The $atts["text"]
value is also not vulnerable since the HTML tag will be escaped or removed.
To exploit this, the Contributor+ role user simply needs to create a drafted post with the below content and send the preview link to other privileged users to trigger the XSS:
For security researchers, we recommend just searching for add_shortcode
string to identify if a plugin or theme has a custom Shortcode implementation.
Gutenberg Block XSS
Gutenberg blocks, or just “blocks”, are pre-built elements that can be added to a page or a post and then be further customized. Blocks can provide the basic components of a webpage, such as text and images, but they also open the door to more advanced features, such as buttons and tables.
Gutenberg block content has this format on the content:
An example Gutenberg block for a gallery with additional attributes looks like this:
Developers can register their custom Gutenberg blocks using register_block_type
function such as:
Same as the Shortcode XSS, by default, the value of the attribute on blocks doesn’t escape characters like single-quote and double-quote but it will sanitize a supplied HTML tag. To exploit this, the Contributor+ role user simply needs to create a drafted post with the below content (as the actual string sent to the save draft post request) and send the preview link to other privileged users to trigger the XSS:
For security researchers, we recommend just searching for the render_callback
string to identify if a plugin or theme has a custom Gutenberg block implementation.
Elementor Widget XSS
Similar to the Gutenberg blocks, Elementor, which is the most popular page builder plugin in WordPress also has a custom content feature called Elementor Widget. In Elementor, widgets are the building blocks for websites. They add functionality to the post or page, allowing users to do everything from writing text to adding dynamic data.
Developers of plugins and themes can register their custom Elememtor widget using the elementor/widgets/register
hook. This Simple Example demonstrates how to create a simple custom Elementor widget.
In Elementor widgets, they usually use settings
naming instead of attributes, however, both are similar value features that can be set from each of the Elementor widget configurations.
Let’s modify the render
function from the above example implementation to test for XSS:
Similar to the Shortcode XSS, by default, the value from the get_settings_for_display
function doesn’t escape characters like single-quote and double-quote but it will sanitize a supplied HTML tag. To exploit this, the Contributor+ role user simply needs to create a drafted post with a customized Elementow widget setting configuration and send the preview link to other privileged users to trigger the XSS:
Example XSS payload for the embed URL:
Example POST body request when updating the post or saving the drafted post:
For security researchers, we recommend just searching for the elementor/widgets/register
and render()
strings to identify if a plugin or theme has a custom Elementor widget implementation.
Below are some of the findings related to Contributor+ XSS:
- Authenticated Stored XSS in WooCommerce and Jetpack Plugin
- Arbitrary Attachment Render to XSS in Elementor Plugin
- WordPress Core 6.3.2 Security Update - Technical Advisory
Reflected XSS
Reflected XSS arises when an application receives data in an HTTP request and includes that data within the immediate response in an unsafe way. In WordPress or generally PHP, the below global variables could be traced to find potential Reflected XSS:
- $_GET
- $_POST
- $_REQUEST
- $_SERVER[‘PHP_SELF’]
Below are some of the findings related to Reflected XSS:
- Multiple High and Critical Vulnerabilities in Avada Theme and Plugin
- Multiple High Severity Vulnerabilities in Ninja Forms Plugin
- Reflected XSS in Advanced Custom Fields Plugins Affecting 2+ Million Sites
Admin Notices XSS
An admin notice is a notification block consisting of a white background, a colored left border, and some text. There are three types: green, orange, and red. Given the class names, they should be used for updating complete notices, update prompts, and errors respectively.
Admin notices are an integral part of plugins and themes development, they allow users to show error/success/warning messages to other users on the admin area (wp-admin
), prompting them for action or simply notifying them of something that has happened.
Developers can create an admin notices message using admin_notices
hook. Simply by attaching a function to the hook, developers should be able to generate an admin notices message.
In a default implementation, a message from the attached function will be displayed across every endpoint in the admin area that starts with /wp-admin
. If we can control part of the message on the admin notices function and the output is not sanitized or escaped, we can achieve a site-wide reflected and also stored XSS on the /wp-admin
area because the message will be persisted unless a user is dismissing the admin notices.
Example of vulnerable code:
To exploit this, an unauthenticated user just needs to send the below URL to the privileged users that have access to the /wp-admin
area and it will trigger the XSS:
The endpoint used doesn’t have to be /wp-admin/index.php
, other endpoints that start with /wp-admin
could also work.
Below are some of the findings related to Admin Notices XSS: