The update_settings function which is hooked to the update_ultimate_options ajax call, does not check user capability, and it does not use a nonce check. This means, that any authenticated user (subscriber, customer, editor, etc) can call this function.

We can update the ultimate_smooth_scroll_options key to almost any value, as there is no escaping or filtering applied to the $_POST['ultimate_smooth_scroll_options'] value.

Looking where the ultimate_smooth_scroll_options is used, we find that it is used in the ultimate_init_vars function which gets executed on wp_head hook in the wp-content/plugins/Ultimate_VC_Addons/Ultimate_VC_Addons.php file.

We got lucky here, because it uses the $speed and $step variable directly in a script tag, we do not have to escape any quotes because there are none. By default, WP uses magic_quotes for compatibility reasons, so we can inject JS, but we can't use quotes like " or ' but that is ok.

Stored XSS

All right, so we got a stored XSS in the Ultimate Addons for Visual Composer. A basic POC would be something like this

The wp_head hook, gets executed on every page load, so the XSS can be triggered on any page, which is nice.

But there is more.

RCE a.k.a Remote Code Execution

Now that we can inject and execute any JavaScript our possibilities opened up. Looking back at the AJAX hooks, one seems pretty interesting: wp_ajax_smile_ajax_add_zipped_font this will execute the add_zipped_font function located at line 279 in the /wp-content/plugins/Ultimate_VC_Addons/modules/Ultimate_Icon_Manager.php file.

It checks the user capability, then it gets the path for an attachment and passes it to the zip_flatten along with an extra array, which contain RegExp patters. So it looks like if we upload a ZIP file to the WP Media library, then take its ID, call this Ajax endpoint with the ID, we can extract its content, given that it matches the RegExp.

The bug

Looking at the zip_flatten function, we can find the bug which turns this font extraction into an RCE.

The RegExp just checks the presence of the string anywhere in the file name, it does not use the $ to mach only the end of the string, so a simple double extension will bypass it, because poc.eot.php will match.

As a side note, there is another bug in the create_config -> write_config function as well. It writes the font name, which is derived from an XML tag to a .php file.

Putting it all together. An attacker might plant the XSS himself, or can create a CSRF page, which plants it. A logged in user with Administrator rights executes the injected code resulting in a code execution.