Web3 安全:攻击过程与检查清单
# Web3 安全:攻击过程与检查清单
先确定谁能改变资产与权限,再沿着每个外部输入和调用检查失败路径。安全不只发生在 Solidity 中,钱包签名、前端域名、RPC、索引器、管理员和发布流程都属于攻击面。
# 几类常见攻击怎样发生
| 风险 | 攻击过程 | 核心防线 |
|---|---|---|
| 重入 | 合约先向外转账,接收者回调,在余额扣除前再次提款 | 先验证、再改状态、最后外部交互;必要时加重入保护,并检查跨函数共享状态 |
| 越权 | 普通用户调用铸造、升级或提现等管理入口 | 每个入口显式鉴权,最小角色、多签、权限交接与测试 |
| 授权钓鱼 | 用户以为在登录,实际签署 Permit 或无限额度授权 | 清楚展示 spender、金额、链、期限,默认最小授权;签名域隔离与 nonce |
| 价格操纵 | 攻击者瞬间改变低流动性池价格,再利用该价格借款或结算 | 可靠预言机、时效校验、偏差限制与合理的时间窗口 |
| 三明治交易 | 观察用户 swap,在前后插入交易改变价格并获利 | 合理滑点和 deadline,必要时使用受保护提交路径;不能保证完全消除 MEV |
| 重放 | 同一签名在另一业务、链或时间再次使用 | 绑定域、链、合约、动作、nonce 与期限,消费 nonce |
| 跨链验证错误 | 目标链相信伪造、重复或尚未最终确认的源链消息 | 验证消息来源与最终性、目标链和接收者,消费唯一消息 ID,并限制桥的资产风险 |
| 资金永久锁定 | 某一方不操作、外部调用一直失败,流程没有出口 | 超时、退款、暂停和仲裁退出路径,不把所有进展依赖单一角色 |
闪电贷提供同一交易内借还的大额资金,本身不是漏洞;它会放大价格操纵、会计错误和治理设计缺陷。不能只用 “禁止闪电贷” 代替修正经济规则。
# 用提款过程理解重入
危险顺序是:检查余额 → 转账给用户 → 扣余额。转账不是简单赋值,接收合约可能执行自己的代码;如果它再次调用提款,此时余额仍未扣除。
Checks-Effects-Interactions(检查、修改状态、外部交互)要求先扣账再转账。转账失败时按业务规则回滚或记录可重试状态,同时检查其他函数能否看到不一致状态。重入锁只是额外防线,不能替代正确会计模型。
经典案例可从 Ethereum 对 DAO 重入事件的介绍 (opens new window) 理解调用顺序;复现只能在本地或授权环境,不对线上第三方合约尝试攻击。
# 重入防护要验证的不只是顺序
以一个记账余额 10 的用户为例,危险实现先发 10,再把余额归零。恶意接收者在收到资金时回调提款,仍看到余额 10,就可能重复提取。安全顺序先把可提余额更新,再调用外部地址;若外部转账失败且选择 revert,本次状态修改也一起撤销。
测试应让恶意接收者实际尝试再次调用,并断言累计收款不超过可提额度、其他用户余额不变、失败后账本与实际资产一致。还要尝试从退款、奖励等其他入口重入,因为只锁提款函数不一定保护共享会计状态。
对于批量付款,某个接收者持续拒收不应永远卡住所有人。可按业务改成用户分别领取的 pull-payment(主动领取)模式,隔离失败;但每次领取仍要校验额度、幂等和重入,并处理长时间不领取的业务规则。
# 预言机、滑点与 MEV 各自保护什么
预言机向合约提供外部价格;要验证数据来源、正值、精度和更新时间,并根据业务设异常偏差处理。直接把一个小池子的瞬时价格当抵押品估值,攻击者可能先推动池价,再借走超额资产。闪电贷只是提供临时资金,根因是错误信任了易操纵价格。
滑点保护限制本次成交比预期差多少。例如预期换得 100,设置最少收到 99,少于 99 应失败。这个门槛必须在合约执行中检查,只在页面显示没有约束力。deadline 限制操作的有效时间,也需要合约实际检查;它不能防止有效期内的抢跑。
MEV 是通过交易排序、包含或排除获得的价值。三明治攻击利用排序夹住用户交易;较紧滑点和受保护提交路径能减少部分风险,但过紧滑点也可能增加失败率。交易业务背景继续复用 CFD 与永续合约,不把开发侧检查当作完整交易风控。
# 管理员安全和用户退出怎样兼顾
多签降低单个密钥失窃风险,时间锁让高影响变更留出观察窗口,但参与者独立性、门槛和实际权限仍需核对。多签成员都由同一个人、同一台设备控制,不能等同于独立复核。
暂停入口要定义范围:可以暂停新存款和高风险执行,却不能不加区分地把所有退款永久锁死。退出是否安全取决于漏洞类型和账本状态,因此需要预先设计受控退出、修复和通知流程,而不是事故时临时给管理员无限转账权限。
# 上线前按资产流检查
# 合约与管理员
- 每条收款、退款、奖励和管理路径都校验权限、金额、单位和业务状态。
- token 白名单不仅检查地址,还明确不支持的转账扣费或余额变化行为。
- 外部转账、NFT 接收回调和预言机失败都经过测试,不使用
tx.origin鉴权。 - 升级、暂停和恢复权限分开,明确多签或时间锁的实际配置及紧急响应责任。
- 大额管理动作有延迟或复核机制;暂停不能无条件破坏用户的安全退出权。
# 钱包、网页与后端
- 钱包登录与支付授权分离,签名包含准确的域、网络、动作和期限。
- 前端不保存私钥,依赖和发布入口受保护;被劫持的页面可以诱导合法签名。
- 后端不信任用户上报金额、合约地址或 “交易已成功” ,重新验证链上事实。
- 公共 RPC 超时有恢复路径;索引具备补扫、幂等和重组处理。
- 数据库、消息、日志不包含助记词、私钥、签名密钥或完整敏感用户资料。
# 运维与事故处理
保存部署版本、管理员变更和关键业务审计记录。发现事故先确定范围、按预案限制受影响操作、保留证据,再修复或迁移。不要在没理解状态时盲目重放队列或批量退款。
# 安全测试应该覆盖反例
正常用户能买一次只是起点,还要测试:非管理员能否改价格,重复回调能否多发一次奖励,退款后是否还能交付,重组后是否重复记账,RPC 超时后重试是否再次扣款。每个反例都对应明确不变量,不能只以代码覆盖率代替业务安全。
已有项目检查表可继续复用 Web3 大学合约安全 和 Aladdin 链上托管。
# 面试时可以这样回答
怎么证明一个 Web3 项目比较安全?参考答案
我不会承诺绝对安全,而是说明我们保护的资产、权限和关键不变量,以及对应的证据。代码侧有权限、重入、失败和状态迁移测试;部署侧核对版本与管理员;运行侧监控异常资金变化,并具备暂停、退款或迁移流程。审计和成熟库能降低风险,但不能替代持续监控和业务级验证。
用了重入锁,资金就安全了吗?参考答案
重入锁只限制特定的嵌套执行,不解决越权、错误记账或价格操纵。我会先保证检查、改状态、外部交互的顺序正确,再检查跨函数共享状态,并用恶意接收合约测试累计提款和账实一致。即使没有重入,按请求金额记账却收到更少代币,也一样会产生资金缺口。
发现合约风险后,为什么不能直接升级并批量退款?参考答案
先要确定哪些状态和资金受影响,否则升级可能破坏旧数据,退款可能重复支付。我会按预案限制受影响入口、保存链上和部署证据,核对余额与负债,再经过测试和受控授权修复或迁移。恢复后继续对账,不把代码发布成功当成事故已经结束。