Multiple XSS vulnerabilities fixed in WordPress Visual Composer plugin.

The WordPress Visual Composer plugin (80,000+ active installations) fixed multiple authenticated stored XSS vulnerabilities.

Affected versions are 25.0 and below and, to some extend, version 26.0 (see Timeline below for more details).

Authenticated stored XSS

The administrator can configure the plugin from its “Settings” page in the backend. Among the three available tabs, “CSS, HTML & JavaScript”, which is accessible at “/wp-admin/admin.php?page=vcv-global-css-js”, allows to inject CSS, JavaScript or HTML code into every page’s header and/or footer:

This tab, as well as the third one, “Sytem Status”, are loaded by calling the addSubmenuPage method from the “visualcomposer/visualcomposer/Modules/Settings/Traits/SubMenu.php” script:

protected function addSubmenuPage($page, $parentSlug = 'vcv-settings')
{
   if (isset($page['capability'])) {
      $capability = $page['capability'];
   } else {
      $capability = 'edit_posts';
   }
   ...
   ...

It expects an optional user capability as an argument. If there’s none, it will restrict the access by default to users who can edit posts, a capability available to low-privileged users such as contributors.
When calling addSubmenuPage to setup the tab, the addPage method in “visualcomposer/visualcomposer/Modules/Settings/Pages/CssJsSettings.php” doesn’t pass a capability:

protected function addPage()
{
   $page = [
      'slug' => $this->getSlug(),
      'title' => __('CSS, HTML & JavaScript', 'visualcomposer'),
      'layout' => 'settings-standalone-with-tabs',
      'showTab' => false,
   ];
   $this->addSubmenuPage($page);
}

A contributor user can access the tab and inject JavaScript code globally into every page and post of the website, both in the backend and frontend:

When users edit a post, they can insert JavaScript code locally or globally too. This applies to all users who can access the editor, including contributors and authors (unlike the default WordPress editor which, for security reasons, only allows admin and editor users to insert JS code into their posts):

When contributor users save their post, it is saved as a “draft” because they don’t have permission to publish posts, but the JS code is immediately injected and executed, here again, globally into every page and post in the backend and frontend of the website:

Bypassing Firewalls & Security Plugins

This second vulnerability is more interesting from a hacker’s point of view than the previous one because, when saving the post, the payload sent by Visual Composer is heavily obfuscated and thus will bypass web application firewalls and security plugins.
Here’s an example of the small payload used in the above image and sent via the vcv-admin-ajax AJAX action:

POST['vcv-zip']=eJyFUu9rwjAQ/VdcoLjBss1uKhMR/ME+joFf9jVNrhqXJiW5uonsf9/VRadSGRTSe/feu7vktmwt19w6K4ENWO8pVWn/Ketl7HaXCK7yErhWbNB5/oWERO0skQPgTKAYCFVo+7pz+GV4EGpDhE6MpbMIFgkZXnFOSMu6Vu58IZDzURMWhYrsSZX0J0magoGCXAL9Jo/jGuvP6IvUhXGZMFyGQOWKUhuglllM7qW79JHpQR3HFIFmCjwndTjUXYVJZRUhsXB3knRnSTqlkOyacue2zU1RKdR2EQ6sKqAranID52+8ywbGSaIs6fL56l9a7hyCbybGYn9OwyC9LnEkDHi8bo8rXNJ1aikQVGuOztPxPp/ftW+G95F6wbOhbAYclCYTgvL9ClUlPRO8uYBHe1SKBXCEojSUY4Mtw01Zb+1a1gRhqjrIjLAfFAf0gHIJarrfvofvYxuNpqa/OMfOYa50ENnJY5XUCbeiqCX5QQJf6GmXttFYU2+89LDW8Hni8f0DzwkeDg==

It is JSON-encoded, ZLIB-compressed and eventually base64-encoded.
Decoding occurs in the parseRequest method from the “visualcomposer/visualcomposer/Modules/System/Ajax/Controller.php” script:

protected function parseRequest(Request $requestHelper, Logger $loggerHelper)
{
   if ($requestHelper->exists('vcv-zip')) {
      $zip = $requestHelper->input('vcv-zip');
      $basedecoded = base64_decode($zip);
      $newAllJson = zlib_decode($basedecoded);
      $newArgs = json_decode($newAllJson, true);
      $all = $requestHelper->all();
      $new = array_merge($all, $newArgs);
      $requestHelper->setData($new);
   }

Note that if you have both Visual Composer and our NinjaFirewall WAF plugin installed on your blog, only the admin will be allowed to insert JS code into their post. NinjaFirewall will decode, uncompress and scan the payload and, if it detects JS code, it will reject the request if it comes from a non-admin user.

Timeline

The vulnerability was reported to the authors on March 26th, 2020 and a new version 26.0 was released on April 1st, 2020. However, because the second vulnerability was still exploitable in that version, the authors were contacted on April 3rd, 2020 again and a new version 27.0 was released on May 12th, 2020.

Recommendations

Upgrade immediately if you have version 26.0 or below. If you are using our web application firewall for WordPress, NinjaFirewall WP Edition (free) and NinjaFirewall WP+ Edition (premium), you are protected against this vulnerability.

Stay informed about the latest vulnerabilities