In testing out a new check we were adding to our tool for doing limited automated security checks of WordPress plugins we ran the plugin ProfileGrid through the tool, since it had previously had the security issue being checked for. That security issue involved usage of a third-party library that hadn’t been updated in 8 years (the library was added to the plugin 9 months ago) and would leak potentially sensitive information about financial transactions. When we ran the plugin through the tool we found that the tool identified that plugin possibly contained a fairly obvious reflected cross-site scripting (XSS) vulnerability. In looking over things we found that there were multiple instances of this issue in the plugin and that it looks like debugging code has been left in the plugin, so the plugin didn’t look exactly production ready in addition to be being insecure.
That wasn’t really surprising when we noticed that one of the developers is CMSHelpLive, which is a company we noted over a year ago at our main blog was still running a version of Joomla that had been EOL’d four and half years ago, while offering to clean up hacked Joomla websites. Over a year later they are still running that version. (It would be hard to make up claims about the poor handling of security by companies involved in security that could outdo what they really do.)
There are several places the reflected XSS vulnerability existed, but as an example let’s take a look one of them that has existed since the first version of the plugin. That involves the usage the outputting of the GET input “search” in the file /admin/partials/user-manager.php, which is used to generate the page /wp-admin/admin.php?page=pm_user_manager. On lines 112 and 115 of that file that input is echo’d without being escaped:
<input type="text" class="sb-search" name="search" id="search" value="<?php if(isset($_GET['search'])) echo $_GET['search'];?>"> </div> <?php if(isset($_GET['search']) && $_GET['search']!=''):?> <div class="sb-search-keyword" id="search_keyword"><?php echo $_GET['search'];?> <span onclick="show_hide_search_text()">x</span></div>
That is a pretty clear cut issue and when we went check things out to make sure there wasn’t any code elsewhere that would restrict that from being exploitable, there wasn’t.
Also, worth noting with this, is that this is something that the security review that is done of new plugins in the Plugin Directory is supposed to be catching, as one of the items on their security checklist is:
Escape all data before output (See: Data Validation)
The plugin is only eleven months old, so the failure to spot this doesn’t seem like it can be blamed on a previous lower standard. While we think the reviews would be better if they focused on issues more likely to lead to an exploitable vulnerabilities, if the team behind this was interested with improving what they are doing now we could certainly help them considering that our automated tool was able to pick this issue up.
In terms of the debugging code, an example were the following commented out lines in the file /admin/partials/add-section.php:
//echo '<pre>';print_r($_POST);echo '</pre>';die; //print_r($identifier);die;
Beyond not needing to be there, in another location what looks to be debugging code was left running in the production version of the plugin. The last line shown below would output any POST input with a request after the code generates a success message (in the file /public/partials/profile-magic-payment-process.php):
case "success": // success case to show the user payment got success echo '<div id="crf-form">'; echo "<div class='info-text'>".__('Payment Transaction Done Successfully','profile-magic')."</br>"; echo '</div></div>'; print_r($_POST);
After we notified the developer of the issue, they released version 2.6.7, which resolved the instances of the plugin directly outputting unescaped output. The example shown above was changed to this:
<input type="text" class="sb-search" name="search" id="search" value="<?php if(isset($_GET['search'])) echo esc_attr( filter_input(INPUT_GET, 'search', FILTER_SANITIZE_STRING) );?>"> </div> <?php if(isset($_GET['search']) && $_GET['search']!=''):?> <div class="sb-search-keyword" id="search_keyword"><?php echo esc_html( filter_input(INPUT_GET, 'search', FILTER_SANITIZE_STRING) );?> <span onclick="show_hide_search_text()">x</span></div>
It isn’t clear why they used an escaping function and filter_input on those, since the escaping function should be enough.
They also removed the relevant debugging code made some other security changes in that version.
Proof of Concept
The following proof of concept will cause any available cookies to be shown in alert box, when logged in as an Administrator. Major web browsers other than Firefox provide XSS filtering, so this proof of concept will not work in those web browsers.
Make sure to replace “[path to WordPress]” with the location of WordPress.
http://[path to WordPress]/wp-admin/admin.php?page=pm_user_manager&search=%22%3E%3Cscript%3Ealert%28document.cookie%29%3B%3C%2Fscript%3E
- November 20, 2017 – Developer notified.
- November 21, 2017 – Developer responds.
- November 23, 2017 – Version 2.6.7 released, which fixes vulnerability.