链接协议及账户抽象的全链路安全拆解
1. 说明
WalletConnect 协议与账户抽象(ERC-4337 及 EIP-7702)的结合,为去中心化身份验证和交易体验带来了革命性变化。本调研旨在从技术实现、交互流程、安全风险及针对钓鱼攻击案例等角度,深入研究 WalletConnect 与账户抽象在 Web3 环境下的安全架构,并探讨针对智能合约钱包安全漏洞的防御方案。
2. WalletConnect 协议及其升级解析
什么是 WalletConnect
WalletConnect 是一种开源的跨钱包通信协议,用于让去中心化应用(dApp)与用户的钱包(如 MetaMask、Rainbow、OKX、OneKey 等)安全连接与交互。它不是一个钱包或应用,而是“连接标准”和“消息传输通道”。
核心作用:不暴露私钥的前提下,让 dApp 请求用户钱包进行签名、发送交易、签名消息、链上切换等操作。
典型场景:在网页 dApp 上点击“Connect Wallet”,通过二维码/深链(deep link)在手机钱包中确认连接,然后可在网页发起交易并在手机签名。
作用与价值
跨设备与跨平台:网页 dApp(PC)与移动端钱包安全连接,不要求浏览器插件。
安全性:私钥始终留在钱包端;dApp 只能发送“请求”,无法直接操作资产。
多链支持:兼容以太坊及 EVM 生态,也支持多链扩展(取决于钱包与 dApp的实现)。
统一标准:通过标准化的请求/响应流程(如
eth_sendTransaction
、personal_sign
),降低集成成本。更好的用户体验:扫码即连、手机确认、可断线重连。
工作原理(简化)
会话创建(Pairing)
dApp 生成连接提议(包含协议版本、支持链/方法、回调端点等),编码为
wc:
开头的 URI。用户在钱包中通过扫描二维码或点击深链打开该 URI。
钱包与 dApp 进行密钥协商,建立加密通道,形成“配对关系”(Pairing)。
会话批准(Session Approval)
钱包展示 dApp 的连接请求(包含 dApp 名称、域名、请求权限、支持链)。
用户在钱包中确认后,钱包返回批准的命名空间(如
eip155:1
代表以太坊主网)和允许的方法(如eth_signTypedData
、eth_sendTransaction
)。会话建立成功,双方获得一个会话
topic
(类似会话 ID)用于后续通信。
请求与响应(Request/Response)
dApp 通过中继网络向会话
topic
发送 JSON-RPC 请求(例如eth_sendTransaction
)。消息以对称密钥加密,通过去中心化中继网络传输。
钱包在本地弹出确认 UI;用户确认后,钱包使用本地私钥完成签名或发起链上交易,并将结果(签名、交易哈希或错误)通过相同通道返回给 dApp。
状态同步与重连
若网络中断或页面刷新,双方可基于持久化的会话信息自动重连。
可随时在钱包或 dApp 端断开会话,撤销权限。
安全要点
私钥不离开钱包;中继仅转发加密消息,不可解密。
会话权限最小化:只允许声明过的方法与链。
域名绑定与会话提示:帮助用户识别 dApp 身份,防钓鱼。
2.1 WalletConnect v1.0 概述
早期版本的 WalletConnect 实现中,用户通过扫描二维码建立连接,然后通过中继服务器交换加密消息,其核心流程可以概括为以下几个步骤:
用户操作启动连接请求后,由 dApp 通过桥接服务器发布加密数据;
钱包端通过扫描二维码或深链启动,接收加密请求,并提示用户确认连接;
双方互相订阅对方的连接主题,完成建立连接及后续交易签名过程。
这种模式虽然实现了跨设备连接,但存在部分问题,例如某些较老版本的钱包不支持最新协议、频繁的链间切换以及浏览器端的风险问题。
2.2 WalletConnect v2.0 升级架构
为了解决上述问题,WalletConnect v2.0 采用了全新的架构设计,核心在于“地址簿-邮箱”模型。通过建立地址簿来记录各客户端与指定 WebSocket 服务器之间的连接,同时引入邮箱机制来缓存离线消息,从而确保双方在任何网络状态下都能及时获得消息.
这种新架构不仅降低了延迟,还通过水平扩展来支持大规模用户的并发连接,解决了传统版本中链路不稳定与过长延时的问题,从而显著提升了用户体验与系统安全性.
2.3 协议安全机制的改进
在安全层面,WalletConnect v2.0 除了加强端到端加密外,还通过改进消息传递策略降低了因链间切换或旧版本兼容性导致的不安全因素。例如,通过消息中继机制,即使某一方暂时离线,消息也能安全地存储在邮箱中,等待对方上线后立即传送,避免了因连接中断引起的信息泄露或篡改风险.
2.4 WalletConnect 的能力边界拆解
虽然 WalletConnect 在连接与信息传输中表现出色,但其设计并不涉及整个钱包生态的所有安全防护措施。例如:
签名策略与交易验证:WalletConnect 自身不处理交易签名的细节,而是依赖于钱包应用中嵌入的签名算法。
权限管理与用户验证:连接后 DApp 的调用权限管理依赖于钱包端对每次请求进行人工或自动审批。
会话管理与生命周期控制:虽然 WalletConnect 支持会话的建立和维护,但会话过期、重连机制仍需要开发者根据业务需求在上层进一步设计。
多阶段风险面与防御要点
发现与配对阶段(扫描二维码/点击深链)
风险点与防御
伪造二维码/域名混淆
防御:
显示可验证 DApp 指纹:域名、证书哈希/ENS 反向解析结果、WC Project ID、图标指纹(hash)。
域名:如
app.uniswap.org
。这是最直观的品牌身份。证书哈希/证书指纹:从 TLS 证书提取的指纹(如 SHA-256 指纹),便于对照官方公告或缓存白名单。
ENS 反向解析结果:例如将 DApp 的公开地址做 ENS 反向解析,显示
dapp.eth ↔ 0x...
的匹配结果;若反向解析与正向解析一致,风险更低。WalletConnect Project ID:WC 2.0 要求项目使用 Cloud Project ID。展示并校验该 ID 可减少中间人伪装。
图标指纹(hash):将 DApp 提供的图标做哈希显示,防止替换图标误导用户。
工作原理:将“机器可验证的身份信息”转化为“用户可识别的要素”,提升对仿冒站的可见度。
来源校验提示:二维码来源是嵌入式页面还是跳转页;提示用户核对域名与证书信息。
嵌入式页面 vs 跳转页:
嵌入式:二维码出现在 DApp 的官方页面内。
跳转页/中转页:从社交媒体、广告、搜索结果跳转到的页面,风险更高。
来源域名与证书:提示用户核对当前页面的域名和 TLS 证书信息是否与官方公布一致。
Referer/Deep link 来源:对于移动端深链,显示触发应用的包名/签名或来源 App。
工作原理:结合浏览器上下文、应用来源信息,帮助用户判断是否在“正确的站点/应用里”完成连接。
相似域名告警:如 punycode/同形异义字符检测。
punycode 展开与高亮:将
xn--
域名展开并高亮差异字符。同形异义字符集检测:检测混用字符集(拉丁、希腊、西里尔)或相似字形。
编辑距离与品牌词库:与品牌白名单作 Levenshtein 距离对比,距离很小但非白名单时提示。
工作原理:基于字符集规则和品牌词典识别“视觉上相似但实际不同”的域名。
举例:
裁剪/篡改的
wc:
URI现象:攻击者在分享时篡改
relay-protocol
、symKey
、topic
或 metadata 链接,导致指纹错配或会话劫持尝试。防御:
严格解析与校验 URI:校验必要字段、协议版本、参数格式;超出规范立即拒绝。
校验版本:
<version>
必须为受支持的版本(例如2
),否则拒绝。必需参数:对当前协议模式下的必需参数进行完整性检查:
relay-protocol
:必须在受支持列表(如irn
)。若缺失或为未知值,拒绝。symKey
:必须为期望长度/编码(Base16/Base64 的具体长度);长度或字符集不合法直接拒绝。topic
:应满足长度与字符集规则(通常为 32 字节随机量的十六进制表示)。不符合即拒绝。projectId
(若出现在 URI 或随提议提供):必须存在且格式匹配。
参数格式与编码:
对每个参数执行严格的 URL decode,禁止二次编码绕过。
拒绝包含控制字符、空白、不可见字符或超长值(设定上限,例如 512 字节)。
拒绝重复参数、未知关键参数、或保留字段被意外占用。
跨字段一致性:
如果 URI 同时携带
relay-protocol
与relay-data
,对 relay-data 的键值做 schema 校验。如果 URI 引用了 metadata 的外部链接,需强制 HTTPS,并对响应大小/类型做限制(防止恶意 payload)。
显示并比对 Project ID 与 DApp 域名映射(钱包内维护安全清单或基于众包信誉)。
可信清单来源:
官方/行业联盟签名发布的清单(含域名、品牌名、Project ID、图标哈希、证书指纹)。
众包信誉:结合上报与投票机制,但需抗操纵(签名、阈值与冷却期)。
多因子比对:
域名匹配(含
punycode
展开与二级域名策略)。图标哈希与品牌名校验。
TLS 证书指纹比对(若在 dApp 内置浏览器环境可获取)。
缓存与历史一致性:
对已连接过的会话记录“历史 Project ID”,若同一域名切换 Project ID,弹出“指纹变更”强提醒。
允许“官方迁移模式”(清单中标注有效期内的新旧双签名有效)。
剪贴板劫持
现象:用户复制
wc:
链接时被恶意软件替换。防御:
粘贴检测与二次确认:粘贴时高亮显示目标域名/Project ID;与历史指纹比对并提示变化。
粘贴检测:
wc:
前缀、URI 解码安全、重复字段拒绝。100–300ms 二次剪贴板一致性检查。
指纹展示:
域名(punycode 展开)、Project ID、relay、icon hash。
比对策略:
历史档案比对、可信清单比对、品牌相似度检测。
深链/第三方 App 跳转钓鱼
现象:从非官方聚合器或广告落地页拉起钱包扫描。
防御:
来源 App 显示:在授权页显示“来自 XX 应用/浏览器”;来源异常时警示。
受信来源白名单:允许用户配置可信钱包内置浏览器/聚合器名单。
会话建立阶段(session proposal → approve)
风险点与防御
会话过权(methods 超范围)
防御:
模板化最小权限:默认仅授予只读方法与必要链。
一键降权:可取消高危 methods/chains。
高危方法二次确认:
eth_sign
、eth_sendTransaction
等。为不同 DApp 保存差异化权限集:记忆上次授权,避免每次放大。
链混淆与重放
防御:
强制检查 chainId 与会话匹配。
签名 UI 显示目标链。
EIP-155 防重放;typed data 包含 chainId、domain separator。
Namespaces 欺骗与“默认全链”请求
现象:DApp 请求广泛的
eip155:*
或多个主网/侧链以扩大攻击面。防御:
拒绝通配链,要求逐链列举并逐链确认。
按链隔离账户选择:每条链单独选择/隐藏地址。
错配的 metadata 与身份切换
现象:同域名但不同 Project ID、图标/描述突变。
防御:
指纹缓存与变更提醒:图标哈希、Project ID、名称/描述任何变化都提示“身份变更”。
高敏变更默认降级权限:若身份不一致,限制为只读或拒绝。
会话使用阶段(RPC 请求、签名与事件)
风险点与防御
事件滥用与钓鱼弹窗
防御:
请求限流与去抖:频繁请求/事件订阅限制。
显示来源 DApp 名称与域名。
后台请求标识:当来源页面不在前台,增加“背景提醒”。
请求升级(session_update 滥用)
现象:DApp 在会话中追加 methods/chains,试图“静默扩大”权限。
防御:
任何扩大权限的更新必须弹窗二次确认,并显示差异对比(增删改)。
只允许在原 DApp 指纹未变更前提下更新。
高危签名诱导
现象:
eth_sign
任意签名、permit
/setApprovalForAll
类授权签名伪装成登录/抽奖。防御:
签名可视化:结构化解析 typed data,标红权限/额度/到期/目标合约。
已知恶意模板检测:基于特征字段和链上黑名单合约地址提示。
默认禁用
eth_sign
,仅一次性特许并带强警示。
交易目标混淆与金额欺骗
现象:显示
0 ETH
但调用代币授权;或利用自定义代币名混淆。防御:
合约方法解码:显示函数名、参数(spender、amount)、代币精度与符号来自可信源(多源交叉验证)。
风控规则:异常大额、无限授权、陌生合约标红且需长按确认。
事件订阅资源滥用(CC级别攻击)
现象:海量事件/订阅导致钱包性能受损或内存膨胀。
防御:
订阅配额与超时回收;对无交互会话降级事件推送频率。
会话维护阶段(续期、空闲、重连)
风险点与防御
断线重连与请求重放
防御:
消息去重与时序校验:基于 nonce、会话计数器、时间窗。
状态恢复时强制 re-auth:若断线超过阈值或密钥轮换,要求重新确认关键权限。
多设备并发会话状态漂移
现象:同一钱包在多设备上对同一会话进行不同降权/续期,出现“不一致授权”。
防御:
会话状态单一事实源:通过账户云备份或本地加密同步,合并策略为“最小权限优先”。
会话终止阶段(到期、撤销、删除)
风险点与防御
会话未真正撤销
现象:钱包侧删除 UI 记录,但 DApp 仍认为会话有效。
防御:
双向终止:发送
session_delete
并等待确认;失败则标记为“已失效”,拒绝后续消息。
残留缓存与指纹泄露
现象:本地日志/缓存保留敏感 metadata。
防御:
最小存留:仅保留 DApp 指纹哈希与审计摘要;清理会话密钥和临时数据。
可导出审计日志:供用户回溯授权变更,日志本地加密存储。
自动续期陷阱
现象:DApp 利用后台请求触发续期,用户未感知。
防御:
续期必须用户显式操作:禁止静默续期;后台请求只可导致提示,不可自动生效。
3. 账户抽象协议(EIP-4337)
传统的以太坊账户主要分为两类:外部拥有账户(EOA)和合约账户。而 EIP-4337 提出的账户抽象(Account Abstraction)机制,旨在将传统钱包与合约功能相结合,为用户提供更为灵活安全的使用方式。
作用:
在不改协议层的情况下,把账户“智能化”
通过
UserOperation + Bundler + EntryPoint + Paymaster
,让账户逻辑在合约中可编程,实现:可编程验证:多签、阈值、社交恢复、自定义签名算法、会话密钥。
灵活支付:第三方代付、用 ERC-20 支付 gas、费用赞助。
更好的 UX:批量交易、插件化、策略化执行,减少签名次数。
渐进部署与兼容性
无需硬分叉或节点升级,任何链可落地;为生态提供一套“完整 AA 基建”路径。
3.1 EIP-4337 核心组件与工作流程
EIP-4337 的基本思想是通过一个名为 “UserOperation” 的结构,对用户操作进行打包、打签名,并由称为 Bundler 的节点收集这些请求,将它们聚合打包后发往区块链进行处理。
ERC-4337 核心工作流程
User 创建 UserOperation:用户创建一个 UserOperation 对象,其中包含交易的各种参数,例如接收者、调用数据、gas 限制和签名等。UserOperation 类似于一个交易 "to-do list" 提供给你的 Ethereum 账户。
提交到 Alt Mempool:UserOperation 被发送到一个替代的内存池(alt mempool),这个内存池专门用于存放 UserOperation,而非传统的交易。
Bundler 收集:Bundler 节点监听 alt mempool,收集多个 UserOperation。Bundler 可以被认为是“facilitators”。
Bundler 打包:Bundler 将收集到的多个 UserOperation 打包成一个 batch,然后将这个 batch 提交到 Ethereum 网络。
EntryPoint 验证和执行:EntryPoint 是一个智能合约,作为 Ethereum 网络的“gatekeeper”。它负责解包 Bundler 提交的 batch,并逐个执行其中的 UserOperation。如果遇到任何操作失败,它可以回滚该操作的所有动作,确保交易的完整性和可靠性。
Paymaster (可选):如果 UserOperation 使用了 Paymaster,Paymaster 是一种可以代表您的交易支付(赞助)交易费用的可选实体(即智能合约)。
Aggregators (可选):Aggregators 是与 Contract Account 交互的可选智能合约,帮助它一起验证来自多个 UserOperation 的签名。
4. EIP-7702 的改进与兼容性分析
随着 EIP-4337 的推广应用,社区中逐渐提出了更为高效且符合实际操作需求的新版本——EIP-7702。
作用:
让传统 EOA 临时获得“合约化验证能力”(低摩擦)
通过原生协议支持,让 EOA 在一笔或短期内“代理”到某段合约代码进行验证,从而:
在无需部署智能钱包的情况下,获得多签/限权/会话授权等核心 AA 体验。
保持向后兼容,适合海量存量 EOA 用户快速使用。
降低 AA 普及门槛与迁移成本
不依赖 4337 的外部基础设施(bundler/mempool),轻量、原生、即插即用,作为 AA 的“过渡/桥接方案”。
4.1 EIP-7702 的核心改进点
EIP-7702 主要在以下几个方面进行了改进:
灵活执行逻辑:允许 EOA 在安全授权下临时具备执行合约代码的能力,从而减少多层中介带来的延时与复杂性;
模块化设计:在保留账户抽象基本框架的前提下,更加注重多层次智能合约模块之间的调用与交互,极大地提升了系统整体的扩展性和安全性;
用户体验优化:通过简化链间切换流程、减少重复验证过程,使得用户操作流程更加平滑,交易响应时间显著缩短。
流程图解释:
User Initiates Transaction: 用户发起交易。
Wallet Creates "set code transaction": 钱包创建一个类型为
SET_TX_CODE_TYPE
的 "set code transaction",用于将合约代码与用户的 EOA 关联。Wallet Signs "set code transaction": 钱包对 "set code transaction" 进行签名。
Sends "set code transaction": 用户将签名后的 "set code transaction" 和初始化数据发送给 Relayer。
Relayer Broadcasts: Relayer 将交易广播到区块链网络。
Executes Contract Code: EOA 执行合约代码,从而转变为 Smart Account。
User Initiates Multicall Transaction: 用户发起一个多重调用交易,用于执行多个操作。
Wallet Signs Multicall Transaction: 钱包对多重调用交易进行签名。
Sends Multicall Transaction: 用户将签名后的多重调用交易发送给 Relayer。
Relayer Forwards: Relayer 将多重调用交易转发到 EOA 进行执行。
Executes Multicall Transaction: EOA 执行多重调用交易。
Returns Result: EOA 将执行结果返回给用户。
关键点:
SET_TX_CODE_TYPE: 新的交易类型,允许将合约代码与 EOA 关联。
Relayer: 帮助广播交易到网络的第三方服务。
Multicall: 将多个函数调用编码到单个交易中。
EOA 变为 Smart Account: EOA 通过执行合约代码,直接从其地址执行合约代码,变为具有智能合约功能的账户。
无需 Bundler/Paymaster 基础设施: 如果只需要批量处理交易,EOA 可以直接发送调用自身的常规 EOA 交易,而无需 Bundler/Paymaster 基础设施。
4.2 EIP-7702 与 EIP-4337 之间的兼容性
尽管 EIP-7702 引入了诸多改进,但从架构角度来看,它并非完全替代 EIP-4337,而是建立在同一体系中的优化方案。两者在基本设计理念上保持高度一致,目的都是为了实现更安全高效的交易验证流程。实际上,EIP-7702 的推出既为现代化钱包提供了更多灵活性,也为未来多功能钱包设计指明了方向.
比较表:EIP-4337 与 EIP-7702 核心特性对比
核心痛点(共同想解决)
单一签名模型僵化:传统 EOA 只能用单把私钥签名,无法原生支持多签、社交恢复、限额、会话授权等。
Gas 支付刚性:必须用 ETH 支付 gas,新手门槛高;无法原生代付或用代币付费。
交互体验差:不能批量操作、难以做自动化策略与插件扩展;复杂交互需要多次签名,容易出错。
迁移成本高:现有大量 EOA 用户与资产,直接迁移到智能钱包摩擦大。
注意:
EOA 仍然是 EOA:地址形态不变、余额不变、依旧由你的私钥控制。
临时绑定合约逻辑:在发起交易时,EOA 可以提供一个“要使用的验证/执行逻辑(合约代码或其引用)”,节点在该交易的验证过程中按该逻辑来验签/限权/批处理等。
交易上下文内生效:这种“合约化验证”是针对特定交易或时间窗口生效,过后 EOA 不会永久携带该合约代码,也不会像 4337 的账户合约那样常驻链上。
风险挖掘
授权域与重放风险
场景例子
某钱包实现 7702 授权时,让用户对一段“授权使用某合约代为执行”的消息签名,但消息只包含
authorizedContract
地址,没有绑定chainId/有效期/唯一 uid
。攻击者截获该签名后,将其打包到另一条链(如从 Polygon 复制到 BSC),在相同的授权合约地址部署后重放,导致用户在另一条链上也被“临时托管”,并被发起高风险操作(如对陌生合约approve
无限额度)。
防御要点
使用
EIP-712
结构化授权,强制包含chainId/authorizedContract/validUntil/uid (nonce-like)
,并与交易上下文匹配。钱包拒绝用
eth_sign/personal_sign
承载 7702 授权,统一使用模板化域。授权合约在执行前自检:
require(block.chainid == domainChain && msg.sender == expectedEntry)
。
Nonce 与时效管理缺陷
场景例子
钱包将 7702 授权消息与普通 EOA 交易共用全局
nonce
。用户在前台签好一笔授权,后因网络卡顿,先广播了另一笔普通转账,导致nonce
变化。攻击者利用“未消费授权”在窗口期内构造另一笔交易,用老授权绕过检查执行敏感操作。攻击流程举例(时间线)
参与角色与设定
用户 Alice:EOA,使用支持 EIP-7702 的钱包。
授权合约 X:临时 AA 执行入口,需要 Alice 的授权签名。
钱包缺陷:将“7702 授权的 nonce”和“普通转账的 EOA nonce”共用同一全局计数器;且授权签名不含独立的分域 nonce/uid/短 TTL。
攻击者 Mallory:监听内存池与用户链上行为,有机会与 Alice 交互或拦截流量(如钓鱼 DApp、前端注入、同链 mempool 观察)。
原理
共享全局 nonce 的问题
EOA 的全局 nonce 设计用于“顺序化外部交易”,不是用来界定“授权消息”的消费顺序。
当“授权签名”和“普通转账”共用一个计数器时,会出现:
前台签了授权(假定 nonce=N),但链上先消费了另一笔普通交易(同样 nonce=N),导致“授权对应的链上状态背景”发生位移。
若授权合约的校验不是对“独立的分域 nonce/uid”做强一致匹配,就可能让签名在位移后的状态下仍被接受(因为链上并没有记录这份授权的“消费状态”)。
缺少 uid/短 TTL 导致“重放窗口”过大
没有一次性
uid
(签名级唯一 ID),就难以在合约上精确标记“这份签名是否已被消费/撤销”。TTL 过长(如 30 分钟、几小时)给了攻击者足够时间等待机会(比如等用户先广播别的交易、等网络或 relayer 抖动),从而在可利用窗口里完成攻击。
合约校验粒度不够
仅凭“签名有效 + 授权合约地址匹配”是不够的。
缺少以下强校验之一就容易出事:
分域 nonce 精确匹配(并在消费后自增)
一次性 uid 消费标记(并支持撤销)
短 TTL 硬限制(签名过期立即拒绝)
防御要点
分域 nonce(隔离授权与普通交易)
为授权定义独立的 nonce 域,例如
nonceDomain = 7702_EXEC
。合约存储
domainNonce[owner][nonceDomain]
,与 EOA 全局 nonce 完全独立。授权签名中携带
expectedNonce
,执行时要求:require(domainNonce[owner][domain] == expectedNonce)
成功后
domainNonce[owner][domain]++
示例(伪代码):
function execute(auth, sig, expectedNonce): signer = verifyEIP712(auth, sig) require(auth.nonceDomain == DOMAIN_7702_EXEC) require(domainNonce[signer][DOMAIN_7702_EXEC] == expectedNonce, "nonce mismatch") domainNonce[signer][DOMAIN_7702_EXEC]++ // ...
强制短 TTL + 可选 validAfter
授权中必须包含
validUntil
(建议 1–5 分钟)和可选validAfter
。钱包侧签名前先做本地 TTL 校验(例如>5分钟拒绝),发送前/模拟前再次检查当前时间。
合约侧在
verify
时强制validAfter <= now <= validUntil
。
示例(伪代码):
require(now >= auth.validAfter && now <= auth.validUntil, "expired");
一次性 uid 与撤销
授权签名包含
uid = keccak256(random || context)
。合约有
consumed[uid]
映射,首次使用置 true;提供revokeUid(uid)
主动作废。钱包提供“未消费授权”列表与一键撤销入口;若在有效期内未被消费,持续提醒。
示例(伪代码):
require(!consumed[auth.uid], "replay");
consumed[auth.uid] = true; function revokeUid(uid) external { consumed[uid] = true; }
客户端流程加固
构造授权时读取链上
domainNonce
作为expectedNonce
,签名绑定之。发送前双 RPC 模拟,避免节点差异引发错误判断。
若检测到“同账户同域存在未消费授权”,在 UI 顶部持续告警,提供立即撤销按钮。
若用户在授权后先发了其他交易,客户端应提示此前授权“可能失效/需重签”,并默认不继续使用老授权。
批量执行中的顺序依赖与“沙盒突破”
场景例子
DApp 构造一笔 7702 批量交易:
第一步升级授权合约的白名单策略,移除限额/白名单;
第二步立刻调用一个陌生合约执行
permit
无限授权;
用户界面只看到“批量执行 2 步”,未意识到第一步已解除安全限制。
你在一个“免 Gas 领取空投”的网站领取奖励。
网站让你签一个 7702 授权,随后弹出“批量交易 2 步”:
“为更好发放空投,更新授权策略(安全优化)”;
“调用空投合约领币(需要代币授权)”。
实际第一步把“任意合约调用 + 无额度限制”打开,第二步就给攻击者的合约“无限授权”。
攻击者随后从你的钱包直接把常用代币(USDT、USDC、WETH)拉走。
防御要点
策略变更延时生效(合约侧)
任何“更新白名单/额度”的修改,都写入“延时队列”,必须在下一笔交易之后才生效。
代码思路(简化):
// 策略变更必须 schedule -> 等待 DELAY -> commit 才生效
uint48 constant DELAY = 300; // 5 分钟为例
struct PendingChange { bytes32 what; bool value; uint48 eta; }
mapping(bytes32 => PendingChange) public pend;
function scheduleChange(bytes32 what, bool value) external {
bytes32 id = keccak256(abi.encode(what, value, block.timestamp));
pend[id] = PendingChange({what: what, value: value, eta: uint48(block.timestamp + DELAY)});
}
function commit(bytes32 id) external {
PendingChange memory p = pend[id];
require(p.eta != 0 && block.timestamp >= p.eta, "DELAY_NOT_OVER");
_apply(p.what, p.value); // 实际更新白名单/限额
delete pend[id];
}
这样,一个批量里无法做到“先改策略再立刻放开危险操作”。
钱包逐条解码和高危标红(客户端侧)
针对每个子调用显示:
to
地址、selector
、是否为高危函数(如approve/permit/setApprovalForAll
);金额/额度(是否
type(uint256).max
)、是否新合约或低声誉合约。
对高危项要求“二次确认”,并提供“仅签低危子集”的选项。
UI 上的“差异对比”
对“策略变更”类调用,直观展示“变更前 VS 变更后”(白名单多了哪些地址?额度从多少到多少?)
用户能看懂“第一步让进入白名单”“额度从 100 提高到无限”。
// 极简分类示例
function classify(selector, to, value, decodedArgs) {
if (selector === "0x095ea7b3" /* approve */ && decodedArgs.amount === MAX_UINT)
return "high";
if (selector === "0xd505accf" /* permit */)
return "high";
if (value > 0) return "mid";
return "low";
}
禁止“策略变更与敏感操作”同批生效(合约侧强规则)
在执行器中添加规则:若批量中含有“策略变更”子调用,那么本批中的“敏感操作”(
approve/permit/transferFrom/upgrade
等)直接拒绝。
bool policyChangedInThisBatch = false;
for (i in batch) {
if (isPolicyChangeCall(calls[i])) policyChangedInThisBatch = true;
if (policyChangedInThisBatch && isSensitiveCall(calls[i])) revert("POLICY_CHANGE_THEN_SENSITIVE_BLOCKED");
}
高危签名诱导(把授权伪装成登录/空投)
场景例子
钓鱼站提示“签名即可免 Gas 领取空投”,实际让用户用 7702 模板签下“授权合约 = 攻击者控制”的授权。交易提交后,授权合约立刻对多个代币执行
permit
或approve
无限额度,随后从用户常用 DEX 拉走资产。
防御要点
UI 可视化授权摘要:显示授权合约指纹(地址、代码哈希、发布者)、目标白名单、额度、有效期。
默认拒绝“无限授权/永久有效”,需长按+二次确认;对陌生合约/新部署合约标红。
钱包内置受信模块/发布者白名单,历史指纹变更高亮。
费用与代付路径被滥用(过费/扣错资产)
场景例子
授权合约支持用特定代币代付。攻击者在构造交易时设置超高
maxFeePerGas
和callGasLimit
,并诱导用用户持有的治理代币代付,短时间消耗大量余额。
防御要点
钱包对
maxFeePerGas/maxPriority/callGasLimit
做上限+异常值二次确认。授权合约实现 per-token/per-day 限额与速率限制,超限即
revert
。代付方需白名单并在 UI 明确展示“谁在付费、从哪里扣”。
与 ERC-4337/传统流程混用造成边界错判
场景例子
某 DApp 既支持 4337 又尝试支持 7702。它把“登录签名”的
personal_sign
错误复用到 7702 授权路径,导致任意环境下可将登录签名拼接为授权,越权执行。
防御要点
严格区分签名域:4337 的
UserOperation
、7702 的授权域、普通登录域各自独立,不可复用。SDK 提供防混用保护:一旦走 7702 授权流程,必须是 7702 模板数据结构;其它签名一律拒绝。
5. 案例分析:
1.假冒 WalletConnect 应用及钓鱼攻击
假“WalletConnect 客户端”App(移动端)
表现:
在应用商店/第三方下载站上架,自称“官方 WalletConnect 钱包/客户端/连接器”。
启动后引导“连接钱包以验证/领取”,随后连续弹出签名授权。
有的版本会直接诱导“导入助记词”;更多时候依赖授权窃取。
已被多家钱包/安全团队通报:
常见为短期上架、评分刷量、随后下架的循环。
通报点包括图标与名称仿冒、请求权限异常、内置恶意深链/二维码指向陌生会话。
假“WalletConnect 支持/验证”网页(桌面端 + 手机钱包扫码)
表现:
伪装“官方支持/验证/修复”页面,域名含有“walletconnect/help/support/verify”等字样。
展示二维码或
wc:
深链,诱导你用手机钱包扫码连接。连接后立刻发起
approve(MAX)
、permit/Permit2
或setApprovalForAll
请求。
可见证据:
多起“扫码后几分钟被扣划”的受害者在链上浏览器的交易历史中出现“授权→transferFrom”的连续交易。
安全研究者在 Twitter/Discord/GitHub issues 中多次披露这类域名与会话参数篡改。
假“空投/白名单/兑换”页面内置 WalletConnect
表现:
通过社媒广告/钓鱼私信引流到“假空投/白名单”的站点。
点击“Connect Wallet(WalletConnect)”后,钱包弹出“不会花费费用”的签名请求(实为
permit/Permit2
或订单签名)。
链上可验证:
攻击者在同一区块内用
permit + transferFrom
(或Permit2.transferFrom
)打包搬走资产。亦有 NFT 场景的
setApprovalForAll
后批量safeTransferFrom
。
假“修复授权/提高成功率”向导(Approval 流程伪装)
表现:
自称“解决交易失败/滑点/路由器错误”,引导用户“重新授权提高成功率”。
实际为对攻击合约的无限授权。
通报来源:
多家路由器/聚合器与安全公司在博客与社区公告中披露过此类“修复向导”型界面。
攻击共同点(可用于识别)
目的都是让你在“真实钱包”里点下授权/签名。
强调“不会花费费用/仅用于验证”的文案,掩盖
approve/permit/Permit2/ApprovalForAll
的效力。使用陌生或近似的域名与 Logo;证书主体模糊,隐私保护注册。
钱包弹窗中的关键字段往往被 UI 淡化:
spender/operator
为陌生地址或非官方合约额度为“无限(MAX_UINT)”或“全局(ForAll)”
EIP‑712 结构复杂,重要字段不显眼
通过多调用(
multicall
)将“授权+搬运”压缩为 1-2 笔链上交易,几乎不给撤销窗口。
可视化示例:假冒 WalletConnect 钓鱼攻击流程图
图 2:假冒 WalletConnect 钓鱼攻击流程图
2.地址使用混乱
表面“兼容”的根因:格式相似,但语义完全不同
地址/哈希的外观相似
EVM 地址、Aptos/Sui 地址、以及交易哈希常常都是
0x
开头、固定长度的十六进制字符串。钱包在“收款地址”输入框通常只做“格式校验”(长度、hex),只要形如
0x...
且长度对得上,就被视为“合法格式”。
语义完全不同
EVM 的
address
(20 字节)与tx hash
(32 字节)仅对 EVM 有意义。Aptos/Sui 的地址是链内通过各自规则计算的 32 字节标识,和 EVM 的哈希/地址没有可比性与可互转性。
把 EVM 的“看起来对”的字符串粘到 Aptos/Sui 的目标地址栏,节点照样会打包——但那只是把币打到“这个 32 字节值在 Aptos/Sui 链上的账户槽位”,与你在 EVM 的身份没有任何关系。结果就是链上记账成功、你却没有签名权,资金等同锁死。
常见误粘贴场景拆解
把 EVM 的
address
(20 bytes,经常显示成 40 hex →0x
+40)粘到 Aptos/Sui:有些钱包会“pad/左填充”为 32 bytes,或你自己粘的是一个 32 字节的值(比如某些合约地址或特殊展示方式),在 UI 上看起来刚好满足 Aptos/Sui 的 32 字节格式。
把 EVM 的
tx hash
(32 bytes →0x
+64 hex)粘到 Aptos/Sui:完整满足 Aptos/Sui 地址的“长度/hex”校验,更容易被当作“合法地址”。
一旦发出交易,Aptos/Sui 会把这串 32 字节当作“收款账户地址”的比特串,没有“跨链识别/解析”。
还能不能找回?现实路径评估
链上不可逆:没有私钥/认证权,就无法动用该地址上的资产。
是否存在“可控收款方”:
如果你把钱误转到了一个你自己可控的 Aptos/Sui 地址(极低概率),那还有机会。但通常不是。
如果是转到了中心化交易所(CEX)的链内地址,有极小概率可以走工单让他们人工找回(但跨链误转基本无解,CEX 一般也不支持)。
交易层面:
在 Aptos/Sui 浏览器上定位你的误转交易,确认落账地址是否有人活动、是否有合约逻辑能“回款”。大概率没有。
社工途径:
如果误转到的是某个公开项目/团队控制的合约或多签,且对方可证明控制该地址,有机会联系协助返还。但概率与成本都很高。
结论:绝大多数情况下,资金无法找回。能做的只有确认“一定无可控性”后,及时止损、优化流程,避免再次发生。
如何从源头避免踩坑
钱包与流程
强制“网络选择”:在钱包里明确选择网络(EVM/Aptos/Sui)后再生成收款码,避免跨网络复制。
地址白名单:给常用地址加“所属链”标签;对跨链地址禁用或额外二次确认。
小额试转:新对手方/新链/新钱包首次转账先试 1 美元等小额。
显示链标识的支付链接:使用支持 CAIP-2/CAIP-10 或链特定 URI 的链接/二维码。
正则校验增强:选用能识别“EVM tx hash ≠ Aptos/Sui address”的钱包/插件(或向钱包团队反馈)
6. 合约钱包安全风险与防御措施
6.1 威胁模型
核心风险不是“纯转账”,而是“你本人签过的授权/签名”,包括:
ERC20.approve(spender, amount)
→ 攻击者transferFrom
EIP-2612 permit
/ UniswapPermit2
→ 攻击者transferFrom
ERC721/1155.setApprovalForAll(operator, true)
→ 攻击者批量转移 NFTEIP-712 订单类签名(Seaport、Blur、LooksRare 等)→ 攻击者
fulfill
订单
伪装途径:假页面/App 充当“交互中间者”,诱导你用“真实钱包”点确认。
防御要点:识别与约束“授权/签名”的能力,缩短授权窗口,限制授权范围,建立实时发现与撤销通道。
6.2 用户与运营层防御(最小授权与链上体检)
6.2.1 最小授权策略(MOA,Minimum-Of-Authorization)
原则:
能用
permit
的“一次性小额+短时有效期”优先于“无限授权”对同一代币按“场景隔离地址”,避免单一地址持有所有可被扣划资产
落地建议:
USDC/USDT 等敏感代币:尽量不做无限授权,或使用“路由器授权≤实际交易额的 110%”
用完即撤销:使用
revoke.cash
或链上浏览器 Approvals 工具
6.2.2 周期性体检(自动化)也可以在慢雾官网扫描,更进一步提供定期扫描安检功能
每周/每日扫描账户授权并比对白名单,发现异常立即撤销:
代币授权:读取
allowance(owner, spender)
NFT 授权:
isApprovedForAll(owner, operator)
Permit2:查询
allowance(owner, token, spender)
或事件索引
示例(伪代码):
for (spender in known_spenders) {
const allowance = ERC20.allowance(owner, spender)
if (allowance > threshold && spender not in whitelist) alert(owner, spender, allowance)
}
6.2.3 行为习惯
只从官网发起连接;不扫未知二维码/不点
wc:
深链钱包弹窗核对三要素:
方法
、spender/operator
、额度/范围
对“不会花费费用/仅用于验证”的签名保持最高警惕
6.3 钱包端防御(UI/策略/签名门禁/解析)
钱包是“最后的刹车”。改进钱包能力能系统性降低误签概率。
6.3.1 风险方法高亮与二次确认
对以下方法强制红色高亮+二次确认:
approve
(额度≥MAX/大额)permit
/Permit2
(特别是无限或长期有效)setApprovalForAll
任意 EIP‑712 订单/权限签名(显示结构化字段)
策略示例:
if (method in ["eth_signTypedData_v4", "personal_sign"]) {
const parsed = parseEIP712(data)
if (isPermitLike(parsed) || isSeaportOrder(parsed)) {
showDangerUI(parsed.keyFields()) // spender/operator/offer/consideration/amount/deadline
requireSecondFactor() // 二次确认/长按/物理按键
}
}
6.3.2 EIP‑712 深度解析与字段映射
对常见协议类型内置解析器(EIP‑2612、Permit2、Seaport、OpenSea 订单等)
展示关键字段:
Permit:
owner
、spender
、value
、nonce
、deadline
、domain
Permit2:
token
、amount
、expiration
、spender
Seaport 订单:
offer
(谁付出什么)、consideration
(收到什么)、zone
、conduit
、counter
、start/end time
解析示例(TypeScript):
function isPermitLike(typed: EIP712Typed): boolean {
const names = typed.types["Permit"]?.map(f => f.name) ?? []
return ["owner","spender","value","deadline","nonce"].every(n => names.includes(n))
}
6.3.3 域名与合约信誉校验
交叉验证来源域名、合约地址与已知官方清单(签名前提示不匹配)
利用 onchain reputation:
检查
spender/operator
是否在黑名单或低信誉列表(新部署、短寿命、已关联攻击)检查合约 codehash 是否与已知路由器/市场合约匹配
6.3.4 硬件钱包与二因子
对高危方法强制使用硬件钱包物理确认,或要求生物/二因子确认
6.4 dApp 前端与后端防御(反钓鱼、会话、风控)
目标:减少用户面对“假页面/假会话”的机会,并在后端识别异常授权链路。
6.4.1 反钓鱼 UX 与域名策略
官方域名白名单与 HSTS、.well-known/assetlinks.json 验证
在前端明显位置显示“唯一官方域名”,并提供签名模板预览说明
使用内容安全策略(CSP)和子资源完整性(SRI)防注入
6.4.2 WalletConnect 会话风控
会话参数校验(链 ID、方法白名单、请求频控)
对首次连接的会话限制仅暴露必要方法;需要高危方法时逐步升级
在二维码/URI 中增加短期有效性与服务端签名校验(防换码)
6.4.3 交易与授权风控
服务端记录用户账户常用
spender/operator
列表,出现陌生高危地址时在前端阻断/二次确认对“无限授权”给出强烈反对建议与动态限额计算(基于用户行为)
引导使用“会过期的小额 Permit2”
6.5 智能合约侧防御(授权设计、限权模式、Permit 安全)
合约设计是“源头治理”。避免要求用户长期/无限授权,提供安全默认值。
6.5.1 避免长期无限授权
采用“按次授权”或“限额+过期”的授权模式(例如 Permit2 的限制签名)
若必须使用路由器,考虑引入“用户自定义限额”参数并默认小额
6.5.2 Pull → Push 权限转换
优先设计成“用户主动推送代币到合约(push)”,而不是“合约从用户拉取(pull)”
使用
msg.sender
作为唯一资金来源,禁止使用transferFrom(msg.sender != tx.origin)
的隐式扣划
示例(安全接收 Token):
function deposit(uint256 amount) external {
// 用户自行调用 token.transferFrom(msg.sender, address(this), amount)
// 或先在 UI 引导用户转账,再在合约侧记录 balance
}
6.5.3 授权最小化合约范式
合约维护 per-user 的限额映射与过期时间,操作时强制检查:
mapping(address => uint256) public limit;
mapping(address => uint64) public expiry;
function setLimit(uint256 newLimit, uint64 newExpiry) external {
limit[msg.sender] = newLimit;
expiry[msg.sender] = newExpiry;
}
function spend(address user, uint256 amount) internal {
require(block.timestamp <= expiry[user], "expired");
require(limit[user] >= amount, "exceed limit");
limit[user] -= amount;
}
6.5.4 EIP‑2612/Permit2 正确用法与安全检查
校验
deadline
不应过长;拒绝MAX_UINT
的长期有效对
nonce
连用场景要有防重放逻辑(permit 自带 nonce;业务侧也应记账)对 Permit2:
使用
PermitSingle
/PermitBatch
的expiration
字段,限制时间窗校验
sigDeadline
与details.nonce
(避免多次重放)
6.5.5 NFT 合约与市场
尽量避免要求
setApprovalForAll
;改为“按 tokenId/按批次”的限定授权对市场合约:对
conduit
/zone
的字节码哈希进行白名单校验
6.6 基础设施与监控(链上监控、撤销自动化、风控策略)
6.6.1 实时监控授权事件
订阅事件:
ERC‑20:
Approval(owner, spender, amount)
ERC‑721/1155:
ApprovalForAll(owner, operator, approved)
Permit2:
Approval(address owner, address token, address spender, ... )
或等效事件
策略:
非白名单
spender/operator
出现“大额/无限/ForAll”即触发告警告警后自动发起撤销交易(可选;需注意抢跑与 gas)
伪代码:
if (Approval.amount == MAX_UINT || ApprovalForAll.approved == true) {
if (!whitelist.contains(spender)) {
alert(owner, spender)
if (autoRevokeEnabled) submitRevoke(owner, token, spender)
}
}
6.6.2 模式识别
连续模式:授权后 ≤ N 秒内来自陌生地址的
transferFrom
大额出现 → 高危协议指纹:drainer 常见合约 bytecode 签名匹配、交易图谱(多调用、多跳清洗)
会话来源:关联用户连接的 referer/域名/IP 指纹,发现钓鱼源头
6.6.3 多签与金库隔离
重要资产放多签/金库;热钱包仅持操作资金
对高额转出设置时延与多重审批(时间锁 + 观察者告警)