Wordfence Sold Non-Public Information on Unfixed Vulnerability in Competing Security Plugins to Hackers
On Reddit this week, a hacker suggested that the website of the WordPress security provider Wordfence is a good place to get information on hacking WordPress websites. A recent blog post on their website highlights how they are helping hackers while also trying to profit off of those hacks.
With a vulnerability found by a competitor, Patchstack, Wordfence explained how to exploit the vulnerability. The explanation for doing that seems to be missing a good reason for doing that:
We were able to reverse engineer the exploit based on attack traffic and a copy of the vulnerable plugin and are providing information on its functionality as this vulnerability is already being exploited in the wild and a patch has been available for some time.
That it was already being exploited and there is a patch available doesn’t explain a need for that information. There can certainly be a good reason for providing such information, but they didn’t even try to come up with one.
They didn’t just provide that information on their website, but posted it on a website for those looking for exploits to use.
Why do that? Well, in their post, they are promoting hiring them to clean up websites hacked through the vulnerability:
If you believe your site has been compromised as a result of this vulnerability or any other vulnerability, we offer Incident Response services via Wordfence Care. If you need your site cleaned immediately, Wordfence Response offers the same service with 24/7/365 availability and a 1-hour response time. Both of these products include hands-on support in case you need further assistance.
So they make it easier for this to be exploited and then promote they can clean up the result of the exploits. That seems highly problematic.
That isn’t the only way that Wordfence can profit off of the hackers they are supposed to be stopping.
This Isn’t Responsible Disclosure
At the end of that post, they claimed that security researchers “can responsibly disclose your finds to us”:
If you are a security researcher, you can responsibly disclose your finds to us and obtain a CVE ID and get your name on the Wordfence Intelligence Community Edition leaderboard.
But looking at their responsible disclosure policy shows that it isn’t that. Here are the first three steps of their process:
- Our Threat Intelligence team verifies the vulnerability and determines severity.
- Where possible, we develop a firewall rule to protect our customers. This rule is obfuscated to prevent reverse engineering.
- We notify the vendor, if necessary, and simultaneously release a firewall rule to protect our premium customers via the Threat Defense Feed. Customer sites are updated immediately with the rule and no customer action is required.
By their own admission, they are providing information on the vulnerabilities to people paying them (for their Wordfence Premium) service before even notifying the developer. So any hacker willing to pay would have access to that information. They do claim the rules are “obfuscated to prevent reverse engineering”. But is that true? No.
In October, we discussed an example where they had not even obfuscated the rule for an non-public unfixed vulnerability. Any hacker paying them would have easily turned that rule into an exploit. Troublingly, the US government seemed to play a role in that bad situation.
With another rule, that was provided to their paying customers over a month ago and is now available for free, the rule was obfuscated, but not in a way that would prevent reverse engineering. That situation involved a non-public unfixed vulnerability in two competing security plugins.
Here is the obfuscated rule:
if ((md5Equals(‘dd7bff06f070a6396734e7032928c83a’, request.md5Body[‘ef3e30e070f70244fd6578d88a6b77ac’]) or md5Equals(‘782abb92e47aeecaceea63199414fa2d’, request.md5Body[‘ef3e30e070f70244fd6578d88a6b77ac’]) or md5Equals(‘0e27366d718c95c5b8011a7f28a24589’, request.md5Body[‘ef3e30e070f70244fd6578d88a6b77ac’]) or md5Equals(‘916e2b118592d424951dbc47d3230624’, request.md5Body[‘ef3e30e070f70244fd6578d88a6b77ac’]) or md5Equals(‘c599ef9fc7ffba714eee1b205871ec07’, request.md5Body[‘ef3e30e070f70244fd6578d88a6b77ac’]) or md5Equals(‘f4825723c2d5ee07555173a64ae7e833’, request.md5Body[‘ef3e30e070f70244fd6578d88a6b77ac’]) or md5Equals(’19fbe04f83543842fc954c4e2278583a’, request.md5Body[‘ef3e30e070f70244fd6578d88a6b77ac’])) and currentUserIsNot(‘administrator’, server.empty)):
block(id=525, category=’auth-bypass’, score=100, description=’WAF-RULE-525′, whitelist=0)
It is certainly obfuscated, but surely if Wordfence really has the security experts they claim to have on staff, they would know that wouldn’t prevent reverse engineering since the obfuscation is handled through MD5 hashing. You can think of hashing as one-way encryption, where the text to be obfuscated in is obfuscated in a way that can’t be directly reversed. If done properly, it can’t be directly reversed, but there are multiple well known methods to indirectly do that.
One approach that would be effective here is to use the free software Hashcat and reasonably powerful GPU to brute force the hashes by hashing combinations of characters until you find hashes that match the ones in the rule.
Using another approach allowed us to not only instantly determine what text has been hashed, but identify what plugins the text has come from. Here is the rule without the obfuscation:
if ((md5Equals(‘tab_all_switch’, request.md5Body[‘option’]) or md5Equals(‘tab_2fa_switch’, request.md5Body[‘option’]) or md5Equals(‘tab_waf_switch’, request.md5Body[‘option’]) or md5Equals(‘tab_login_switch’, request.md5Body[‘option’]) or md5Equals(‘tab_backup_switch’, request.md5Body[‘option’]) or md5Equals(‘tab_malware_switch’, request.md5Body[‘option’]) or md5Equals(‘tab_block_switch’, request.md5Body[‘option’])) and currentUserIsNot(‘administrator’, server.empty)):
block(id=525, category=’auth-bypass’, score=100, description=’WAF-RULE-525′, whitelist=0)
That rule relates to a vulnerability that had been in the security plugin miniOrange 2 Factor Authentication, which has 20,000+ installs, and still is in the security plugin Multi Factor Authentication, which has 800+ installs. The plugin that has been fixed was only fixed a week after Wordfence started providing any hacker willing to pay with the information on the vulnerability.
The rule indicates that the vulnerability is an authentication bypass vulnerability. But what exactly is at issue? Once you know what the underlying rule is, it would be easy for a competent hacker to figure out the rest of what is going on.
We will look at the code that was in miniOrange 2 Factor Authentication, as that appears to be what the rule was written for.
In the file /controllers/dashboard_ajax.php, the function mo2f_switch_functions() is registered to run during admin_init, which makes it accessible to even those not logged in to WordPress:
5 | add_action( 'admin_init' , array( $this, 'mo2f_switch_functions' ) ); |
As of the last vulnerable version, it didn’t do any security checks before permitting changing the settings for plugins from the developer of the plugin, miniOrange:
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 | public function mo2f_switch_functions(){ if(isset($_POST) && isset($_POST['option'])){ $tab_count= get_site_option('mo2f_tab_count', 0); if($tab_count == 5) update_site_option('mo_2f_switch_all', 1); elseif($tab_count == 0) update_site_option('mo_2f_switch_all', 0); $santizied_post=isset($_POST['switch_val'])? sanitize_text_field($_POST['switch_val']):null; switch(sanitize_text_field($_POST['option'])) { case "tab_all_switch": $this->mo2f_handle_all_enable($santizied_post); break; case "tab_2fa_switch": $this->mo2f_handle_2fa_enable($santizied_post); break; case "tab_waf_switch": $this->mo2f_handle_waf_enable($santizied_post); break; case "tab_login_switch": $this->mo2f_handle_login_enable($santizied_post); break; case "tab_malware_switch": $this->mo2f_handle_malware_enable($santizied_post); break; case "tab_block_switch": $this->mo2f_handle_block_enable($santizied_post); break; } } |
If you look closely at the code, you will see the various bits of text from the rule there.
In the new version, a capabilities check to limit access to Administrators and a nonce check to prevent cross-site request forgery (CSRF) have been added:
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public function mo2f_switch_functions(){ if(current_user_can('administrator') ) { if(isset($_POST) && isset($_POST['option'])){ $tab_count= get_site_option('mo2f_tab_count', 0); if($tab_count == 5) update_site_option('mo_2f_switch_all', 1); elseif($tab_count == 0) update_site_option('mo_2f_switch_all', 0); $sanitized_post=isset($_POST['switch_val'])? sanitize_text_field($_POST['switch_val']):null; switch(sanitize_text_field($_POST['option'])) { case "tab_all_switch": if($this->mo2f_check_wpns_nonce()){ $this->mo2f_handle_all_enable($sanitized_post); |