WordPress Elementor plugin fixed SVG XSS protection bypass vulnerability.

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