Sunday 26 February 2017

oop - PHP Oddities: Bypass Class Visibility?



Today I was working on an old diagnostic library I had written back in the halcyon 5.1 days. That library provided highly detailed dumps of any variable you could give it, using color coding to indicate types, and using Reflection to produce a lot of insight into objects (including, depending on what flags you pass, outputting relevant PHPDoc and even source code for objects - especially useful in backtraces).



At the time I been able to bypass member visibility to output the value of protected and private members of a class. That proved to be quite useful from a debugging perspective, particularly with respect to detailed error logs we produce.



To bypass visibility in 5.1, I used the Reflection API, which let you see the value of protected and private members with ReflectionMethod->getValue($object). This of course is a bit of a security bypass, but it's not too bad since if you're going to view and modify values this way, you're pretty clearly breaking the object's intended API.



PHP 5.2 stopped Reflection from being able to access protected/private members and methods. Of course this was intentional, and considered that ability to be a security concern. I simply added a try/catch around this piece of my library, and output it if the language allowed it, and didn't if it doesn't. Java Reflection AFAICR always allowed you to bypass visibility (I believe they are of the opinion that if you want it badly enough, you'll get it one way or another, visibility is just an advertised API for an object, violate this at your own risk).




As a thought exercise, and perhaps to bring my dump library up to date, I'm curious if anyone can think of clever ways to bypass visibility in modern versions of PHP (5.2+, but of particular interest to me is PHP 5.3).



There are three avenues which seem particularly hopeful. First: mangling serialize/unserialize:



Class Foo {
protected $bar;
private $baz;
}
class VisibleFoo {

public $bar;
public $baz;
}

$f = new Foo();
$data = serialize($f);
$visibleData = str_replace($data, 'O:3:"Foo":', 'O:10:"VisibleFoo":');
$muahaha = unserialize($visibleData);



Of course it's more involved than this, because protected members are flagged as such: null*nullProperty, and private members are bound against their original class name: nullOriginalClassnullProperty (see PHP Serialization), but theoretically you could clean those all up and trick serialize / unserialize into exposing these values for you.



This has a few drawbacks: first, it is fragile with respect to language versions. PHP doesn't (AFAIK) make any guarantee that the data produced by serialize() will remain consistent from version to version (indeed, the way protected and private members are represented has changed since I've used PHP). Second, and more importantly, some objects declare a __sleep() method, which might have unintended side effects of 1) not giving you access to all the private members, and 2) maybe this will tear down database connections, close file streams, or other side effects of the object thinking it's going to sleep when it is in fact not.



A second option is to try to parse out print_r() or other built-in debugging statements to scrape values. This has the consequence of being incredibly difficult to do well beyond simple values (my old library would let you drill down into members which are themselves objects, and so forth). Interestingly it's a variant of this approach that I used to detect infinite recursion ($a->b = &$a) using var_dump().



A third option is to subclass the target and increase its visibility that way. This will get you access to protected members, but not to private members.



I seem to recall a few years ago reading a post by someone who had figured out a way to bypass with lambda functions or something to that effect. I can't find it any longer, and having tried a variety of variations on this idea, I've come up empty.




TLDR version: an anyone think of some magic hoops to jump through to dredge out protected and private members of a PHP object instance?


Answer



Fourth option:



$reflectionProperty->setAccessible(true);


can be used to make any property accessible using the getValue() method, even if it's protected or private. Test the visibility, then use setAccessible(true), getValue() and setAccessible(false) to reset.



I think this is a lot cleaner that a serialize()/unserialise() to a new class that has all properties public.... and doesn't require you to have duplicate versions of all your classes



No comments:

Post a Comment

c++ - Does curly brackets matter for empty constructor?

Those brackets declare an empty, inline constructor. In that case, with them, the constructor does exist, it merely does nothing more than t...