PHP 8: Override internal functions with disable_functions
INI directive
In PHP, it is possible to disable certain functions and classes with php.ini
directives.
disable_functions
: A comma-separated list of function names that PHP does not allow to be used.disable_classes
: Similar to functions, a list of classes that is now allowed to use.
These directives were used commonly to disable potentially unsafe functions from being used even if a malicious actor gets code-execution access in a server.
In PHP 8, disabled functions are not included in the functions table, which means function_exists()
calls will return false
for disabled functions, and most importantly, it will be possible to redefine the disabled functions.
Example
Here is an example of redefining sleep()
function with a user-land implementation.
1. Create the new function definitions
Once we disable the functions, they will not be available to anywhere in the code under test. If you override certain features that your testing framework itself uses (such as microtime()
, which is often used to calculate the execution time of tests), you will need to be careful to not break that functionality.
// test.php
function sleep() {
return;
}
\sleep(1662815);
In a test.php
file, we redefine the sleep()
function to immediately return, without actually holding the execution.
Notice how we used a fully-qualified function name and it even works there.
2. Execute the script with disable_functions
INI directive
php -d disable_functions=sleep test.php
When you run the code, PHP will first disable the internal sleep
function, and due to PHP 8 changes, our custom test.php
file is able to define a new sleep
function.
Use cases
¯\(ツ)/¯
It appears that disable_functions
+ redefine is the only way to effectively override a PHP internal function with user-land code.
Log/Fail unsafe internal functions
You can redefine functions such as system
and create a log entry to detect whenever this function is used.
Note that language constructs, such as echo
, include
cannot be redefined. This unfortunately includes eval
as well.
// troll.php
function system() {
echo 'Ha!';
}
# php.ini file
[PHP]
disable_functions=system
auto_prepend_file=troll.php
Seamlessly polyfill legacy code with modern libraries
While it's not the most convenient approach, you can overwrite legacy functionality such as the whole mcrypt_*
suite, or even mysql_*
functions with modern libraries such as Sodium and PDO.
A user-land implementation that wraps an alternative library/extension with legacy function names can be technically done. Note that neither mcrypt
, nor mysql
extensions are in MySQL 8, so you are not actually redefining anything in this particular example.
function mcrypt_encrypt(string $cipher, string $key, string $data): string {
return sodium_crypto_secretbox(...);
}
Make bad decisions
// hackers-go-brrrr.php
function password_hash(string $password): string {
return md5($password);
}
function password_verify(string $password , string $hash): bool {
return md5($password) === $hash;
}
var_dump(password_hash('hunter2'));
var_dump(password_verify('hunter2', '2ab96390c7dbe3439de74d0c9b0b1767'));
php -d disable_functions=password_hash,password_verify hackers-go-brrrr.php
More Sane approaches
Symfony PHPUnit Bridge Mocks
Symfony PHPUnit Bridge has ClockMock
and DnsMock
that dynamically declares time and DNS related features right before PHPUnit tests are run.
It is not possible to use fully-qualified function names with PHPUnit Bridge.
Autoload modified code
Another approach albeit being a bit more complicated, would be override the class autoloader mechanism, and load (automatically) modified code converts fully-qualified function names to namespaced function names, and define those functions in the same namespace.
PHP autoload override library does exactly that.
Advanced PHP Debugger
APD extension has features to override an internal function.
Updated
This post was updated updated on 2020 July 08. A prior version pointed that it was not possible to override certain functions that are inlined in PHP. This has then been fixed.