There were multiple security vulnerabilities fixed in the Hide My WP plugin by wpWave which allowed unauthenticated SQL injection and allowed unauthenticated users to retrieve a token to deactivate the plugin.
Do you want to be the first to be alerted about such vulnerabilities? Sign up for Patchstack Community (Free) plan and monitor up to 10 websites for free.
For plugin developers, we have security audit services and Threat Intelligence Feed API for hosting companies.
The premium plugin Hide My WP (versions 6.2.3 and below) suffers from multiple vulnerabilities. The first vulnerability allows any unauthenticated user to perform SQL injection and the second vulnerability also allows any unauthenticated user to retrieve a reset token which can then be used to deactivate the plugin.
The plugin is described as a plugin that helps you hide your WordPress installation from attackers, spammers, and theme detectors. It can also hide your WordPress login URL and allows you to rename your admin URL.
The described issues were fixed in version 6.2.4 after being in contact with the Envato team.
The security vulnerabilities in the Hide My WP plugin
The SQL injection vulnerability in this plugin existed because of how the IP address is retrieved and how it is used inside of a SQL query.
The snippet of code that is vulnerable to SQL injection looks like the following:
$user_ip = $this->hmwp_get_user_ip();
$dbips_info = $wpdb->get_var("SELECT `ip` FROM `{$blocked_ips_table}` WHERE `allow`='1' AND `ip`='{$user_ip}'");
The function hmwp_get_user_ip tries to retrieve the IP address from multiple headers, including IP address headers which can be spoofed by the user such as X-Forwarded-For.
By supplying a malicious payload in one of these IP address headers, it will be directly inserted into the SQL query which makes SQL injection possible.
An example using CURL would look like the following:
curl --location --request GET "https://example.com" --header "X-Forwarded-For: 1' union all select sleep(3)#
"
The second vulnerability is caused by the following snippet of code:
if (isset($_GET['die_message']) && is_admin())
add_action('admin_init', array(&$this, 'die_message'), 1000);
The die_message function, with any irrelevant code removed, looks like the following:
function die_message()
{
if (!isset($_GET['die_message']))
return;
switch ($_GET['die_message']) {
case 'new_admin':
$title = "Custom Admin Path";
$token = get_option('hmwp_reset_token');
$reset_url = plugins_url() . '/'. dirname(HMW_FILE) . '/d.php'.'?token='.$token;
$content = sprintf(__('<div class="error"><p>Do not click back or close this tab.<br> Follow these steps <strong>IMMEDIATELY</strong> to enable new admin path or <a href="' . add_query_arg(array('new_admin_action' => 'abort'), $page_url) . '">Cancel</a> and try later. (<a target="_blank" href="http://support.wpwave.com/videos/change-wp-admin-to-myadmin" >' . __('Video Tutorial', self::slug) . '</a>) <br><strong>1) Re-configure server: (if require)</strong> <br> If you don\'t have a writable htaccess or enabled multi-site choose appropriate setup otherwise, HMWP updates your htaccess automatically and you can go to next step<br/><a target="_blank" href="' . add_query_arg(array('die_message' => 'single'), $page_url) . '" class="button">' . __('Manual Configuration', self::slug) . '</a> <a target="_blank" href="' . add_query_arg(array('die_message' => 'multisite'), $page_url) . '" class="button">' . __('Multisite Configuration (Apache)', self::slug) . '</a> <a target="_blank" href="' . add_query_arg(array('die_message' => 'nginx'), $page_url) . '" class="button">' . __('Nginx Configuration', self::slug) . '</a> <a target="_blank" href="' . add_query_arg(array('die_message' => 'iis'), $page_url) . '" class="button">' . __('IIS Configuration', self::slug) . '</a>
<br><br><strong> 2) <span style="color: #ee0000">Edit /wp-config.php </span></strong><br> Open wp-config.php using FTP and add following line somewhere before require_once(...) (if it already exist replace it with new code): <br><i><code>define("ADMIN_COOKIE_PATH", "%1$s");</code></i><br><br>%4$s<a class="button btn-red" href="%3$s">Cancel and Use Current Admin Path</a> <a class="button btn-blue" target="_blank" href="%2$s">I Did it! (Login to New Dashboard)</a> </p>
<p style="color: #ee0000"><strong>If you get locked out of your WordPress site, use this link to instantly uninstall HMWP plugin - </strong><input type="text" value="'.$reset_url.'" onclick="this.select();" readonly></p></div>', self::slug), preg_replace('|https?://[^/]+|i', '', get_option('siteurl') . '/') . $new_admin_path, add_query_arg(array('new_admin_action' => 'configured'), $page_url), add_query_arg(array('new_admin_action' => 'abort'), $page_url), '');
$content .= "<style>input[type='text']{display: block;width: 100%;max-width: 100%;box-sizing: border-box;margin: 5px 0;padding: 10px;border-radius: 4px;border: 1px solid #ccc;font-size: 14px;cursor: pointer;}.button{background: #f7f7f7;border: 1px solid #ccc;color: #555;text-decoration: none;font-size: 13px;line-height: 2;height: auto;display: inline-block;box-sizing: border-box;margin: 5px;padding: 4px 10px;cursor: pointer;-webkit-border-radius: 3px;-webkit-appearance: none;border-radius: 4px;white-space: nowrap;-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;-webkit-box-shadow: 0 1px 0 #ccc;box-shadow: 0 1px 0 #ccc;vertical-align: top;}.btn-red{background: #d33434;color: #FFF;}.btn-blue{background: #249124;color: #FFF;}</style>";
$body_email = 'Hello admin,<br/><br/><p style="color:green"><strong>If you get locked out of your WordPress site, use this link to instantly uninstall HMWP plugin - '.$reset_url.'<strong></p>';
$subject_email = sprintf(__('[%s] Your New WP Login!', self::slug), self::title);
wp_mail(get_option('admin_email'), $subject_email, $body_email, array('Content-Type: text/html; charset=UTF-8'));
break;
}
wp_die('<h3>' . $title . '</h3>' . $content);
}
As you can see, the reset token (hmwp_reset_token) will be directly printed onto the screen which can then be used to deactivate the plugin in the file /wp-content/plugins/hide_my_wp/d.php (located in the root folder of the plugin).
Note that this will only work if a valid token actually exists and the token is not an empty value.
Simply by visiting a URL such as /wp-admin/admin-ajax.php?die_message=new_admin&action=heartbeat we can make it display the reset token on the screen.
The patch in Hide My WP
Since this is a premium plugin, the patch cannot be seen at the WordPress.org SVN repository.
Based on our own research, we can confirm that for the SQL injection vulnerability a patch has been applied that validates the IP address using PHP’s filter_var function along with the usage of the $wpdb->prepare function.
For the second vulnerability, a capability check has been added to the die_message function using the current_user_can function that makes sure the current user has the manage_options capability.
Timeline
29-09-2021 – We discovered the vulnerability in Hide My WP and released a vPatch to all Patchstack paid version customers.
29-09-2021 – We reached out to the developer of the plugin.
05-10-2021 – No reply from the developer of the plugin. We reached out to Envato who replied within 10 minutes.
26-10-2021 – The developer released a new plugin version, 6.2.4, which fixes the issues.
24-11-2021 – Published the article.
24-11-2021 – Added the vulnerability to the Patchstack vulnerability database.
Websites with Patchstack paid version are protected from the issue and have received a vPatch.