PHP面向对象
类的基础
类的定义以关键字class开始,后面跟着这个变量的名称。类的名称通常每个单词的第一个字母 大写,以中括号开始和结束。
类可以继承,在PHP之中可以用extends关键字来表示类的继承,后面跟着父类的类名。
父类定义的类成员可以不用在子类中重复定义
- 同一个父类的子类拥有相同的父类定义的类成员。
- 子类可以修改和调整父类定义的类成员(重写)
类的实例化为对象时使用关键字new,new过后紧跟类的名称和一对括号()
- 对象的属性成员可以通过->符号来访问
- 对象中的成员方法也可以通过->来访问
类的访问控制
public公有的类成员,可以在任何地方被访问(定义该成员的类(自身)、该类的子 类、其他类)
protected受保护的类成员,可以被其自身以及其子类访问
private私有类的成员,只能被自身访问
1 | class Animal { |
魔术方法
面向对象的魔术方法:
1 | __construct(),类的构造函数 |
__construct()
php中构造方法是对象创建完成后第一个被对象自动调用的方法。在每个类中都有一个构造方法
作用:通常构造方法被用来执行一些有用的初始化任务,如对成员属性在创建对象时赋予初始值。
注意点
在同一个类中只能声明一个构造方法,原因是,PHP不支持构造函数重载。
构造方法名称是以两个下画线开始的__construct()
__destruct()
析构方法允许在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件、释放结果集等。
注意点
析构函数不能带有任何参数。
__call()
该方法有两个参数,第一个参数 $function_name
会自动接收不存在的方法名,第二个 $arguments
则以数组的方式接收不存在方法的多个参数。
作用
为了避免当调用的方法不存在时产生错误,而意外的导致程序中止,可以使用 __call() 方法来避免。
该方法在调用的方法不存在时会自动调用,程序仍会继续执行下去。
__callStatic()
此方法与上面所说的__call()
功能除了 __callStatic()
是未静态方法准备的之外,其它都是一样的。
__get()
类的成员属性被设定为 private
后,如果我们试图在外面调用它则会出现“不能访问某个私有属性”的错误。那么为了解决这个问题,我们可以使用魔术方法 __get()
。
作用
在程序运行过程中,通过它可以在对象的外部获取私有成员属性的值。
__set()
用来设置私有属性, 给一个未定义的属性赋值时,此方法会被触发,传递的参数是被设置的属性名和值。
__isset()
作用
当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
对象外部使用“unset()”函数删除对象内部的私有成员属性时,对象会自动调用__unset()
函数来帮我们删除对象内部的私有成员属性
__sleep()
serialize()
函数会检查类中是否存在一个魔术方法 __sleep()
。如果存在,则该方法会优先被调用,然后才执行序列化操作
注意
__sleep() 不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误
__wakeup()
unserialize()
会检查是否存在一个 __wakeup()
方法。如果存在,则会先调用 __wakeup
方法,预先准备对象需要的资源。
__toString()
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj;
应该显示些什么。
注意
此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR
级别的致命错误。
__invoke()
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
重要方法demo
1 | class Animal { |
反序列化
关键函数
反序列化主要是这两个函数
1 | serialize() //将一个对象转换成一个字符串 |
通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击。
反序列化可以控制类属性,无论是private还是public
序列化实例
1 | class Animal { |
这里说明一下序列化字符串的含义:O:6:"Animal"
指Object(对象) 6个字符:Animal:3
对象属性个数为3
{}中为属性字符数:属性值
但是可以看到后面的两个明显是不对等的,那是因为他们分别是私有和保护类型,这两者序列化会加入不可显字符
1 | private序列化之后应该是 N;s:14:"%00Animal%00weight |
__weakup()
当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
例如上面那个序列化
1 | O:6:"Animal":3:{s:4:"name";s:6:"monkey";s:6:"*age";N;s:14:"Animalweight";s:4:"53kg";} |
这样就可以绕过__weakup()方法了。
pop
面向属性编程
面向属性编程(Property-Oriented Programing) 用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链来执行一些操作。
基本概念
POP 链的构造则是寻找程序当前环境中已经定义了或者能够动态加载的对象中的属性(函数方法),将一些可能的调用组合在一起形成一个完整的、具有目的性的操作。我们可以通过反序列化操控参数,寻找危险函数,从而达到攻击的目的。
利用
一般的序列化攻击都在PHP魔术方法中出现可利用的漏洞,因为自动调用触发漏洞,但如果关键代码没在魔术方法中,而是在一个类的普通方法中。这时候就可以通过构造POP链寻找相同的函数名将类的属性和敏感函数的属性联系起来。
实战
MRCTF 2020 EZPOP
1 | 欢迎使用index.php |
exp
1 | class Modifier { |
本题重点
__wakeup
,之前做的题目一般都是需要绕过__wakeup
,因为在反序列化一开始就会调用__wakeup魔术方法,所以一开始就把这个魔术方法作为需要bypass的东西,然后这却是我们最需要利用的最开始的一步。
原因在于preg_match会将$this->source作为字符串来使用,去执行匹配,也只有这样我们才可以触发__tostring
,一步步走向包含方法。
1 | preg_match()->__tostring()->__get()->__invoke()->append() |