PHP奇怪:绕过类可见性?

时间:2011-02-06 18:36:18

标签: php oop

今天我正在研究一个我曾在宁静的5.1天写回来的旧诊断库。该库提供了您可以提供的任何变量的高度详细的转储,使用颜色编码来指示类型,并使用Reflection生成对对象的大量洞察(包括,取决于您传递的标志,输出相关的PHPDoc甚至源代码对象 - 在回溯中特别有用)。

当时我能够绕过成员可见性来输出类的受保护成员和私有成员的值。从调试的角度来看,这非常有用,特别是对于我们生成的详细错误日志。

为了绕过5.1中的可见性,我使用了Reflection API,它可以让您看到ReflectionMethod->getValue($object)的受保护成员和私有成员的价值。这当然是一个安全绕过,但它不是太糟糕,因为如果你要以这种方式查看和修改值,你很明显地打破了对象的预期API。

PHP 5.2停止反射能够访问受保护/私有成员和方法。当然这是故意的,并认为这是一个安全问题的能力。我只是在我的库中添加了一个try / catch,如果语言允许则输出它,如果没有则不输出。 Java反射AFAICR总是允许你绕过可见性(我相信他们认为,如果你想要它足够严重,你会以某种方式得到它,可见性只是一个广告的对象的API,违反这个你自己风险)。

作为一个思考练习,也许是为了让我的转储库更新,我很好奇是否有人能想到巧妙的方法来绕过PHP的现代版本中的可见性(5.2+,但我特别感兴趣的是PHP 5.3)。

有三种途径似乎特别有希望。第一:修改序列化/反序列化:

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);

当然它比这更复杂,因为受保护的成员被标记为:null * null属性,私有成员与其原始类名绑定:null OriginalClass { {1}}属性(请参阅PHP Serialization),但从理论上讲,您可以清除所有这些并使用序列化/反序列化技巧为您公开这些值。

这有一些缺点:首先,它在语言版本方面很脆弱。 PHP没有(AFAIK)保证serialize()生成的数据在版本之间保持一致(实际上,自从我使用PHP以来,受保护和私有成员的表示方式已经改变)。其次,更重要的是,一些对象声明了一个__sleep()方法,它可能会产生意想不到的副作用:1)不允许您访问所有私有成员,2)这可能会破坏数据库连接,关闭文件流,或者对象的其他副作用,认为它实际上不会睡觉。

第二个选项是尝试解析print_r()或其他内置调试语句来刮取值。这样做的结果是难以完成超出简单值的难度(我的旧库可以让你深入到成为自身对象的成员,等等)。有趣的是,这是我使用var_dump()检测无限递归(null)的方法的变体。

第三种选择是对目标进行子类化并以此方式增加其可见性。这将使您可以访问受保护的成员,但不能访问私有成员。

几年前,我似乎想起了一篇读过一篇文章的人,这篇文章已经找到了绕过lambda函数的方法或类似的东西。我再也找不到它,并且尝试了各种各样的变化,我已经空了。

TLDR版本:有人想到一些神奇的箍来跳过来挖掘PHP对象实例的受保护和私有成员吗?

2 个答案:

答案 0 :(得分:4)

第四个选项:

$reflectionProperty->setAccessible(true);

可用于使用getValue()方法访问任何属性,即使它是受保护的或私有的。测试可见性,然后使用setAccessible(true),getValue()和setAccessible(false)重置。

我认为对于一个公开所有属性的新类,serialize()/ unserialise()更清晰,并且不要求你拥有所有类的重复版本

答案 1 :(得分:1)

正如对于遇到这个主题的任何人都要注意寻找调试脚本的方法或任何打开受保护属性的方法来解决内部问题,可能最容易和最深远的方法是绕过正确的包含/ require系统用于调试脚本并使用eval()加载您正在调试的代码,如下面的快速示例所示:

$code = file_get_contents('ClassFile.php');
$code = trim($code, '<?php>');
$code = str_replace('protected', 'public', $code);
eval($code);

当然,您可能也希望也将公共关键字替换为公共关键字,并且在更换代码时要小心谨慎,以避免在任何实际上并不替换字符串“受保护”的情况下使用受保护的关键字等等...

我确信我不需要说任何形式的这种做法,或者除了开发以外的任何其他原因,远非最佳做法。但是,除非删除eval()函数,否则这种绕过受保护/私有成员的方法不太可能在短时间内被PHP更新破坏。