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

6

47

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?

Monkhmer answered 21/1, 2012 at 0:49 Comment(6)
Please show us a concise test script that produces this behavior for you. Maybe the problem is just in the testing method.Danny
You may want to check the headers your browser sends.Phalange
Does ini_get report that it is off?Olivaolivaceous
Thank you all for your feedback! :)Monkhmer
I can assure you, this problem also happens in November 2019 with PHP 7.1.33 (2019-10-23) and WordPress 5.2.4 (2019-10-14)... (Magic quotes were removed in PHP 5.4.0.)Avraham
You should change the accepted answer to one that doesn't open up security vulnerabilities in people's WP installations.Gadmann
A
20

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.

Apocrine answered 11/12, 2013 at 8:20 Comment(12)
Interesting solution! Do you have this specific solution working in a plugin already?Monkhmer
@Monkhmer Yes, I wired it up last night, it's working like a charm!Apocrine
To completely remove the annoying escapes you can comment out wp_magic_quotes(); from the wp-settings.php and it will no longer apply the escapes to POST, GET, etc.Support
@Support That's going to break Wordpress code that depends on them being escaped; not a good idea.Apocrine
@Apocrine Haven't run into any problems at all since commenting it out. Though you make a good point.Support
Thanks for the wp-settings solution, was having a problem with wordpress and CI integrated together, where wordpress would modify all the post data even in CI.Mielke
"What's interesting though is this call is made after plugins have been loaded, before the theme is loaded. " So in my plugin, I don't need to worry about it? the $_POST etc.. aren't escaped yet? But is it the case for all hooks that my plugin implements?? any reason why magic quotes are added at theme level?Bluegill
I'm not really sure why it's applied after the plugins and before the view, but that seems to be the case. The WP devs could probably shed some light on that, though I don't find them very clever in general. Here's another situation I thought they handled poorly for example.Apocrine
how to check magic quotes have been applied or not? is there a function? I assume if may be magic quoted in some cases and not magic quotes in other cases depending on the action my plugin is implementing?Bluegill
I have posted a new question so as it's easier to follow: #23092716Bluegill
Nearly 6 years later there is still the unconditional call to wp_magic_quotes() in wp-settings.php (WordPress 5.2.4, 2019-10-14).Avraham
@PeterMortensen WordPress - the living PHP Anti-pattern reference, lol.Apocrine
M
44

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.

Monkhmer answered 21/1, 2012 at 1:8 Comment(7)
thank you for sharing info about such a ridiculuos wp behaviorPlumb
after 2 years, we still have this bug. Notice that you can not use this few times, so if you use it, other plugin use it, result will be unpredictable.Hauler
VilliusL, if you have issues with this interfering with other plugins, remember, that you're not obligated to overwrite the original superglobals ($_POST, $_GET, etc). You could always do something like: $post_copy = array_map('stripslashes_deep', $_POST); (Note that $post_copy would not be a superglobal, so you would use global $post_copy; or pass $post_copy as a parameter.Monkhmer
WARNING: Using the code in this answer as-is is a potential security vulnerability. See this quote form the WordPress ticket. "Currently magic quotes are necessary because removing them could easily open us to unexpected security vulnerabilities. And even if we fix all those in core, there would likely be hundreds (conservative estimate) of plugins that would be suddenly vulnerable because they were assuming slashed data and it wasn't."Annual
@AlexanderO'Mara: Thanks for your warning! One workaround to this would simply be to not overwrite the original superglobals. Instead, one could use separate, regular arrays like $post, $get, etc. (e.g. $post = array_map('stripslashes_deep', $_POST);)Monkhmer
Also note @quickshiftin's excellent answer as a nice alternative.Monkhmer
18322 was reopened on 2019-11-22 (that is nearly 8 years later).Avraham
A
20

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.

