The WordPress Elementor Page Builder plugin (4+ million installations), was prone to a broken access control vulnerability affecting version 2.9.7 and below that could lead to stored XSS vulnerability via SVG image upload.
Authenticated SVG Uploads Activation
Elementor has an option to allow SVG uploads. It can be enabled from the “Settings > Advanced” tab:
The ajax_enable_svg_uploads
function used to activate the option lacks capability check and because it is called by the handle_ajax_request
function which uses a shared security nonce, it is accessible to all logged-in users:
public function ajax_enable_svg_uploads() { update_option( 'elementor_allow_svg', 1 ); }
Any authenticated user can activate SVG uploads via the WordPress AJAX API.
SVG Sanitizer Bypass & Authenticated Stored XSS
In WordPress, a user must have the upload_files
capability in order to upload a file to the media library. Authors have such capability: they can upload files and edit their own posts but they aren’t allowed to insert potentially dangerous elements such as JavaScript code and various HTML tags into their posts. However, after enabling SVG uploads, an author could bypass those restrictions.
In the “elementor/core/files/assets/svg/svg-handler.php” script, the sanitizer
method is used to load and sanitize the SVG file:
public function sanitizer( $content ) { ... ... $open_svg = $this->svg_dom->loadXML( $content ); if ( ! $open_svg ) { return false; } $this->strip_doctype(); $this->sanitize_elements();
Because an XML document is case-sensitive, the function loads the content of the file with DOMDocument::loadXML
which, unlike DOMDocument::loadHTML
, doesn’t convert all elements to lowercase. Then, it calls the sanitize_elements
function:
const SCRIPT_REGEX = '/(?:\w+script|data):/xi'; .. .. private function sanitize_elements() { ... ... $href = $current_element->getAttribute( 'href' ); if ( 1 === preg_match( self::SCRIPT_REGEX, $href ) ) { $current_element->removeAttribute( 'href' ); }
The function performs several actions such as searching and removing JS code and data URI schemes from the href
attribute. But because the getAttribute
and removeAttribute
functions perform a case-sensitive search, an author could bypass the verification by changing the attribute’s case to HREF
or hReF
for instance and, to bypass several additional checks in the plugin, could insert a specially crafted SVG file as a base64-encoded data URL, allowing the injection of JavaScript code into their post:
Because the content of the encoded file won’t be sanitized, it is possible to insert plain JavaScript code or even HTML events such as onmouseover
or onmousemove
for instance.
Additional Issues
Elementor attempts to remove PHP code and comments from uploaded files with two functions, strip_php_tags
and strip_comments
:
private function strip_php_tags( $string ) { $string = preg_replace( '/<\?(=|php)(.+?)\?>/i', '', $string ); // Remove XML, ASP, etc. $string = preg_replace( '/<\?(.*)\?>/Us', '', $string ); $string = preg_replace( '/<\%(.*)\%>/Us', '', $string ); if ( ( false !== strpos( $string, '<?' ) ) || ( false !== strpos( $string, '<%' ) ) ) { return ''; } return $string; } private function strip_comments( $string ) { // Remove comments. $string = preg_replace( '/<!--(.*)-->/Us', '', $string ); $string = preg_replace( '/\/\*(.*)\*\//Us', '', $string ); if ( ( false !== strpos( $string, '<!--' ) ) || ( false !== strpos( $string, '/*' ) ) ) { return ''; } return $string; }
However, they are called in the wrong order:
$content = $this->strip_php_tags( $content ); $content = $this->strip_comments( $content );
A user could obfuscate PHP code with a fake comment:
</* foo */?php echo "Hello World"; ?>
The strip_php_tags
function would miss it but strip_comments
would remove the comment and recreate the original PHP code:
<?php echo "Hello World"; ?>
Recommendations
Update immediately if you have version 2.9.7 or below installed.
If you are using our premium web application firewall for WordPress, NinjaFirewall WP+ Edition, you are protected against this type of vulnerabilities affecting SVG files.
Timeline
The vulnerability was reported to the authors on April 14th, 2020 and a new version 2.9.8 was released on April 21st, 2020.
Stay informed about the latest vulnerabilities
- Running WordPress? You can get email notifications about vulnerabilities in the plugins or themes installed on your blog.
- On Twitter: @nintechnet