Skip to content

签名规范化绕过防御(Verify-After-Parse / RFC 8785 JCS / HPP 拦截) ​

防御目标 ​

消除“验签层看到的报文”与“业务层读取的报文”之间的解析差异,阻断参数排序截断、URL 二次解码差异、JSON 重复 key 覆盖、HTTP 参数污染(HPP)以及 XML 签名包裹攻击(XSW)。

规范化差异与防御标准对照 ​

绕过维度解析差异根源强制防御标准
HTTP 参数污染(HPP)验签取首值/全拼接,业务层取末值(amount=0.01&amount=100)解析 Query/Form 时严禁静默覆盖重复键,发现任意重复参数名直接返回 400 Bad Request
sign 字段混入排序与截断sign 字段自身参与排序或拼装字符串未按定界符转义仅对净业务字段集合签名;字段名与值之间用不可伪造长度前缀或 RFC 8785 JSON 序列化
URL 编解码差异一端对 +/%20/%2F 解码后再签名,另一端按原文验签统一规定按解码后规范键值对排序,或直接对原始字节流(如 Webhook raw_body)做 HMAC
JSON 键冲突与数字格式JSON {"amount":100,"amount":0.01} 或 1e2 vs 100 导致差异反序列化时使用 object_pairs_hook 拒绝重复键,并采用 RFC 8785(JCS)确定性序列化或直接验签原始 raw_body
XML 签名包裹(XSW)仅验证 Reference URI 指向的签名节点,而业务 XPath 读取了未签名位置节点先校验 SignedInfo 内 URI 唯一性及与业务根节点位置关系(只信任已验签子树本身,不再重新全局 XPath 搜索)

防御实现与校验示例 ​

1. 拒绝重复键的表单/JSON 统一规范化验签器(Python) ​

确保验证层与业务层消费完全一致的数据对象,在解析阶段即拦截 HPP 与 JSON 重复键:

python
import hmac
import hashlib
import json
from urllib.parse import parse_qsl

def parse_strict_query(raw_query: str) -> dict[str, str]:
    pairs = parse_qsl(raw_query, keep_blank_values=True, strict_parsing=False)
    result: dict[str, str] = {}
    for k, v in pairs:
        if k in result:
            raise ValueError(f"duplicate_query_param:{k}")
        result[k] = v
    return result

def parse_strict_json(raw_json: bytes) -> dict:
    def no_duplicates_hook(ordered_pairs: list[tuple[str, object]]) -> dict:
        obj = {}
        for k, v in ordered_pairs:
            if k in obj:
                raise ValueError(f"duplicate_json_key:{k}")
            obj[k] = v
        return obj
    data = json.loads(raw_json.decode("utf-8"), object_pairs_hook=no_duplicates_hook)
    if not isinstance(data, dict):
        raise ValueError("json_root_must_be_object")
    return data

def verify_and_extract_query(secret: bytes, raw_query: str, sig_param: str = "sign") -> dict[str, str]:
    params = parse_strict_query(raw_query)
    if sig_param not in params or not params[sig_param]:
        raise ValueError("missing_signature")
    client_sig = params.pop(sig_param)
    # 明确排除 sign 字段后,按 UTF-8 字典序序列化净键值对
    canonical = "&".join(f"{k}={params[k]}" for k in sorted(params))
    expected = hmac.new(secret, canonical.encode("utf-8"), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(client_sig.lower(), expected):
        raise ValueError("invalid_signature")
    # 下游业务只能使用这里返回的已验签 params,严禁重读 raw_query
    return params

if __name__ == "__main__":
    key = b"K" * 32
    base_canon = "amount=100&order_id=A1"
    good_sig = hmac.new(key, base_canon.encode(), hashlib.sha256).hexdigest()
    verified = verify_and_extract_query(key, f"order_id=A1&amount=100&sign={good_sig}")
    assert verified == {"order_id": "A1", "amount": "100"}
    try:
        verify_and_extract_query(key, f"order_id=A1&amount=100&amount=0.01&sign={good_sig}")
        assert False, "HPP should have failed"
    except ValueError as e:
        assert "duplicate_query_param" in str(e)
    try:
        parse_strict_json(b'{"amount":100,"amount":0.01}')
        assert False, "Duplicate JSON key should have failed"
    except ValueError as e:
        assert "duplicate_json_key" in str(e)
    print("canonicalization_defense_check: PASS")

防御工作流 ​

text
1. 单一解析器原则:在网关或中间件层仅解析一次请求,拒绝任何重复键或编码歧义
2. 原始体签名优先:对 Webhook/API POST 场景优先直接对 HTTP raw_body 字节流计算 HMAC,避免 JSON 重序列化漂移
3. 剔除签名字段后序列化:确保 sign 字段本身绝不进入待签字符串参与排序或拼接
4. 传值不传原文:验签函数返回已验证的强类型对象给下游业务,禁止业务代码二次读取原始 request 字符串
5. XML 安全加固:禁用外部实体(XXE),并使业务层直接读取 Signature 验证通过后返回的已绑定 DOM 子树

验证与判定标准 ​

  • HPP 阻断:请求包含重复键(如 amount=100&amount=0.01)或重复 JSON 键时,解析层 100% 抛出拒绝错误
  • 值绑定一致:修改空值、空格、+/%20 或键顺序,服务端规范化结果唯一且不产生与业务层读取值的偏差
  • XSW 防护:在 SAML/XML 报文插入未签名克隆节点时,业务层仅读取经签名引用确认的节点内容

MCP 工具映射 ​

防御环节MCP 工具用途
关联重放与实现防御kb_router检索签名实现与算法锁定方案
严格解析中间件落盘workspace_write_text输出 parse_strict_query 与 parse_strict_json 工具模块
参数污染监控规则make_sigma_stub生成检测请求中包含重复关键参数(HPP)的 WAF/日志规则

Apache-2.0 · 仅供授权环境下的学习与防御性研究使用 · 免责声明