Apocrine answered 11/12, 2013 at 8:20 Comment(12)
Interesting solution! Do you have this specific solution working in a plugin already?Monkhmer
@Monkhmer Yes, I wired it up last night, it's working like a charm!Apocrine
To completely remove the annoying escapes you can comment out wp_magic_quotes(); from the wp-settings.php and it will no longer apply the escapes to POST, GET, etc.Support
@Support That's going to break Wordpress code that depends on them being escaped; not a good idea.Apocrine
@Apocrine Haven't run into any problems at all since commenting it out. Though you make a good point.Support
Thanks for the wp-settings solution, was having a problem with wordpress and CI integrated together, where wordpress would modify all the post data even in CI.Mielke
"What's interesting though is this call is made after plugins have been loaded, before the theme is loaded. " So in my plugin, I don't need to worry about it? the $_POST etc.. aren't escaped yet? But is it the case for all hooks that my plugin implements?? any reason why magic quotes are added at theme level?Bluegill
I'm not really sure why it's applied after the plugins and before the view, but that seems to be the case. The WP devs could probably shed some light on that, though I don't find them very clever in general. Here's another situation I thought they handled poorly for example.Apocrine
how to check magic quotes have been applied or not? is there a function? I assume if may be magic quoted in some cases and not magic quotes in other cases depending on the action my plugin is implementing?Bluegill
I have posted a new question so as it's easier to follow: #23092716Bluegill
Nearly 6 years later there is still the unconditional call to wp_magic_quotes() in wp-settings.php (WordPress 5.2.4, 2019-10-14).Avraham
@PeterMortensen WordPress - the living PHP Anti-pattern reference, lol.Apocrine
K
3

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); }

?>
Kilo answered 9/11, 2015 at 7:54 Comment(0)
C
0

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!

Consols answered 28/11, 2015 at 21:7 Comment(3)
But just to be clear, this also permanently modifies the superglobals, right?Monkhmer
(E.g. All other plugins/themes/the core will also be fed the un-magic-quoted values? It might be better to use plugin-scoped variables instead. Regardless, I like your solution!)Monkhmer
Never touch the superglobals, they are assumed by other code to be slashed. This results in missing backslashes from user input at best, security vulnerabilities at worst. Always use a local copy like $get = stripslashes_deep($_GET);Gadmann
D
0

EDIT:

See @Walf's comment below

Never touch the superglobals, they are assumed by other code to be slashed. This results in missing backslashes from user input at best, security vulnerabilities at worst. Always use a local copy like $get = stripslashes_deep($_GET);

Original answer to AVOID:

WordPress provides a solution for this by using the WordPress function stripslashes_deep. So, the snippets mentioned in @rinogo's answer would become :

$_GET     = stripslashes_deep($_GET);
$_POST    = stripslashes_deep($_POST);
$_COOKIE  = stripslashes_deep($_COOKIE);
$_REQUEST = stripslashes_deep($_REQUEST);

Also a note, WordPress doesn't say anything about the $_SERVER global variable, so I would assume it's not affected.

WordPress adds slashes to $_POST/$_GET/$_REQUEST/$_COOKIE regardless of what get_magic_quotes_gpc() returns. So in the context of WordPress, stripslashes() or stipslashes_deep() should always be used when using those variables.

Donothingism answered 24/4, 2017 at 12:43 Comment(4)
It's only in wordpress you are escaping the data first and then un-escaping it on the very next line.Plumb
@Your Common Sense Ironic right ? Wordpress always did stuff like this and after a few years you get a new version with option to disable the actual functionality, by defining a constant in wp-config or something ...Donothingism
Never touch the superglobals, they are assumed by other code to be slashed. This results in missing backslashes from user input at best, security vulnerabilities at worst. Always use a local copy like $get = stripslashes_deep($_GET);Gadmann
Thanks for pointing this out @Gadmann I'll edit my answer to not cause someone big trouble in the long runDonothingism
S
-2

Or, just do like I did. Comment out all of the implementation in load.php's wp_magic_quotes() method.

I have no use for magic quotes. This was causing me many more headaches than it was worth. Personally, I prefer to maintain my own discipline of input sanitation. I just don't want to start forming bad programming habits.

But, I do understand WordPress' compulsion to include such a "feature". Perhaps the development community would be best served with a global option to disable it.

Sextan answered 11/1, 2015 at 1:56 Comment(2)
If this solution works for you, then great! Two potential problem areas to be aware of, though: 1) Modifying core is error-prone and even dangerous (security-wise), especially with regard to something like escaping. 2) Many plugins are designed to work nicely with wp_magic_quotes(). Modifying that implementation could alter their behavior. More discussion on retaining/killing wp_magic_quotes(): core.trac.wordpress.org/ticket/18322Monkhmer
See my comment to @Support beneath my solution, this is not a good idea as you'll break Wordpress code that expects these values to be escaped.Apocrine

© 2022 - 2024 — McMap. All rights reserved.