26 Nov

Security Tip for WordPress Plugin Developers: Use wp_safe_redirect() Instead of wp_redirect()

Seeing as even a number of the 1,000 most popular WordPress plugin in the Plugin Directory are not doing things in a secure way we thought it would be a good idea to emphasize something from a previous post, which is that if you are using the function wp_redirect() to handle redirections that will only go to other pages on the same website you should instead use wp_safe_redirect(). That latter function makes sure that any attempt to redirect to another website else will not work, which can help you to avoid open redirect vulnerabilities in your plugins.

04 Jan

Tip For Security Researchers: Make Sure The Developer Has Actually Fixed The Vulnerability You Found

When reviewing a report of a vulnerability in a WordPress plugin while preparing to add it our service’s dataset we test out the vulnerability. We do that to help us to do a number of things, including making sure the vulnerability actually exists and to determine what versions are vulnerable. Through that we often find that the vulnerabilities have only been partially fixed or have not been fixed at all, despite the discover of the vulnerability stating that it has been fixed.

If the vulnerability is likely to be exploited that can be a big issue, since hackers are unlikely to check what version of the plugin is in use, instead they will just try to exploit it. So people that have updated won’t be protected by keeping up to date and they even may think they are if the run across a source claiming it has been fixed (we seem to be the only ones that do that sort of checking with WordPress plugins, so if someone else is telling you a vulnerability has been fixed they likely don’t know if that is true or not).

What looks to be a common cause of this is that discoverer of the vulnerability gets told by the developer of the plugin that it has been fixed and assumes that to be the case. Considering how many vulnerabilities involve failures to do security basics in the first place, it wouldn’t be surprising that the plugin’s developer wouldn’t actually know if it has been fixed. In other cases with more complicated issues it is understandable that the developer might not fully grasp the issue at first and what needs to be done to fully fix it.

In our experience, when we get in touch with the plugin’s developer to let them know that the vulnerability hasn’t been fixed and offer help on getting it resolved, the issue is often quickly fixed. So checking if the vulnerability has in fact been fixed before disclosing it will make a big difference towards better security.

A Partial Fix

To give you an example of the need to make sure the vulnerability has been fixed let’s take a look at a recently disclosed server side request forgery (SSRF) vulnerability in the plugin Nelio AB Testing discovered by Yeo Quan Yang.

A server side request forgery vulnerability involves a request being made by the server, which could allow an attacker to send request to systems that are accessible by the server, but not publicly accessible. The request could also allow them increased access to publicly accessible systems than they normally would have.

In the case of the Nelio AB testing when a request was to sent to the file /ajax/iesupport.php in version 4.5.8 the value of the POST input “originalRequestUrl” was used for the value of the variable “$url”:

if ( isset( $_POST['originalRequestUrl'] ) ) {
	$url = $_POST['originalRequestUrl'];
	$url = preg_replace( '/^\/\//', '', $url );

That value of that variable then is used as the URL to be requested by a curl request sent from the server:

curl_setopt( $ch, CURLOPT_URL, $url );

In the discoverer’s post they mention that they were told the vulnerability was fixed:

I’ve since reported this to the plugin author and was told it has been fixed!

That appears to be in reference to version 4.5.9, which adds a restriction to what the POST input “originalRequestUrl” could be set to:

if ( isset( $_POST['originalRequestUrl'] ) ) {
	$url = $_POST['originalRequestUrl'];
	$url = preg_replace( '/^\/\//', '', $url );
	if ( ! preg_match( '/^https?:/', $url ) ) {
	}//end if

So now the URL to be requested has to either be the HTTP or HTTPS protocol, which does limit things since curl supports:


But that doesn’t resolve the issue entirely since it would still allow an attacker to requests to arbitrary HTTP or HTTPS URLs.

When we reviewed the vulnerability we noticed the issue and notified the developer of the issue. In less than a day they had put in place a fix and replied to us asking us to review it before the released it.

That fix involved taking the domain that requests from this file are intended to be sent to:

define( 'NELIOAB_BACKEND_NAME', 'nelioabtesting' );

If the URL to be requested isn’t from that domain then the file exits before the request would be made:

$url = $_POST['originalRequestUrl'];
$url = preg_replace( '/^\/\//', '', $url );
$data = $_POST['data'];
// If the URL isn't the one we expect, leave.
if ( strpos( $url, NELIOAB_BACKEND_DOMAIN . '/' ) !== 0 ) {
	// Silence is gold
	header( 'HTTP/1.1 400 Bad Request' );

That change resolved the issue.

01 Dec

Tip For Security Researchers: WordPress Uses a Nonce to Protect Against Cross-Site Request Forgery (CSRF)

For the last three false reports of vulnerabilities in WordPress plugins we have discussed, there has been a common denominator that we don’t quite understand. Each has involved a claim that a plugin has a cross-site request forgery (CSRF) vulnerability, but in the proof of concept for exploiting each of the vulnerabilities there has been nonce included. Seeing a nonce is what is used in WordPress to protect against that type of vulnerability, we have a hard time understanding what is going on here, other than people without the proper knowledge to make a claim that this type of vulnerability exist are in fact doing that.

When used in a form a simple version of the nonce looks like this:

<input type="hidden" id="_wpnonce" name="_wpnonce" value="aa27b52873" />

While it is not required to actually use the word “nonce”, in most cases it will be labeled as such.

While the existence of a valid looking nonce in a proof of concept of a vulnerability likely indicates that the report is false, the existence of a nonce in a plugin’s pages is not always an indication that there is not a CSRF vulnerability, as plugins do not always actually check if the nonce exists or that it is valid when processing the request tied to it. One way to test out if the CSRF protection is properly functioning is to use the developer tools in your web browser to modify the value of the nonce or remove it and see if the request is still successful.

24 May

Tip For Security Researchers: wp_insert_post() and wp_update_post() Sanitize the Submitted Input

From reviewing lots of vulnerability reports one of our big takeaways is that the proper testing of suspected vulnerabilities often isn’t being done by security researchers. Without doing that you miss an easy to chance to catch things happening that nullify potential vulnerabilities in part or sometimes in full.

One recent example we noticed repeating an issue from a previous false report of a vulnerability, involves a correctly identified cross-site scripting (CSRF) vulnerability in a plugin that the discoverer then extrapolated could be lead to persistent cross-site scripting (XSS) due to what the CSRF vulnerable function does:

The function is intended to create or modify a slideshow object stored in the wp_posts table. However it doesn’t check the post type so it can be maliciously used to modify anything in that table, including normal posts and pages. This way the attacker can inject arbitrary JavaScript on the site, escalating this bug to stored XSS.

Looking at the relevant code you can certainly see why they might believe this since you can see in the plugin’s code that no sanitization is done on the POST variable “content” before modifying a post:

        //edit post
        $slide_type = htmlspecialchars($_POST['slide_type']);

        $title = htmlspecialchars($_POST['title']);
        $content = $_POST['content'];

        // Create post object
        $my_post = array(
          'post_title'    => $title,
          'post_content'  => $content,

        // Insert the post into the database
        wp_update_post( $my_post );

Testing this out though would have shown that the sanitization is being done at some point and therefore the cross-site scripting can not occur.

The harder method to find this out would be to follow the path the data takes from the plugin into WordPress. If you do that you can see that the function wp_update_post() ends by calling the function wp_insert_post(), which in turns

passes data through sanitize_post(), which itself handles all necessary sanitization and validation (kses, etc.).

So that is what prevents the persistent cross-site scripting (XSS) from occurring, but with proper testing you wouldn’t need to know that to have avoid this type of mistake.