WordPress Plugin Security Review: HTTP Headers
For our seventeenth security review of a WordPress plugin based on the voting of our customers, we reviewed the plugin HTTP Headers.
If you are not yet a customer of the service you can currently sign up for the service for half off and then start suggesting and voting on plugins to get security reviews. For those already using the service that haven’t already suggested and voted for plugins to receive a review, you can start doing that here. You can use our tool for doing limited automated security checks of plugins (now accessible through a WordPress plugin of its own) to see if plugins you are using have possible issues that would make them good candidates to get a review. You can also order a review of a plugin separately from our service.
The review was done on version 1.8.0 of HTTP Headers. We checked for the following issues during this review:
- Insecure file upload handling (this is the cause of the most exploited type of vulnerability, arbitrary file upload)
- Deserialization of untrusted data
- Security issues with functions accessible through WordPress’ AJAX functionality (those are a common source of disclosed vulnerabilities these days)
- Persistent cross-site scripting (XSS) vulnerabilities in publicly accessible portions of the plugin
- Cross-site request forgery (CSRF) vulnerabilities in the admin portion of plugins
-
SQL injection vulnerabilities (the code that handles requests to the database)
-
Reflected cross-site scripting (XSS) vulnerabilities
- Security issues with functions accessible through any of the plugin’s shortcodes
- Security issues with functions accessible through the admin_action action
- Security issues with functions accessible through the admin_init action
- Security issues with import/export functionality
- Security issues with usage of is_admin()
- Host header injection vulnerabilities
-
Lack of protection against unintended direct access of PHP files
- Insecure and unwarranted requests to third-party websites
Results
We found several issues during our review.
Admin_init
We recently added a number of new checks to our reviews. One of those is to check any functions that are registered to run during “admin_init”. There are a couple of connected reasons for that. The first being that functions that are set to run then can even run when request is coming from someone that is not logged in, which is something that some of the developers using it seem to not be aware of. Even if that wasn’t true, since they run for anyone logged in, there could still be issues if something was only meant to be accessible to higher level users. The second is that because of those things, functions running then have been the source of vulnerabilities, including a zero-day vulnerability that was exploited in August in a plugin’s code to save its settings.
This plugin registers the function http_headers_admin() to run during admin_init:
813 | add_action('admin_init', 'http_headers_admin'); |
That function first registers quite a few settings, starting with this one:
480 | register_setting('http-headers-mtd', 'hh_method'); |
That is fine. The rest of the code is where there was a problem, as a number of actions can be taken by anyone since the only thing needed to cause them to run is including several URL parameters with a request:
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 | # When method is changed if (isset($_GET['settings-updated'], $_GET['tab']) && $_GET['settings-updated'] == 'true' && $_GET['tab'] == 'advanced') { update_headers_directives(); update_auth_credentials(); update_auth_directives(); update_content_encoding_directives(); update_expires_directives(); update_cookie_security_directives(); update_timing_directives(); } # When particular header is changed if (isset($_GET['settings-updated'], $_GET['header']) && $_GET['settings-updated'] == 'true' && get_option('hh_method') == 'htaccess') { switch ($_GET['header']) { case 'www-authenticate': update_auth_credentials(); update_auth_directives(); break; case 'content-encoding': case 'vary': update_content_encoding_directives(); break; case 'expires': update_expires_directives(); break; case 'cookie-security': update_cookie_security_directives(); break; case 'timing-allow-origin': update_timing_directives(); break; default: update_headers_directives(); } } |
What was interesting about this code is that it didn’t appear to be used.
Server Side Request Forgery (SSRF)
One the things that we have checked for since the first of these reviews is for a lack of protection against cross-site request forgery (CSRF) in the admin portion of plugins. CSRF is a type of vulnerability in which an attacker can cause someone else to take an action they are allowed to, but didn’t intend to. In looking into instances where the protection is missing or broken we sometimes run into larger issues. In the case of the plugin we found the “Inspect headers” tool on the plugin’s admin page not only lacked protection against CSRF, but also was accessible to anyone by making a request directly to the file, /views/ajax.php, the results for the tool come from.
With that tool you can make a request to other websites through the server, the only limit that you can only make request to URLs that begin http:// or https:// (so you couldn’t say make a request to a FTP address):
10 | if (!(isset($_POST['url']) && preg_match('|^https?://|', $_POST['url']))) |
That is referred to as server side request forgery (SSRF) vulnerability. Part of the risk of that is that it would allow an attacker to make requests to URLs that are accessible to the server, but not directly to the attacker, say another system that the server connects locally.
Lack of Protection Against Direct Access to Files
The plugin’s .php files lacked code at the beginning of the files to restrict direct access to them. Beyond the SSRF issue, we didn’t see anything that could be exploited in the files without the restriction in place.
Security Issues Now Mostly Resolved
While reviewing plugins is usually fairly easy if you have experience with what is being checked on, getting the developer to properly fix the issue isn’t necessarily. In this case it took a couple of releases for the issues to be mostly resolved.
For the issue involving admin_init, in version 1.9.2 the relevant code was moved to a new function http_headers_option() that runs when the actions “added_option” and “updated_option” occur. In version 1.9.4 protection against CSRF was added for that.
For the SSRF issue the code in file /views/ajax.php was removed in version 1.9.4. In version 1.9.2 the code had been copied to a new /views/ajax-inspect.php, which can be accessed through WordPress’ AJAX functionality. In version 1.9.2 when accessing it through AJAX there was protection against CSRF added and in version 1.9.4 a capabilities check was added. The file /views/ajax-inspect.php can be accessed directly for some reason, but that file includes a file /includes/config.inc.php, which first runs code that is intended to block direct access to the file and has the additional impact of causing /views/ajax-inspect.php to stop running before getting to anything of importance.
With the exception of the file just mentioned related to the SSRF vulnerability and the related /includes/http.class.php, the plugin’s files received protection against direct access in version 1.9.2.
Proof of Concept for SSRF
The following proof concept will make a request to the homepage of our website.
Make sure to replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/wp-content/plugins/http-headers/views/ajax.php" method="POST"> <input type="hidden" name="do" value="inspect" /> <input type="hidden" name="url" value="https://www.pluginvulnerabilities.com" /> <input type="submit" value="Submit" /> </form> </body>