退信泄露与订单越权防御 — 最小化 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 | 列出访问控制与信息泄露防护模块 |