Language Features
Magic Quotes
Magic quotes may have been one of the biggest sins of the past in PHP, and we should celebrate the fact that now, ultimately, it is gone from PHP. Magic quotes is a perfect example of how long it can take to undo a broken feature that was introduced into a programming language.
The basic idea of magic quotes, as always, was a good one. As we all know, user input is to be considered dangerous, at least from a security perspective. In fact, a healthy level of security paranoia is to consider every request an attack until the application manages to prove otherwise. With regard to security, we all have heard about the basic rule “filter input, escape output”.
SQL injection attacks, also well-known, can be extremely dangerous. Not only can they enable attackers to read arbitrary data from our database, they might even be able to modify existing data. One textbook example for an SQL injection, for example, is to use the following username in a login screen:
' OR 1=1; --
If the code builds an SQL query to retrieve the user’s ID from a MySQL database like this:
$sql = 'SELECT id FROM user WHERE username=' . $username .
' AND password=' . $password;
the resulting SQL will be:
SELECT id FROM user WHERE username='' OR 1=1; -- AND password=...
It does not really matter which password we enter, we have just gained access to the system without providing proper credentials, and are impersonating the first user listed in the user table. To make things worse, this is often the system administrator.
Magic quotes was conceived to fix this problem, and it utterly failed in trying to do so. The idea was to automatically escape incoming data to protect against SQL injections. With magic quotes enabled, single quotes were escaped with a backslash, rendering the described attack useless, because the SQL statements now expands to:
SELECT id FROM user WHERE username='\' OR 1=1; -- AND password=...
This SQL statement is syntactically incorrect, and cannot be executed. While, depending on how the application deals with such an error, there still may be annoying side effects, at least the application is not vulnerable to an SQL injection attack.
Or so it would seem. One big problem with magic quotes is that
they could be enabled or disabled in php.ini
. This
means that application developers cannot safely rely on the fact
that data has been escaped. So in fact, magic quotes made our world
insecure because either application developers relied on PHP to
automatically escape data, or it would lead to broken data because
the application would escape the data again.
But there are even more problems with magic quotes. The SQL standard requires a single quote to be escaped with another single quote rather than a backslash. MySQL traditionally also accepts a backslash as the escape character, but newer versions of MySQL are stricter in following the SQL standards, so this is not guaranteed to work anymore. Current MySQL versions, for example, come with strict mode enabled by default, which uses the SQL-compliant single quote character rather than a backslash for escaping.
The main conceptual problem with magic quotes, however, is that this approach escapes the data too early. Correct escaping depends on the context. In the SQL context, single quotes are dangerous and must be escaped. In HTML context, the greater than and less than sign are dangerous and must be escaped. In other contexts, yet other characters must be escaped. Escaping too early without knowing the output context can never be secure.
Since an application could not safely rely on magic quotes to be enabled, it had to escape data explicitly by itself. To avoid double escaping, however, the application would have to detect whether magic quotes were enabled. And to be able to escape data correctly according to the output context, the application would in fact have to undo any escaping that PHP had magically done. This was necessary because it was not possible to disable magic quotes at runtime.
To sum it up, magic quotes is a feature that, in hindsight,
nobody ever should have used. In fact, most of what made up the
functionality has already been deprecated in PHP 5.3 and 5.4. The
function set_magic_quotes_runtime
and its alias
magic_quotes_runtime
, which has been deprecated since
PHP 5.3, have finally been removed in PHP 7. Starting with version
5.4, PHP has raised an E_CORE_ERROR
when this function
was called. Trying to use this function in PHP 7 yields a fatal
error:
Fatal error: Uncaught Error:
Call to undefined function set_magic_quotes_runtime() in ...
The two php.ini
settings that control the magic
quotes behavior, magic_quotes_gpc
and
magic_quotes_runtime
have already been removed in PHP
5.4. Trying to set them in php.ini
will lead to a fatal
error:
PHP Fatal error: Directive 'magic_quotes_runtime' is
no longer available in PHP in Unknown on line 0
Error messages “in Unknown on line 0” usually relate to parsing
php.ini
on PHP startup.
The functions get_magic_quotes_runtime
and
get_magic_quotes_gpc
that allow you to query the
respective php.ini
settings still exist, but will
always return false
. In PHP 7.4, those functions have
deprecated, so provided that your error_reporting
level
has been set to a sensible value, PHP will now also yell at you when
you ask for magic quote settings:
PHP Deprecated:
Function get_magic_quotes_runtime() is deprecated in ...
PHP Deprecated:
Function get_magic_quotes_gpc() is deprecated in ...
If you upgrade to PHP 7 from a recent version of PHP 5, magic quotes should not be a problem for you. When you upgrade from PHP 5.3, make sure your code does not rely on magic quotes. If your code does, you probably have some serious security issues to deal with before you should proceed with the upgrade.
The proper fix is to escape data where it is used, and escape it for the context it is being used in. When it comes to escaping, you should not give in to a quick fix, but make sure you have a solid solution in place. Security does not come for free, and it cannot just be built into the language.
Assigning the Result
of new
by Reference
Back in ye olde PHP 4 times, PHP still was a procedural programming language with no proper support for object-oriented programming. Fortunately, this did not keep people from starting to use PHP as an object-oriented language, which in turn motivated building “proper” support for object orientation into PHP 5. Still, looking back to PHP 4, most things related to object-oriented programming were pretty cumbersome.
One particularly annoying “feature” of PHP 4 was to pass scalar values and objects by value, instead of passing objects by reference, as PHP 5 does.
Look at this PHP 4 example:
class Something
{
}
$a = new Something;
$b = $a;
An object is created and stored in $a
(remember: we
are talking PHP 4 here). The line $b = $a
creates a
copy of this object instance. PHP 5 would store a reference
to the object in $a
instead, and make $b
another reference to the same instance. Actually, things are a
little different inside PHP, but this is the behavior that is
exposed.
To prevent PHP 4 from copying the object, we have to add an ampersand to the assignment:
$b =& $a
This forces PHP to make $b
a reference instead of
copying the value. In fact, PHP 4 even creates a copy after creating
an object with new
and assigning it to a variable, so
we must also add an ampersand in the line that creates
Something
:
$a =& new Something;
PHP 4 basically forced the developers to sprinkle their code with lots of ampersands. If you forgot one, you would have two copies of the same object, modify one, and then look at the other copy, wondering why the modification had no effect. It usually took you a day or two to find out what was going on. Been there, done that.
In PHP 5, objects were passed by reference, so the ampersands were not necessary anymore. However, nobody kept you from putting them into the code, potentially even with a negative effect on performance. In PHP 7, you cannot assign the result of a new statement by reference anymore. Consequently, the code
class Something
{
}
$object =& new Something;
will result in a compile-time error in PHP 7:
Parse error: syntax error, unexpected 'new' (T_NEW) in ...
Once created, you can still assign objects by reference, if you insist:
class Something
{
}
$object = new Something;
$reference = &$object;
var_dump($object);
var_dump($reference);
This ampersand has no effect, however, since PHP 7 (and 5, for that matter) automatically treats objects by reference. In other words: when dealing with objects, nowadays it is safe to just drop any ampersands. You can confirm this by running the above program, then removing the ampersand, and re-running the program. In both cases, the output is
object(Something)#1 (0) {
}
object(Something)#1 (0) {
}
The two #1
tell us that both variables refer to an
identical object instance.
Assigning a new result by reference in PHP 5 is still possible,
but earns you an E_DEPRECATED
error with the message
Assigning the return value of new by reference is deprecated
.
If you encounter any syntax error, unexpected 'new'
compile-time errors when trying to execute your code in PHP 7, the
fix is easy: search the codebase for all instances of
=& new
and = &new
(both versions
are possible, and you might have to search for multiple whitespace
characters, not only one). Then remove the ampersands. Your code
will still work fine – unless you try to execute it with PHP 4, of
course.
Static Calls of Instance Methods
In PHP 4, quite a few things were a bit strange. One example is that PHP 4 allowed static calling of instance methods:
class Something
{
function doWork()
{
var_dump('doing work');
}
}
$something = new Something;
Something::doWork();
To keep backwards compatibility with PHP 4, this code works up to
PHP 5, even though doWork()
is not declared
static
, and called statically using ::
rather than the object operator ->
. PHP 5 at least
complains with the E_DEPRECATED
error:
Deprecated: Non-static method Something::doWork()
should not be called statically in ...
Now things will get really weird. When calling an instance method
of another class statically, the $this
context would
carry over from the caller to the called class. In other words,
$this
suddenly refers to another object instance:
class Something
{
public function doWork()
{
var_dump($this);
}
}
class Another
{
public function run()
{
return Something::doWork();
}
}
$something = new Something;
$something->doWork();
$another = new Another;
$another->run();
While in PHP 5, this used to be an E_STRICT
error,
PHP 7 will emit an E_DEPRECATED
error.
There are two problems with E_STRICT
errors. First,
E_ALL
does not include E_STRICT
before PHP
5.4, so in older PHP versions you have to set error reporting to
E_ALL & ~E_DEPRECATED
(both error levels are bit
fields, so you need a bitwise or to combine both). Second, you have
to enable this error reporting level in php.ini
.
Setting it at runtime by calling the error_reporting()
function will not work, because E_STRICT
error messages
are already generated at compile-time.
When the above example gets executed with PHP 5,
$this
inside Something
referred to the
instance of Another
, which is a really unexpected
behavior. PHP 7, on the other hand, will complain about an undefined
variable:
Notice: Undefined variable: this in ...
Since $this
is undefined, trying to call a method
will lead to another error, which in our example turns into a fatal
error, since it is not caught:
Fatal error: Uncaught Error:
Using $this when not in object context in ...
One might wonder why such a weird behavior still has not been removed from PHP after more than 10 years. The reasoning is that before something can be removed from PHP, it has to be marked as deprecated first, only then it can be removed in the next major version. PHP upholds backwards compatibility, that is for sure. Even if it feels ludicrous, we will have to wait until PHP 8 to finally get rid of this PHP 4 relic.
Should your application call non-static methods statically, you
can fix this by injecting an instance of the class. You can then
modify the call to use the object operator ->
instead of the ::
operator:
class Something
{
public function doWork()
{
var_dump($this);
}
}
class Another
{
private $something;
public function __construct(Something $something)
{
$this->something = $something;
}
public function run()
{
return $this->something->doWork();
}
}
$something = new Something;
$something->doWork();
$another = new Another($something);
$another->run();
First, we call the doWork()
method directly on the
instance of Something
, then we call it from the
instance of Another
. The result is
object(Something)#1 (0) {
}
object(Something)#1 (0) {
}
In both cases, $this
refers to the
Something
instance.
For the sake of completeness, we should mention that PHP also allows calling of static methods dynamically:
class Something
{
public static function doWork()
{
var_dump('do work');
}
public function __construct()
{
$this->doWork();
}
}
$something = new Something;
Fortunately, in this case, there is no problem with
$this
context propagation in the first place. This,
however, does not argue away the fact that one should not write code
like this.
ASP Tags
Originally, PHP was designed to be directly embedded into HTML. Today, it is considered a best practice to clearly separate PHP code from HTML, but for templating, both are often still mixed together.
In addition to the PHP open tag <?php
and the
short open tag <?
, PHP, for historic reasons, also
supported the so-called ASP tags <%
and
%>
. In analogy to the <?=
tag, a
<%= $value %>
tag exists, which echos the given
value. ASP tags had to be explicitly turned on through the
php.ini
setting asp_tags
.
This ini setting has been removed in PHP 7, together with the support for ASP tags. As you upgrade to PHP 7, you have to remove the ini setting, otherwise a fatal error occurs when starting up PHP:
PHP Fatal error: Directive 'asp_tags' is no longer
available in PHP in Unknown on line 0
This makes perfect sense, because a project relying on ASP tags
might otherwise deliver their source code to the browser by
accident. This is not to say that this might have happened in the
past, with ASP tags deactivated in php.ini
.
In case your application uses ASP tags, the fix is simple: just
replace <%
tags with <?php
,
<%=
with <?=
and %>
with ?>
. You should at least run a Lint check on all
files that you have modified, to make sure that you did not
introduce syntax error by accident. To run a lint check on a PHP
source file, call PHP at the command line using the -l
command line switch.
call_user_method()
PHP is a very dynamic language. You can, for example, write code like
$method = $_GET('method');
SomeClass::$method();
We are not going to argue about the fact that writing such code is a very bad idea from a security perspective. The example just serves the purpose of showing how flexible PHP can be. Since it is a dynamic language, pretty much everything can be dynamically evaluated at runtime. This is the main reason why some problem can be solved with less code in PHP as compared to languages like Java that are compiled ahead-of-time. On the other hand, PHP gives developers a lot of rope to hang themselves with. With great power comes great responsibility.
PHP is also dynamic about the parameters passed to a dynamically
called user-defined function, or method. The two built-in functions
call_user_func()
or call_user_func_array()
perform dynamic function or method calls, allowing to pass arbitrary
parameters:
class Something
{
public function doWork($foo, $bar)
{
var_dump($foo, $bar);
}
}
$something = new Something;
call_user_func([$something, 'doWork'], 'the-foo', 'the-bar');
call_user_func_array(
[$something, 'doWork'], ['the-foo', 'the-bar']
);
In this example, we call the method doWork()
twice,
passing the strings the-foo
and the-bar
as
arguments. When calling functions, the name of the function to call
must be passed as the first parameter to
call_user_func()
, or
call_user_func_array()
, respectively. To call an
instance method, we need to pass an array containing an object
reference as the first element, and the method name as the second
argument.
While call_user_func_array()
expects an array of
arguments that are passed to the called function,
call_user_func()
requires individual arguments. Other
than that, both functions are exactly the same.
The result, unsurprisingly, is:
string(7) "the-foo"
string(7) "the-bar"
string(7) "the-foo"
string(7) "the-bar"
Before PHP 7, there also were the two methods
call_user_method
and
call_user_method_array()
, which basically just differed
in the way the object reference and method name to call are
specified. Even though those methods had already been deprecated in
PHP 4.1, they were not yet removed in PHP 5. But now, finally, they
are gone.
In case your code still uses them, the fix is simple. Just
replace call_user_method()
by
call_user_func()
and
call_user_method_array()
by
call_user_func_array()
and replace the first two
parameters by an array like in the example above.
Removed
set_socket_blocking()
The function set_socket_blocking()
, an alias of
stream_set_blocking()
, has been removed in PHP 7.
Luckily, working around this “problem” is as easy as globally
searching and replacing a string in your codebase.
Removed XSL
ini
Option xsl.security_prefs
When using XSL stylesheets, sometimes additional files need to be
read or even written to by the transformation process. To
programmatically restrict access to this potentially dangerous
feature set, the xsl
extension provides the
setSecurityPrefs()
method.
Previous versions of PHP allowed predefining a security setting
using the accompanying ini setting xsl.security_prefs
.
This configuration setting has been removed in PHP 7, all security
settings now need to be set explicitly at runtime using the
aforementioned function.
wddx
is
Gone
Believe it or not, there was a time before everybody used JSON and REST APIs. There is the Simple Object Access Protocol (SOAP) for example, which turned out to be not so simple after all, or XML-RPC, which essentially allows you to represent a remote function (technically: procedure) call and its response as XML documents.
The WDDX (Web Distributed Data eXchange) format, which dates back
to 1998, is another XML-based serialization format. PHP used to
support WDDX through the wddx
extension, which could be
compiled into PHP by using the --enable-wddx
compile-time option, which in turn required the expat
XML parser library to be available.
As of PHP 7.4, wddx
is not bundled with PHP anymore,
but has allegedly been moved to PECL, the home for C-based PHP
extensions. At the time of writing of this chapter, however, no
source code seems to be available (yet) at
https://pecl.php.net/package/wddx.
We have never seen anybody actively use WDDX in recent years, and given that XML-RPC and SOAP are still valid options, nobody should really miss WDDX in PHP. If you desperately need it, there is always the option to create and parse the WDDX XML by hand.