PHP Magic Method Mapping

PHP object injection is one of the more esoteric web application vulnerabilities that we look for in penetration tests at Dionach. A detailed explanation is beyond the scope of this post, but there are a number of good resources available that discuss object injection (such as these excellent slides from BlackHat 2010).

Background

For object injection to occur, the web application needs to be passing user-supplied input to the unserialize() function. Generally this is done by developers who are intending to unserialize an array, usually passed in either a POST request or a cookie. Note that since serialized objects often include unprintable characters, these will usually be base64 encoded. Because our provided input is passed directly to the unserialize() function, we can create arbitrary objects in the application.

PHP classes can have a number of magic methods which are automatically called in specific circumstances. The most well-known of these is the constructor __construct(), which is called when a new instance of the class is created. There is also a matching destructor __destruct() that is called when the object is destroyed (which will happen automatically when the script exits) and a wake up method __wakeup() is called when an object is unserialized. As we can unserialize an arbitrary object we can potentially use this (and other magic methods) to gain a limited form of code execution.

Identifying Exploitable Classes

Once we’ve identified our injectable parameter that allows us to unserialize() arbitrary objects, we need to find a class with appropriate magic methods that we can use to exploit this. The easiest methods to use are __wakeup() and __destruct(), because they’re both called automatically on any unserialized object (apart from a few cases where __destruct() isn’t called, such as PHP encountering a fatal error). However, there are a number of other methods such as __toString(), __get(), __set() and __call() that might be executed, depending on what the application code does after calling unserialize() on our input.

At this point we need to begin inspecting the application source code (it’s almost impossible to exploit object injection blind), and looking for classes that might be exploitable. Especially in complex applications, this can be a difficult and time consuming process if done manually. One technique that can be used is searching across the codebase for magic methods ($ grep -ir "__destruct" *), and then if a usable method is found, working out if that class is accessible from the vulnerable script. Sometimes the application will be configured to autoload classes that aren’t available (very useful for us), but often it won’t.

Another method is to examine the script, and then search for vulnerable classes in other PHP files that are included or required earlier in the execution. However this can quickly become unmanageable when these scripts include more scripts, that may have further includes. The PHP get_included_files() function can be used here to get a list of all files that have been included in a script, so adding this function just before the serialize() call in a local copy of the web application will provide a quick list of files that need to be inspected.

Magic Method Mapping

While trying to find a way to exploit an object injection vulnerability during a penetration test in a fairly well-known piece of open source software, I decided to write a helper script that would automate part of this process. Unfortunately the software turned out to be unexploitable in a default install, however the code for this helper script can be found on the Dionach GitHub.

The PHP get_declared_classes() function returns a list of all classes that are available. This includes all of the core classes, and classes created by various PHP extensions. Because these are loaded from compiled objects, they’re much harder to examine, and are unlikely to be exploitable. The classes are then examined using PHP’s (partially documented) reflection API, and non user-defined classes are ignored. The script then tests to see if each class contains any of the magic methods (or inherits them from parent classes), and if they do then the filename and line number of magic method is output. Finally, the script scans each magic method for a number of interesting functions such as eval(), system() and unlink(), and flags if any are found.

Usage

The magic method mapping script needs to be included in the vulnerable page, immediately before the unserialize() function is called. It will output a list of magic methods that are accessible, along with their paths and parent classes, and then exit. Below is a quick PHP file that declares two classes (one inheriting from the other), and then includes the script.

<?php
class foo
{
    function
__construct() {}
    function
__destruct() { eval (""); }
}
class
bar extends foo
{
    function
__toString() { unlink(""); }
    function
__get($self) {}
}
include
"magicmapping.php";
?>

The following output is produced:

foo::__destruct() {calls eval} - /var/www/class.php:5
bar::__destruct() [extends foo] {calls eval} - /var/www/class.php:5
bar::__toString() {calls unlink} - /var/www/class.php:9
bar::__get() - /var/www/class.php:10

The class and method name are shown on the far left. If the method is inherited from a parent class, it is shown in square brackets: “[extends foo]”. If any interesting function calls are identified in the method, these are shown in curly braces: “{calls eval}”. Finally we have the path and line number of the method. Note that in cases where a method is inherited from a parent, this will be the path to the declaration of the method in the parent class.

If you’re trying to create a POP chain (where one magic method calls other methods from other classes) then you can add more methods to scan for to the array at the top of the script.

Comments, improvements and pull requests are all welcome.


Find out how we can help with your cyber challenge

Please enter your contact details using the form below for a free, no obligation, quote and we will get back to you as soon as possible. Alternatively, you can email us directly at [email protected]