• php反序列化基础


    目录

    php反序列化基础

    php类与对象

    魔术方法

    php序列化/反序列化

    php反序列化漏洞(对象注入)

    php反序列化基础

    php类与对象

    类是定义一系列属性和操作的模板,而对象,就是把属性进行实例化,完事交给类里面的方法,进行处理。

    name." is smile...\n";
       }
    }
    ​
    $psycho = new people(); //根据people类实例化对象
    $psycho->smile();
    ?>

    img

    上述代码定义了一个people类,并在在类中定义了一个public类型的变量$name和类方法smile。然后实例化一个对象$psycho,去调用people类里面的smile方法,打印出结果。

    这就是php类与对象最基础的使用。

    魔术方法

    为什么被称为魔法方法呢?因为是在触发了某个事件之前或之后,魔法函数会自动调用执行,而其他的普通函数必须手动调用才可以执行。PHP 将所有以 (两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 为前缀。下表为php常见的魔术方法:

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

    额外提一下__tostring的具体触发场景:

    (1) echo($obj) / print($obj) 打印时会触发

    (2) 反序列化对象与字符串连接时

    (3) 反序列化对象参与格式化字符串时

    (4) 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)

    (5) 反序列化对象参与格式化SQL语句,绑定参数时

    (6) 反序列化对象在经过php字符串函数,如 strlen()、addslashes()时

    (7) 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用

    (8) 反序列化的对象作为 class_exists() 的参数的时候

    举个例子:

    ";
                echo $this->name . " is sleeping...\n";
            }
            public function __wakeup(){
                echo "
    ";           echo "调用了__wakeup()方法\n";       }       public function __construct(){           echo "
    ";           echo "调用了__construct()方法\n";       }       public function __destruct(){           echo "
    ";           echo "调用了__destruct()方法\n";       }       public function __toString(){           echo "
    ";           echo "调用了__toString()方法\n";       }       public function __set($key, $value){           echo "
    ";           echo "调用了__set()方法\n";       }       public function __get($key) {           echo "
    ";           echo "调用了__get()方法\n";       }   }       $ji = new animal();   $ji->name = 1;   echo $ji->name;   $ji->sleep();   $ser_ji = serialize($ji);   //print_r($ser_ji);   print_r(unserialize($ser_ji)) ?>

    img

    php序列化/反序列化

    在开发的过程中常常遇到需要把对象或者数组进行序列号存储,反序列化输出的情况。特别是当需要把数组存储到mysql数据库中时,我们时常需要将数组进行序列号操作。

    php序列化(serialize):是将变量转换为可保存或传输的字符串的过程

    php反序列化(unserialize):就是在适当的时候把这个字符串再转化成原来的变量使用

    这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。

    常见的php系列化和反系列化方式主要有:serialize,unserialize;json_encode,json_decode。

    序列化

    举个序列化小栗子:

    $team_members = '奥力给';
        }
    }
    $object = new object();
    echo serialize($object);
    ?>

    img

    以上是序列化之后的结果,o代表是一个对象,6是对象object的长度,3的意思是有三个类属性,后面花括号里的是类属性的内容,s表示的是类属性team的类型,4表示类属性team的长度,后面的以此类推。值得一提的是,类方法并不会参与到实例化里面。

    需要注意的是变量受到不同修饰符(public,private,protected)修饰进行序列化时,序列化后变量的长度和名称会发生变化

    • 使用public修饰进行序列化后,变量$team的长度为4,正常输出。

    • 使用private修饰进行序列化后,会在变量$team_name前面加上类的名称,在这里是object,并且长度会比正常大小多2个字节,也就是9+6+2=17。

    • 使用protected修饰进行序列化后,会在变量$team_group前面加上*,并且长度会比正常大小多3个字节,也就是10+3=13。

    通过对比发现,在受保护的成员前都多了两个字节,受保护的成员在序列化时规则:

    \1. 受Private修饰的私有成员,序列化时: \x00 + [私有成员所在类名] + \x00 [变量名]

    \2. 受Protected修饰的成员,序列化时:\x00 + * + \x00 + [变量名]

    其中,"\x00"代表ASCII为0的值,即空字节," * " 必不可少。

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

    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

    反序列化

    反序列化的话,就依次根据规则进行反向复原。

    这边定义一个字符串,然后使用反序列化函数unserialize进行反序列化处理,最后使用var_dump进行输出:

    img

    php反序列化漏洞(对象注入)

    在反序列化过程中,其功能就类似于创建了一个新的对象(复原一个对象可能更恰当),并赋予其相应的属性值。如果让攻击者操纵任意反序列数据, 那么攻击者就可以实现任意类对象的创建,如果一些类存在一些自动触发的方法(魔术方法),那么就有可能以此为跳板进而攻击系统应用。

    挖掘反序列化漏洞的条件是:

    1. 代码中有可利用的类,并且类中有wakeup(),sleep(),__destruct()这类特殊条件下可以自己调用的魔术方法。

    1. unserialize()函数的参数可控。

    php对象注入示例一:

    test);
        }
    }
    $test = $_POST['test'];
    $len = strlen($test)+1;
    $p = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象
    $test_unser = unserialize($p); // 反序列化同时触发_destruct函数
    ?>

    如上代码,最终的目的是通过调用destruct()这个析构函数,将恶意的payload注入,导致代码执行。根据上面的魔术方法的介绍,当程序跑到unserialize()反序列化的时候,会触发destruct()方法,同时也可以触发__wakeup()方法。但是如果想注入恶意payload,还需要对$test的值进行覆盖,题目中已经给出了序列化链,很明显是对类A的$test变量进行覆盖。

    img

    可以看到当我们传入的参数为 phpinfo()

    img

    这样的话在调用__destruct方法执行eval之前就把变量$test的值替换成恶意payload。

    php对象注入示例二:

    这是来自bugku的一道题。题目地址

    index.php

    "; 
        if(preg_match("/flag/",$file))
        { 
           echo "不能现在就给你flag哦"; 
           exit(); 
        }
        else
        { 
           include($file); 
           $password = unserialize($password); 
           echo $password; 
        } 
    }
    else
    { 
           echo "you are not the number of bugku ! "; 
    } 
    ?>

    hint.php

    file)){  
                echo file_get_contents($this->file); 
                echo "
    ";           return ("good");       }     }   }   ?>

    hint.php文件中使用了魔术方法tostring()方法,当一个对象被当作一个字符串被调用时即可触发,方法的主要作用是读取并打印传进来的$file,估计是通过反序列化漏洞来读取flag.php的内容。追踪以下调用链,在index.php文件中发现使用echo将反序列化的对象当作字符串打印,此处就会触发tostring()方法,并且unserialize()内的变量可控,满足反序列化漏洞条件。直接构造payload:(关于使用php://filter进行任意文件的读取,参照p牛:《谈一谈php://filter的妙用》)

    img

    php对象注入示例三:

    test);
            fclose($fp);
        }
    }
    $a = $_GET['id'];
    print_r($a);
    echo "
    "; $a_unser = unserialize($a); require "flag.php"; ?>            

    如上代码主要通过调用魔术方法wakeup将$test的值写入flag.php文件中,当调用unserialize()反序列化操作时会触发wakeup魔术方法,接下来就需要构造传进去的payload,先生成payload:

    ";
    }
    $test = new test();
    echo serialize($test);
    ?>

    img

    传入payload:

    img

    在执行unserialize()方法时会触发__wakeup()方法执行,将传入的字符串反序列化后,会替换掉test类里面$test变量的值,将php探针写入flag.php文件中,并通过下面的require引用,导致命令执行。

  • 相关阅读:
    性能测试 —— Jmeter 命令行详细
    Python 人工智能编程指南:基础、库和工具大全解析
    House of apple 一种新的glibc中IO攻击方法
    javascript中使用new来调用构造函数,生成新对象时发生了什么
    计算机毕业设计Java校园绿化管理系统(源码+系统+mysql数据库+Lw文档)
    CarEye 智能云平台升级
    starrocks
    Transformer之Positional encoding
    从物联网到万物互联:AI与6G融合,实现互联智能
    List & Label Professional Reporting Edition 28
  • 原文地址:https://blog.csdn.net/m0_74608722/article/details/142174553