There are many ways to attack an application, many are working by injecting some malicious data hoping to trigger a deserved action in the end.
Most of these are possible when input data is not properly sanitized. This can have connotation in the programming language used, a famous PHP example is with register globals for example or the famous magic quotes.
These are fairly old stories and nowadays using a recent PHP version does not come with these rough edges so you’re normally more safe. However, this also means you need to look more specifically for areas of your code where injection is still possible.
Due to some recent work I needed to review code for autoloader invalid classname injection (see class autoloading). I did not found much concrete information online which functions/code are able to trigger this. Somewhat a pity, so first of all here is a list of functions which are (many by default) triggering autoloading:
call_user_func() call_user_func_array() class_exists() class_implements() class_parents() class_uses() get_class_methods() get_class_vars() get_parent_class() interface_exists() is_a() is_callable() is_subclass_of() method_exists() property_exists() spl_autoload_call() trait_exists()
All but one of these functions filter the classname parameter (the first character if "\" – and only the first – is removed). Only the function (spl_autoload_call()) passes the parameter in raw – that is the first character always unchanged.
This list is likely not complete (e.g. getting objects from the database with PDO/Mysqli and classnames), feel free to add your findings as well via comments or your own posting. Some of these functions – depending on PHP version – can not be used for injection by default.
Further potentially exploitable code is instantiating the ReflectionClass or making use of variable classname instantiation via new like new $classname;. The latter would result in a fatal error, however if an autoloader could be exploited via injection, the exploit would work before the fatal error is triggered.
Interestingly, the unserialize() function with PHP’s default unserializer can not be used to inject invalid classnames, unserialisation fails in that case highlighting the first mismatching character offset in a class / namespace name per a warning. PHP just refuses to parse those while unserializing. Howver, take care here for anything that has it’s own serialization and __sleep / __wakeup can be injection points on their own.
Less surprisingly, the get_object_vars() function does not work for injection because it needs an object, not a class. Sometimes you don’t know with PHP, so I included it in my review.
Defense In Depth at Classloader Level
So what is a valid classname anyway? Back in 2010 when I wrote PHP Syntax Regulary Expressed I noted that the syntax of namespaces remains undefined in PHP documentation. Reading PHPs source at least let one assume that it’s the same as for the classname plus the namespace separator (I write assume because it’s all at the tokenizer/parser level depening on what T_STRING stands for).
So expressing a valid fully qualified classname (with optionally prefixed by a namespace separator) as PCRE regular expression could look like (two variants):
Inside an auto-loader, this can be quickly checked with preg_match():
$pattern = '(^\\\\?(?>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\\\\?)+$)'; $valid = (bool) preg_match($pattern, $classname);
One might argue that because a prefix of the namespace separator \ is invalid and most of PHP function calls filter it away anyway, that it is more strict to exclude it from the regular expression:
$pattern = '(^(?>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\\\\?)+$)'; $valid = (bool) preg_match($pattern, $classname);
Next to a regular expression I’m not aware of any way to validate a classname in PHP. I just filed a feature request #64185 because one function that actually could be able to do that is not yet able so: is_callable() with the $syntax_only parameter set to true.
Take care and let me know if you find some more functions worth to look when reviewing the code.
Image Credits: Citations from Along the River During Qingming Festival, 18th century remake of a 12th century original by Chinese artist Zhang Zeduan; via Trialsanderrors
Pingback: Remote code execution via PHP [Unserialize] - NotSoSecure