Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

With "magic quotes" disabled, why does PHP/WordPress continue to auto-escape my POST data?

It's a simple question with a strangely elusive answer.

get_magic_quotes_gpc() reports 0. I repeat, magic quotes are off. Magic quotes appear to have been disabled in php.ini (not at runtime).

Nevertheless, all POST data including single quotes (') is escaped when accessed in PHP. What could be causing this?


While preparing a test case, I discovered the general origin of the problem. We're bootstrapping WordPress as our application integrates with a WordPress multisite installation. When I disable the WordPress bootstrapping, the auto-escaping is disabled. Where may WordPress' auto-escape code be located?

like image 460
rinogo Avatar asked Jan 21 '12 00:01

rinogo


4 Answers

I think I found it. Problem (bug): http://core.trac.wordpress.org/ticket/18322

Solution: http://codex.wordpress.org/Function_Reference/stripslashes_deep

    $_GET       = array_map('stripslashes_deep', $_GET);
    $_POST      = array_map('stripslashes_deep', $_POST);
    $_COOKIE    = array_map('stripslashes_deep', $_COOKIE);
    $_SERVER    = array_map('stripslashes_deep', $_SERVER);
    $_REQUEST   = array_map('stripslashes_deep', $_REQUEST);

Note: As suggested by @Alexandar O'Mara, you might want to reconsider overwriting the superglobals like this. If it's appropriate for your situation, for example, you might just "strip locally" using an alternative like $post = array_map('stripslashes_deep', $_POST);

Also see @quickshiftin's excellent answer.

like image 149
rinogo Avatar answered Nov 03 '22 16:11

rinogo


Expanding on @rinogo's answer with a deeper explanation, and offering another workaround.


In wp-settings.php there's an unconditional call to wp_magic_quotes

// Add magic quotes and set up $_REQUEST ( $_GET + $_POST )
wp_magic_quotes();

WordPress escapes quotes no matter what

function wp_magic_quotes() {
    // If already slashed, strip.
    // Escape with wpdb.
    // Force REQUEST to be GET + POST.
}

What's interesting though is this call is made after plugins have been loaded, before the theme is loaded. Sooo, at the top of your plugin

// A hack to cope with un-configurable call to wp_magic_quotes
// E.G. Make the original $_POST available through a global $_REAL_POST
$_REAL_GET     = $_GET;
$_REAL_POST    = $_POST;
$_REAL_COOKIE  = $_COOKIE;
$_REAL_REQUEST = $_REQUEST;

Then you can freely use $_REAL_POST et al. in place of $_POST (remembering it's a global, not a superglobal) where you need to. Also remember that while your plugin has loaded before the theme, if the theme calls down into one of the plugin functions which uses $_POST, it should read from $_REAL_POST to get the unescaped values.

like image 29
quickshiftin Avatar answered Nov 03 '22 16:11

quickshiftin


I just had to deal with this issue and found what I think is a pretty nice workaround. It ensures that the GPCs are never slashed. I just put this at the top of my plugin file (it would work at the top of a theme too, I think):

add_action( 'init', 'unslash_gpc' );
function unslash_gpc() {
    $_GET       = array_map('stripslashes_deep', $_GET);
    $_POST      = array_map('stripslashes_deep', $_POST);
    $_COOKIE    = array_map('stripslashes_deep', $_COOKIE);
    $_SERVER    = array_map('stripslashes_deep', $_SERVER);
    $_REQUEST   = array_map('stripslashes_deep', $_REQUEST);
}

And now everything is perfect!

like image 2
Jordan Avatar answered Nov 03 '22 16:11

Jordan


The best answer provided here is to copy for own use like:

$post = array_map('stripslashes_deep', $_POST);

There's a theoretical problem with this however: since you're working with a duplicate, you can't persist any changes to the superglobals (hey, I'm not saying it's a good practice, alright?).

Solution: accessor methods

In an attempt to solve this mess in a definite manner and without any side effects, I made "accessor methods" which transparently apply stripslashes_deep() or addslashes_deep()* to get/set requests to the following superglobal arrays:

* I had to throw addslashes_deep() together from WordPress' stripslashes_deep().

  • $_GET
  • $_POST
  • $_COOKIE
  • $_SERVER
  • $_REQUEST

You can use them like:

echo _get('username');    // echo stripslashes_deep($_GET['username']);
_cookie('name', 'value'); // $_COOKIE['name'] = addslashes_deep('value');

Here's the code (I call it gpcsr.php):

<?php

// cat stripslashes_deep() | sed 's/stripslashes/addslashes/g'
function addslashes_deep( $value ) {
    if ( is_array($value) ) {
        $value = array_map('addslashes_deep', $value);
    } elseif ( is_object($value) ) {
        $vars = get_object_vars( $value );
        foreach ($vars as $key=>$data) {
            $value->{$key} = addslashes_deep( $data );
        }
    } elseif ( is_string( $value ) ) {
        $value = addslashes($value);
    }

    return $value;
}

function _generic_slashes_wrap(&$arr, $key, $value = null) {
    if (func_num_args() === 2) return stripslashes_deep($arr[$key]);
    else $arr[$key] = addslashes_deep($value);
}

function _get       ($key, $value = null) { if (func_num_args() === 1) return _generic_slashes_wrap($_GET,      $key); else _generic_slashes_wrap($_GET,        $key, $value); }
function _post      ($key, $value = null) { if (func_num_args() === 1) return _generic_slashes_wrap($_POST,     $key); else _generic_slashes_wrap($_POST,       $key, $value); }
function _cookie    ($key, $value = null) { if (func_num_args() === 1) return _generic_slashes_wrap($_COOKIE,   $key); else _generic_slashes_wrap($_COOKIE,     $key, $value); }
function _server    ($key, $value = null) { if (func_num_args() === 1) return _generic_slashes_wrap($_SERVER,   $key); else _generic_slashes_wrap($_SERVER,     $key, $value); }
function _request   ($key, $value = null) { if (func_num_args() === 1) return _generic_slashes_wrap($_REQUEST,  $key); else _generic_slashes_wrap($_REQUEST,    $key, $value); }

?>
like image 1
André Chalella Avatar answered Nov 03 '22 17:11

André Chalella