区块链Mar 28, 2026
Deep EVM #4:安全原语——msg.sender、访问控制和重入攻击
OS
Open Soft Team
Engineering Team
EVM层面的安全
智能合约安全不是在工作代码之上添加检查——而是理解EVM的执行模型如何创造攻击面。每次外部调用都是潜在的重入点。每次delegatecall都是存储劫持向量。每个未检查的返回值都是静默失败。
msg.sender vs tx.origin
这两个值看起来相似但具有根本不同的安全属性。
- msg.sender(CALLER操作码):当前执行上下文的直接调用者。每次CALL都会改变。
- tx.origin(ORIGIN操作码):发起交易的外部拥有账户(EOA)。永不改变。
tx.origin攻击
使用tx.origin进行认证是一个众所周知的漏洞。修复很简单:始终使用msg.sender,绝不使用tx.origin进行认证。
重入:DAO攻击模式
重入是最臭名昭著的智能合约漏洞——它导致了2016年6000万美元的DAO黑客事件。模式很简单:外部调用在状态更新完成之前将控制权返回给攻击者。
检查-效果-交互模式
标准防御是正确排序操作:
- 检查 — 验证所有条件(require语句)
- 效果 — 更新所有状态变量
- 交互 — 最后进行外部调用
function withdraw() external {
uint256 amount = balances[msg.sender]; // 检查
require(amount > 0, "No balance"); // 检查
balances[msg.sender] = 0; // 效果(在调用之前)
(bool success, ) = msg.sender.call{value: amount}(""); // 交互
require(success, "Transfer failed");
}
重入保护
abstract contract ReentrancyGuard {
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status = NOT_ENTERED;
modifier nonReentrant() {
require(_status != ENTERED, "ReentrancyGuard: reentrant call");
_status = ENTERED;
_;
_status = NOT_ENTERED;
}
}
DELEGATECALL:力量与危险
DELEGATECALL在调用合约的上下文中执行另一个合约的代码。被调用方的代码读写调用方的存储。这是代理模式的基础,但也极其危险。
存储碰撞攻击
当通过DELEGATECALL执行时,实现合约的变量写入代理的存储槽。攻击者可以将实现地址设置为自己的合约并接管代理。
修复:使用非结构化存储和伪随机槽(ERC-1967标准)。
访问控制模式
Ownable
最简单的模式:一个owner地址拥有特权访问。
基于角色的访问控制(RBAC)
OpenZeppelin的AccessControl提供多角色管理。
多签和时间锁
对高价值操作,结合RBAC与多签和时间锁。
MEV机器人的实用安全清单
- 绝不使用tx.origin — 使用msg.sender或基于签名的认证
- 处处使用CEI模式 — 在外部调用之前更新状态
- 检查所有返回值 — 特别是低级调用和非标准代币
- 使用访问控制 — 只有你的EOA/多签才能调用提取利润的函数
- 实现熔断器 — 检测到异常行为时暂停功能
总结
EVM中的安全不是你添加的层——而是你如何构建状态转换相对于外部调用的属性。检查-效果-交互模式、重入保护和谨慎的delegatecall使用不是最佳实践——它们是生存要求。