Summary

It’s incredibly easy to think you’ve found a good little vulnerability in a WordPress plugin or theme only to be confounded to find out that your payload just doesn’t work… The culprit is quite often magic quotes, especially when you’re starting off.

What is does

Magic quotes is quite simple, it protects the variables in $_GET, $_POST, $_REQUEST, $_COOKIES and $_SERVER by calling PHP’s addslashes function. This will escape all single, double quotes to each variable within these arrays (note: it also escapes backslashes and null bytes).

This instantly breaks a lot of SQL, PHP object and JavaScript injection payloads from functioning. However, one bit of good news for bug hunters is that HTML will happily ignore any backslashes allowing for HTML injection.

Proof of Concept

This can be demonstrated with some simple proof-of-concept scripts below, where we print out the a variable from the GET request in a few different contexts:

SQL injection context

<?php

include 'wp-load.php';

global $wpdb;

$sql = "SELECT '" . $_GET['a'] . "'";

$out = $wpdb->get_results($sql);

echo "SQL input: $sql <br>\n";
echo "SQL output: ";
print_r($out);

If we make a request using to the page with /sql.php?a=1'+or+1='1

SQL input: SELECT '1\' or 1=\'1'
SQL output: Array ( [0] => stdClass Object ( [1' or 1='1] => 1' or 1='1 ) ) 

We can see that the SQL injection does not occur because the escaped ' does not escape the string context.

JavaScript variable injection context

<?php

include 'wp-load.php';

?>

<script>
let version = '<?php echo $_GET["a"];?>';
</script>

If we make a request using to the page with /js.php?a=1'+alert(1)+'

<script>
let version = '1\' alert(1) \'';
</script>

We can see that we don’t pop an alert as again we do not escape the string context.

HTML attribute injection context

<?php

include 'wp-load.php';

?>
<a href='/user/test' title='<?php echo $_GET["a"];?>'>user</a>

If we make a request using to the page with html.php?a=1'+onfocus=alert(1)+autofocus=1+tabindex=1. We see that the HTML will become:

<a href='/user/test' title='1\' onfocus=alert(1) autofocus=1 tabindex=1'>user</a>

And success! We pop an alert as we now have arbitrary JavaScript execution by injecting additional attributes into the HTML element.

Source

For those interested this occurs in WordPress is inside the function wp_magic_quotes that adds ‘magic quotes’ to the user-supplied variables when array_merge is called with the GET and POST superglobals:

function wp_magic_quotes() {
	// Escape with wpdb.
	$_GET    = add_magic_quotes( $_GET );
	$_POST   = add_magic_quotes( $_POST );
	$_COOKIE = add_magic_quotes( $_COOKIE );
	$_SERVER = add_magic_quotes( $_SERVER );

	// Force REQUEST to be GET + POST.
	$_REQUEST = array_merge( $_GET, $_POST );
}

And add_magic_quotes is defined as:

function add_magic_quotes( $input_array ) {
	foreach ( (array) $input_array as $k => $v ) {
		if ( is_array( $v ) ) {
			$input_array[ $k ] = add_magic_quotes( $v );
		} elseif ( is_string( $v ) ) {
			$input_array[ $k ] = addslashes( $v );
		}
	}

	return $input_array;
}