This is a blog post about research of an additional vulnerability scenario of the root cause that led to the publicly known WordPress Core Blind SSRF. More affected components were found that may affect hundreds of plugins in the wild.
WordPress core itself is not affected by this, but the plugins that are using the vulnerable built-in functions mentioned below are affected.
Introduction
In September 2022, SonarSource published an advisory on a still-unpatched WordPress Core Unauthenticated Blind SSRF. The article concludes that it is not possible to have major impact without having another vulnerability in the system, so it is negligible to some extent. According to the blog post, the root cause of the vulnerability is the wp_http_validate_url()
function which is vulnerable to a DNS rebinding attack.
But is that everything there is to this unpatched vulnerability or rather the unpatched root cause of the vulnerability?
In this blog post, we explore the unknown horrors of the wp_http_validate_url()
leading to an actual impact in a real-world scenario without having to rely on other vulnerabilities.
The Obvious Part
There is a built-in function in WordPress Core called wp_remote_get()
that can be used to request remote hosts through the server. However, it is a publicly known fact that it can lead to a SSRF vulnerability if the user-input URL is passed into the function.
To avoid SSRF vulnerabilities while requesting the user-input URLs, another built-in function named wp_safe_remote_get()
was introduced which is a safer alternative to wp_remote_get()
with additional validations to prevent SSRF issues.
According to the official WordPress documentation:
This function is ideal when the HTTP request is being made to an arbitrary URL. The URL is validated to avoid redirection and request forgery attacks.
But, does it really protect against all request forgery attacks?
Exploring the Unknown
During vulnerability research in some of the WordPress plugins, we saw a lot of plugins using the wp_safe_remote_get() function to prevent SSRF attacks while requesting arbitrary hosts. We are aware that the function prevents SSRFs, but how exactly is the function implemented to prevent it, and what does the code look like?
Curious by this, we decided to explore what are the underlying mechanisms that make wp_safe_remote_get()
so ‘safe’.
Diving into the Function
The source code for wp_safe_remote_get()
looks like this:
function wp_safe_remote_get( $url, $args = array() ) {
$args['reject_unsafe_urls'] = true;
$http = _wp_http_get_object();
return $http->get( $url, $args );
}
On line 2, $args['reject_unsafe_urls']
is being set to true which will eventually pass down the user input to wp_http_validate_url()
function before requesting the URL provided.
There are no additional validations to the function other than the wp_http_validate_url()
function. It is already publicly known to be vulnerable to the DNS rebinding attack, meaning that all the functions using wp_http_validate_url()
solely for validations are also vulnerable.
Limitations
This is not a full-fledged SSRF however, which is also mentioned in the SonarSource post. There are limitations set by the wp_http_validate_url()
function that are below:
- The protocol needs to be either
http://
orhttps://
only - The port can only be one of 80, 443, and 8080
Requesting any internal hosts within these boundaries is possible through this SSRF.
Vulnerable Plugin Code
These examples are taken out of real-world plugins and simplified to the vulnerable part only.
Non-blind SSRF:
<?php
require_once('wp-load.php');
$url = $_GET['url'];
$response = wp_safe_remote_get( $url );
$response = wp_remote_retrieve_body( $response );
echo $response;
?>
In a nutshell, if the plugin is taking user input, passing it to the wp_safe_remote_get() function, and then displaying the response, it is vulnerable to non-blind limited SSRF.
Blind SSRF:
<?php
require_once('wp-load.php');
$url = $_GET['url'];
$response = wp_safe_remote_get( $url );
$response = wp_remote_retrieve_response_code( $response );
if ($response_code == 200) {
echo "Up and running";
else () {
echo "Down or not found!"
}
?>
If the plugin is passing the user input to wp_safe_remote_get()
but not giving the whole response and instead the response code/status, it is vulnerable to blind SSRF limiting the impact to port-scanning of 80, 443, or 8080 port of any internal host.
Attack Demo
We performed this attack on an Apache server running WordPress on port 80, and a php server running on localhost:8080
with some secret information. The vulnerable code has been inserted in the plugin directory /wp-content/plugins/vulnerable-plugin/index.php
Video PoC
GItHub Repository
You can find a vulnerable docker instance and the exploit script to play with on this vulnerability in this Github repository.
More Vulnerable Functions
The same is true for all these other functions which are vulnerable to this attack:
wp_safe_remote_request()
wp_safe_remote_post()
wp_safe_remote_head()
There are some functions that act as a wrapper for the wp_safe_remote_get()
function which are also vulnerable to some extent:
WP_REST_URL_Details_Controller::get_remote_url()
download_url()
wp_remote_fopen()
WP_oEmbed::discover()
Conclusion
It can be concluded that the functions that are considered to be safe and protect the site from SSRF vulnerabilities are not actually 100% safe and can be exploited to some extent. We urge the WordPress Core team to come up with a patch for this vulnerability to make the internet more secure.