Skip to content

退信泄露与订单越权防御 — 最小化 NDR 回执与所有权强鉴权 ​

配对攻击手册:payment-email-bounce-idor.md。当系统将卡密/兑换码直接明文写入外发邮件,同时订单接口又仅凭 order_id 即可查询时,退信(NDR)日志与自增订单号会形成“零认证批量盗取卡密”的联合链路。

威胁模型与控制映射 ​

攻击向量典型缺陷防御控制措施
退信(NDR)夹带明文卡密发信系统在邮件正文直接包含卡密,退信服务器将原始邮件原文返回或记录到公共日志邮件正文只发送“通知与登录查看链接”,绝不附带卡密、激活码或免密鉴权 Token
退信 webhook / 退信查询接口越权退信记录表暴露给未登录接口或任意已登录用户(含 recipient、raw_body、order_id)退信处理端点强制校验内部服务签名(如 SES SNS / SendGrid ECDSA 签名),管理视图强制 RBAC + 数据范围隔离
自增 order_id + 无鉴权详情接口/api/orders/{id} 仅检查订单是否存在,未核对 order.user_id == current_user.id在每条订单查询与卡密领取路由强制执行基于会话主体 user_id 与 tenant_id 的所有权校验
游客下单凭邮箱+订单号直接查看匿名购卡仅凭可预测订单号或可枚举邮箱即可拉取卡密匿名购卡必须绑定高熵随机访问凭证(≥ 128-bit 一次性/限时签名 Token)+ 邮箱 OTP 二次核验
发信前邮箱格式与退信风暴滥用允许向任意未验证邮箱发送含敏感资产的邮件并无退信熔断注册/购卡前先通过验证码链接验证邮箱所有权;对连续硬退信目标自动加入抑制列表(Suppression List)

核心加固实现 ​

1. 邮件载荷最小化与退信安全处理器 ​

python
# safe_mail_bounce_handler.py — 隔离敏感资产的邮件通知与退信存储
import hashlib
import secrets
from dataclasses import dataclass

SENSITIVE_PATTERNS = ("CARD-", "KEY-", "PIN:", "secret=", "token=")

def build_safe_order_email(order_public_id: str, portal_base_url: str) -> dict:
    """邮件中仅包含非敏感提示与需登录访问的门户链接。"""
    body = (
        f"您的订单 {order_public_id} 已完成支付。\n"
        f"请登录账户后前往订单详情页查看数字商品:{portal_base_url}/orders/{order_public_id}"
    )
    for marker in SENSITIVE_PATTERNS:
        if marker in body:
            raise ValueError("sensitive asset must not be placed in email body")
    return {"subject": f"订单确认 {order_public_id}", "body": body}

@dataclass
class SanitizedBounceRecord:
    bounce_id: str
    recipient_hash: str
    smtp_code: str
    reason_category: str

def record_bounce_safely(recipient: str, smtp_code: str, reason: str, salt: bytes) -> SanitizedBounceRecord:
    # 仅留存哈希后的收件人索引与 SMTP 错误分类,不留存原始退信正文(防止包含 PII 或订单快照)
    rcpt_hash = hashlib.sha256(salt + recipient.strip().lower().encode("utf-8")).hexdigest()[:24]
    category = "hard_bounce" if smtp_code.startswith("5") else "soft_bounce"
    return SanitizedBounceRecord(
        bounce_id=f"bnc_{secrets.token_hex(12)}",
        recipient_hash=rcpt_hash,
        smtp_code=smtp_code[:8],
        reason_category=category,
    )

2. 订单详情与卡密领取的所有权鉴权守卫 ​

python
# order_ownership_guard.py — 不可预测订单号 + 会话所有权强制校验
import secrets

def generate_opaque_order_id() -> str:
    return f"ord_{secrets.token_urlsafe(16)}"

def fetch_order_card_keys(orders_db: dict, current_user: dict, order_id: str) -> dict:
    order = orders_db.get(order_id)
    if not order:
        raise LookupError("order not found")
    # 强制核对租户与所有权,管理员亦需独立权限域与访问记录
    if order["tenant_id"] != current_user["tenant_id"]:
        raise PermissionError("cross-tenant access denied")
    if order["owner_id"] != current_user["user_id"]:
        raise PermissionError("order ownership mismatch")
    if order["status"] != "FULFILLED":
        raise PermissionError("order not fulfilled")
    return {"order_id": order_id, "card_keys": order["card_keys"]}

if __name__ == "__main__":
    mail = build_safe_order_email("ord_abc123", "https://shop.example.com")
    assert "CARD-" not in mail["body"]
    bnc = record_bounce_safely("nobody@invalid.example", "550", "550 5.1.1 User unknown", b"bounce-salt")
    assert bnc.reason_category == "hard_bounce" and "nobody@" not in bnc.recipient_hash

    db = {
        "ord_secret_1": {
            "tenant_id": "t1",
            "owner_id": "u_alice",
            "status": "FULFILLED",
            "card_keys": ["REDACTED-DEMO-KEY"],
        }
    }
    assert fetch_order_card_keys(db, {"tenant_id": "t1", "user_id": "u_alice"}, "ord_secret_1")["card_keys"]
    try:
        fetch_order_card_keys(db, {"tenant_id": "t1", "user_id": "u_bob"}, "ord_secret_1")
        raise AssertionError("should block IDOR")
    except PermissionError:
        pass
    print("payment-email-bounce-idor-defense self-check passed")

防御工作流 ​

text
1. 邮件资产剥离:改造发信模板,移除一切卡密、兑换码与免登查询直链,改为“登录后门户领取”。
2. 退信链路隔离:对邮件服务商 Bounce Webhook 强制验证官方加密签名;退信落库仅保留状态码、错误分类与收件人哈希,丢弃原始邮件回传体。
3. 订单标识随机化:对外暴露的 `order_id` 统一改为 CSPRNG 128-bit 不透明标识(保留内部自增主键仅用于联表)。
4. 强制所有权校验:所有订单、发票与卡密接口在控制器与数据查询层双重附带 `WHERE owner_id = current_user_id AND tenant_id = current_tenant_id`。
5. 邮箱验证与抑制:发送任何订单凭据前要求邮箱已验证,硬退信达阈值自动加入发送抑制名单。

验证与判定标准 ​

  • 退信零敏感信息:构造不存在的收件邮箱触发硬退信后,退信记录与告警内容中均不含卡密、兑换码、金额或客户 PII。
  • 跨账号越权阻断:使用账号 B 访问账号 A 的 order_id 或退信查询接口,均返回 403 或统一 404(不泄露订单存在性)。
  • 订单号不可枚举:新生成订单号无时间戳与自增顺序特征,无法通过偏移加减命中相邻订单。

MCP 工具映射 ​

防御环节MCP 工具用途
订单与退信接口权限核查http_probe验证不同会话访问他人 order_id 及退信端点均返回拒绝状态
IDOR 与邮件防线检索kb_router检索 IDOR 防御与邮件安全加固关联条目
配对攻击细节对照kb_read_file对照读取 ctf-website/12-payment/payment-email-bounce-idor 核查防御点
防御条目目录查询kb_catalog列出访问控制与信息泄露防护模块

Apache-2.0 · For authorized learning and defensive research only · Disclaimer