NodeJS全栈WEB3面试题——P6安全与最佳实践
🔐 6.1 如何防范重放攻击、私钥泄露、钓鱼签名?
✅ 重放攻击(Replay Attack)防范:
-
引入 nonce:每次登录或交易签名都携带唯一 nonce;
-
链 ID 检查:在签名中加入特定链 ID,防止跨链重放;
-
使用 EIP-712 签名结构:结构化签名防止签名滥用。
✅ 私钥泄露防范:
-
从不把私钥写入前端代码或硬盘明文保存;
-
后端钱包使用冷钱包或 HSM 服务(如 AWS KMS, Fireblocks);
-
使用环境变量加密存储助记词或私钥,部署中使用
.env + vault
; -
限制私钥操作权限,冷热钱包分离;
✅ 钓鱼签名防范:
-
签名前显示明确信息说明(如“登录授权而非转账”);
-
使用 EIP-712 结构化数据签名,避免用户签不明数据;
-
前端签名提示加上网站来源(如
yourapp.com
)+ 描述;
⚡ 6.2 什么是“前置运行攻击”(Front-running)?怎么防御?
✅ 定义:
Front-running 是攻击者监听 mempool,抢在用户交易之前提交相同或对其有利的交易(如抢先购买 NFT、调整价格、抢矿);
🛡️ 防御方式:
-
使用 commit-reveal 策略:
-
用户先提交加密数据(commit),后提交明文(reveal);
-
-
延迟执行 + 随机性机制:
-
引入链上随机数,避免固定执行顺序;
-
-
Flashbots:
-
将交易发送至 Flashbots 私有池,避免泄露到公共 mempool;
-
-
签名授权后端执行:
-
用户授权后由后端发送交易,缩短暴露时间窗口;
-
🎁 6.3 如果要做代币空投(Airdrop),你如何设计安全的分发策略?
✅ 目标:确保代币安全发放、避免滥领、节省 Gas。
📌 安全空投策略设计:
-
Merkle Tree 空投(常用):
-
构造用户地址+数量的 Merkle Root,存入合约;
-
用户自行调用
claim()
提供 Merkle proof; -
✅ 优点:一次部署即可支持大规模空投,节省 Gas;
-
✅ 防止重复领取,通过记录
claimed[address] = true
;
-
-
签名式空投:
-
后端为每个地址签名分发信息;
-
用户提交签名至合约,合约验证签名;
-
适合灵活分发、动态设置领取者;
-
-
Batch Transfer + 权限限制:
-
批量发送空投(
transferBatch()
); -
控制可操作账户,避免私钥被盗后滥发;
-
✅ 安全建议:
-
空投逻辑使用开源库(如 OpenZeppelin MerkleDistributor);
-
空投合约部署后不可修改 Merkle Root;
-
添加空投截止时间、防多次领取、防重入等措施;
🔁 6.4 合约升级有几种方式?各自的优缺点?
✅ 1. 代理合约(Proxy Pattern):
-
实现方式:使用
delegatecall
将调用转发至逻辑合约; -
典型模式:
-
Transparent Proxy(OpenZeppelin)
-
UUPS Proxy(更轻量,推荐)
-
模式 | 优点 | 缺点 |
---|---|---|
Transparent | 稳定,OpenZeppelin支持 | 存储布局要求严格,结构复杂 |
UUPS | 更节省 Gas,可自定义升级逻辑 | 升级函数自身易被攻击,需审慎授权 |
✅ 2. 数据迁移升级:
-
新部署一个合约,人工迁移数据;
-
✅ 优点:无 delegatecall 安全风险;
-
❌ 缺点:迁移成本高、用户需重新授权;
✅ 3. Eternal Storage 模式:
-
将状态变量独立放在一个固定合约;
-
易维护,但结构复杂、不推荐新项目使用;
🧨 6.5 哪些 Solidity 编码模式容易出安全漏洞?
🚨 常见风险编码模式:
漏洞类型 | 示例代码 / 描述 | 防御方式 |
---|---|---|
重入攻击 | 调用外部合约前修改状态(如提现) | Checks-Effects-Interactions;使用 ReentrancyGuard |
整数溢出/下溢 | uint256 a = b - c(未判断 b > c) | 使用 SafeMath 或启用 Solidity ≥0.8 自动检查 |
无访问控制 | 没有限制 setOwner() | 添加 onlyOwner 或 AccessControl |
调用未初始化合约地址 | 外部合约地址为空调用 | require 地址非 0 且合约存在 |
delegatecall 滥用 | 调用非可信合约逻辑代码 | 限制 delegatecall 使用,仅可信模块 |
Gas limit 不足 | 数组遍历转账 gas 用尽,状态不变 | 加限制、使用 pull 模式而非 push |
tx.origin 验证身份 | require(tx.origin == owner) 易被 phishing | 用 msg.sender 替代 tx.origin |
📌 总结建议:
-
使用 OpenZeppelin 工具、Hardhat 插件审计合约;
-
引入 CI 检查(如
slither
、mythx
); -
所有用户交互都应防御重入、钓鱼、溢出、权限越权;
-
升级合约需严格测试、控制 upgrade 权限;
-
尽量使用 audited 合约库,避免“自己写轮子”;