Due to a small Twitter discussion, which started after a tweet of Pippin I realized again, the WordPress HTTP API has “safe” functions. Some days or weeks ago I’ve heard of them already, but I didn’t check on them. The topic is the following: The functions
wp_remote_head() do have siblings, which are not documented in the Codex but only in the reference:
After the WordPress community discusses largely the top security, I was quite alarmed. What are these “safe”-functions and what do they do? What is the difference between
How the HTTP API works
Before we start, let’s be clear, what the HTTP API actually does. In wp-includes/http.php several functions are located, with which you can perform HTTP requests. All these functions basically interact with the
WP_Http() class, located in wp-includes/class-http.php.
So it is relatively easy to perform a POST request with the function
wp_remote_post() and to work with the received data afterwards. Tom McFarlin has published a tutorial how to work with this function on Tuts+.
What is the difference between wp_remote_post() and wp_safe_remote_post()?
If you read the source code, you will find only one difference:
wp_safe_remote_post() – and the same applies to all safe_remote functions – extends the argument array with the boolean
'reject_unsafe_urls' set to
As you can see, these arguments are passed to the
WP_Http() class. So what does
'reject_unsafe_urls' accomplish there? Once this boolean is
true, the URL gets validated by the function
What does wp_http_validate_url()?
Since WordPress 3.5.2
wp_http_validate_url() can be found in the core. If you read the description in the source, it says.
[The function] validate[s] a URL for safe use in the HTTP API.
How does the function perform this task? As a first step, it checks (by using
wp_kses_bad_protocol()), if the URL has a valid protocol. Valid protocols are only HTTP and HTTPS. If
'reject_unsafe_urls' is not
true SSL is also perceived as a valid protocol (s. wp-includes/class-http.php L186). If the protocol is not valid the function will return
Also a URL is considered to be unsafe, if it contains a username or a password, by which a user tries to authenticate himself. The following URL would be rejected: http://benutzername:firstname.lastname@example.org
If the host of the URL contains one of the following symbols, the URL will be rejected: :#?
URLs which do resolve to another port than 80, 443 or 8080 – so to speak the usual HTTP posts – will be rejected.
And this is for safety reasons?
To understand the history of the safe_remote functions it helps to study the ticket 24646. In WordPress Version 3.5.2 the core developers focused on securing the HTTP API against Server Side Request Forgeries. Lets just assume, the HTTP API would not check the protocols before firing the request and the requested URL would be
dict://localhost:11211/stat. This would send the string “
stat” to the locale Memcached, which usually listens to the port 11211. Since the request was locally, no firewall would prevent the server to respond to this request. To be short here: Danger! In 3.5.2 the developers closed this issue, but they overshoot a little bit. The result was, WordPress was not able to access the locale installation with the HTTP API. If you wanted to fetch your own feed using
wp_remote_get() the script blocked the resource.
So they had to step back a little bit. Well, the mentioned attack above is not possible, even using
wp_remote_get(). But they introduced the safe remote functions. As long as you can be sure the URL can not be used for an attack on the own system, you still can rely on
wp_remote_head(). This is useful, if you want to perform HTTP requests, which target your own host.
But if you can not be sure about the URL, you should use
wp_safe_remote_head() to raise the level of security. From now on the request is limited to the conditions mentioned above.
If you are not sure about the URL which will be passed through wp_remote_get() use wp_safe_remote_post(). It is simply the safe way to do it.