跳到主要内容
区块链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黑客事件。模式很简单:外部调用在状态更新完成之前将控制权返回给攻击者。

检查-效果-交互模式

标准防御是正确排序操作:

  1. 检查 — 验证所有条件(require语句)
  2. 效果 — 更新所有状态变量
  3. 交互 — 最后进行外部调用
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机器人的实用安全清单

  1. 绝不使用tx.origin — 使用msg.sender或基于签名的认证
  2. 处处使用CEI模式 — 在外部调用之前更新状态
  3. 检查所有返回值 — 特别是低级调用和非标准代币
  4. 使用访问控制 — 只有你的EOA/多签才能调用提取利润的函数
  5. 实现熔断器 — 检测到异常行为时暂停功能

总结

EVM中的安全不是你添加的层——而是你如何构建状态转换相对于外部调用的属性。检查-效果-交互模式、重入保护和谨慎的delegatecall使用不是最佳实践——它们是生存要求。