Blog

WordPress and 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() {
  // AJAX scripts must pass cookie=document.cookie
  $cookie = explode('; ', urldecode(empty($_POST['cookie']) ? $_GET['cookie'] : $_POST['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 {
    // AJAX scripts must pass cookie=document.cookie
    $cookie = explode('; ', urldecode(empty($_POST['cookie']) ? $_GET['cookie'] : $_POST['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

June 3, 2007
8 comments

Posted in:
Articles, Programming


Comments

I tried your workaround and it seemed to fix the post deletion but still cannot delete category. Any ideas?

Reply

KevKha
June 11, 2007 at
11:39 am


I can delete category now by the following changes:

if ( !function_exists('check_ajax_referer') ) :
function check_ajax_referer() {
   $user = $_COOKIE[USER_COOKIE];
   $pass = $_COOKIE[PASS_COOKIE];
   if ( !sp_login( $user, $pass, true ) )
      die('-1');
   do_action('check_ajax_referer');
}

Reply

KevKha
June 12, 2007 at
6:38 am


Good. Seems like the statement that checks for Suhosin cookie encryption does not apply to your server/host.

It would be helpful if you could tell what the following script shows when executed on your host:

<?php
echo ini_get('suhosin.cookie.encrypt');
?>

(mind the quotes, they should be a regular single quote, instead of the fancy ones that WordPress will show you)

Reply

Bas
June 12, 2007 at
2:27 pm


how do i apply this to my wordpress i dont know how

Reply

baterya
June 30, 2007 at
4:01 pm


Follow the instructions for patching the file using the provided patch. Then upload the patched file, replacing the original one.

Reply

Bas
June 30, 2007 at
6:18 pm


Thanks for making a post about this and providing the patch, but this fix still did not work for me. I cannot add categories or delete posts. This is about the biggest problem I have had with the new version so far. The other problem was simply 403 “Forbidden” messages showing up on the dashboard, which I managed to fix…

Reply

Anna
July 13, 2007 at
6:27 am


Thanks for the hack. It works with MU 1.3 which was giving me permission errors when trying to add new categories, and delete posts. Both fixed…

Great work

Reply

Matt
November 8, 2007 at
7:25 pm


Hi! I was surfing and found your blog post… nice! I love your blog. :) Cheers! Sandra. R.

Reply

sandrar
September 10, 2009 at
4:18 pm


Write a reply

Your comment goes here… 




*required