Spare Pencil

June 3 15 comments
Watch the replies

Wordpress and the cookies

Important: The workaround provided in this post is not reliable enough, there is now a more secure way to fix this problem. The new fix is available as a convenient plugin.

Since the release of Wordpress 2.0, a pesky bug has been annoying several bloggers. The nice AJAX effects in the administrator panel stopped working; even a fully privileged user would receive a “You don’t have permission to do that” message when trying to add or remove categories, posts and more.

The Wordpress developers are aware of this bug, but the ticket has recently been moved to milestone 2.4. Which basically means we bloggers will have to wait another few months for an official fix.

So far, it has been unclear as to what is causing this problem. I recently upgraded to Wordpress version 2.2 (many important bugs have been fixed, I recommend it) but I was disappointed to find out that the AJAX problem was still there. That’s when I decided to check the root of this bug myself.

Suhosin

Suhosin is a patch for PHP that hardens it against a wide range of web attacks. It’s got a truly amazing set of precautions for problems of which you didn’t even know they existed.

One of its features is cookie encryption. This basically encrypts the cookie using both server and client specific information (a custom key, user agent and more) before sending it to the client. This feature is useful, because if some malicious code on the client-side (XSS is most common) manages to get a hold of the cookie it can not do anything with it.

In addition to encrypting the cookie before sending it to the client, Suhosin will also decrypt cookies it receives so you can use it in PHP without problems.

Or can we…?

All of the AJAX-powered actions performed in the Wordpress admin panel go to one script: /wp-admin/admin-ajax.php
The first thing this script does is calling the function check_ajax_referer() (located in /wp-includes/pluggable.php).
This is what the function looks like:

function check_ajax_referer() {
	$cookie = explode('; ', urldecode(empty($_POST['cookie']) ? $_GET['cookie'] : $_POST['cookie'])); // AJAX scripts must pass cookie=document.cookie
	foreach ( $cookie as $tasty ) {
		if ( false !== strpos($tasty, USER_COOKIE) )
			$user = substr(strstr($tasty, '='), 1);
		if ( false !== strpos($tasty, PASS_COOKIE) )
			$pass = substr(strstr($tasty, '='), 1);
	}
	if ( !sp_login( $user, $pass, true ) )
		die('-1');
	do_action('check_ajax_referer');
}

As you can see, Wordpress avoids using the HTTP Cookie header for AJAX requests (it’s even mentioned in the comment). Instead, the cookie is appended to the request data.This is done, of course, on the client side using Javascript.

When document.cookie is appended to the request, there is still no problem. The Cookie header and the document.cookie are practically the same. But once the request arrives at the server, Suhosin will only decrypt the Cookie. Not the appended document.cookie, because it is not recognised as a cookie at all (it’s just an encoded variable).

So when Wordpress reads the cookie from the request data, it actually reads the encrypted cookie. And you can’t log in with an encrypted cookie, so you are denied permission.

There is a workaround

Fortunately, most browsers (as far as I know) also send the Cookie header. Suhosin flawlessly decrypts the data contained in this header and puts everything in the $_COOKIE variable.

I am not really sure why the Wordpress developers chose to send cookies via the request body, but until they come up with a better fix for this problem, I am providing the following workaround:

function check_ajax_referer() {
	// Suhosin workaround
	$dough = ini_get('suhosin.cookie.encrypt');
	if ( 1 == $dough || 'On' == $dough || 'on' == $dough ) {
		$user = $_COOKIE[USER_COOKIE];
		$pass = $_COOKIE[PASS_COOKIE];
	} else {
		$cookie = explode('; ', urldecode(empty($_POST['cookie']) ? $_GET['cookie'] : $_POST['cookie'])); // AJAX scripts must pass cookie=document.cookie
		foreach ( $cookie as $tasty ) {
			if ( false !== strpos($tasty, USER_COOKIE) )
				$user = substr(strstr($tasty, '='), 1);
			if ( false !== strpos($tasty, PASS_COOKIE) )
				$pass = substr(strstr($tasty, '='), 1);
		}
	}
	if ( !sp_login( $user, $pass, true ) )
		die('-1');
	do_action('check_ajax_referer');
}

This can be applied to any Wordpress installation. Servers without Suhosin will run Wordpress the regular way. Servers with Suhosin cookie encryption enabled will make Wordpress fall back to using the standard cookie.

I might look into writing a PHP function that simulates Suhosin cookie decryption. This will allow Wordpress to use the cookie in the request in both cases. Unfortunately, I was unable to find sufficient information for this.

Patch for Wordpress 2.2

I created a patch file for Wordpress 2.2. It must be applied to /wp-includes/pluggable.php.

Download the file.
Linux users can patch using:

$ patch /path/to/wp-includes/pluggable.php /path/to/pluggable.diff
Windows users can download GNU patch for Windows. And run it from the Command line interface:

C:\path\to\patch.exe \path\to\wp-includes\pluggable.php \path\to\pluggable.diff

15 Comments

Write one, too!

Reply Here




You may use some markup in your comment, unless you're drunk.
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>