中文
铁叔

天地不仁 以万物为刍狗


  • 首页

  • 归档

  • 关于我

  • 公益404

  • 搜索

openzeppelin 版本4.1.0-4.3.1中UUPS proxy 存在致命漏洞

时间: 2021-10-31   |   分类: Defi   Solidity   安全   | 字数: 899 字 | 阅读: 2分钟 | 阅读次数:

openzeppelin 的Proxy 有两种:

  • 透明代理 Transparent proxy
  • UUPS proxy

两者的最主要的区别是, upgradeTo 函数逻辑的位置。在透明代理中, upgradeTo 函数逻辑在proxy合约中;而在 UUPS 代理中, upgradeTo 函数逻辑在实现合约中。

漏洞

openzeppelin 的代理合约通常都由几个合约组成。每个可升级的部署都包括一个实现合约,实现合约中是可升级合约的逻辑;一个代理合约,保存合约的状态(也就是存储)。当代理合约升级时,将代理合约的实现地址指向行的实现地址即可。

代理合约与实现

代理合约升级示意图:

代理合约升级

在版本4.1.0-4.3.1中,UUPSUpgradeable 合约的 upgradeTo 函数没有设置权限,任何人都可以调用该函数。因此,可以构造一个攻击合约,在攻击合约的 upgradeTo 中调用 SELFDESTRUCT, 然后调用实现合约的 upgradeTo, 参数为我们的攻击合约,这将导致实现合约销毁自己(这里很重要的一点是,调用 SELFDESTRUCT 不会导致失败!),因此,代理合约的逻辑代码被销毁,导致整个合约宕机!

之前版本的 upgradeTo 函数的实现:

    function upgradeTo(address newImplementation) external virtual {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallSecure(newImplementation, bytes(""), false);
    }

4.3.2 版本 upgradeTo 函数的实现:

    modifier onlyProxy() {
        require(address(this) != __self, "Function must be called through delegatecall");
        require(_getImplementation() == __self, "Function must be called through active proxy");
        _;
    }

    function upgradeTo(address newImplementation) external virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallSecure(newImplementation, new bytes(0), false);
    }

可以看到,现在实现合约的 upgradeTo 只有 proxy 合约可以调用。

UUPS 代理合约 upgradeTo 原理

UUPS合约的部署步骤如下:

首先,部署实现合约, 实现合约需继承合约 ERC1967UpgradeUpgradeable;

其次,部署 ERC1967Proxy 合约,在这个合约的初始化方法中,指定实现合约的地址为上一步部署的实现合约地址;

最后,调用 ERC1967Proxy 的 upgradeTo 函数。

upgradeTo 函数的代码:

    function upgradeTo(address newImplementation) external virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallSecure(newImplementation, new bytes(0), false);
    }

    function _upgradeToAndCallSecure(
        address newImplementation,
        bytes memory data,
        bool forceCall
    ) internal {
        address oldImplementation = _getImplementation();

        // Initial upgrade and setup call
        _setImplementation(newImplementation);
        if (data.length > 0 || forceCall) {
            _functionDelegateCall(newImplementation, data);
        }

        // Perform rollback test if not already in progress
        StorageSlotUpgradeable.BooleanSlot storage rollbackTesting = StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT);
        if (!rollbackTesting.value) {
            // Trigger rollback using upgradeTo from the new implementation
            rollbackTesting.value = true;
            _functionDelegateCall(
                newImplementation,
                abi.encodeWithSignature("upgradeTo(address)", oldImplementation)
            );
            rollbackTesting.value = false;
            // Check rollback was effective
            require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
            // Finally reset to the new implementation and log the upgrade
            _upgradeTo(newImplementation);
        }
    }

    function _upgradeTo(address newImplementation) internal {
        _setImplementation(newImplementation);
        emit Upgraded(newImplementation);
    }
    function _setImplementation(address newImplementation) private {
        require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract");
        StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
    }

其中, _authorizeUpgrade 是实现合约的权限验证,任何继承 ERC1967UpgradeUpgradeable 的合约都必须实现这个函数。这个函数通常是限制只有owner才能调用。

在 _upgradeToAndCallSecure 中,对新的实现合约执行了回滚测试 (rollbackTesting),也就是,执行新的执行合约升级逻辑,使proxy升级到现在的实现合约,以确保新的实现合约的 upgradeTo 没有问题,确保新的实现合约可以继续升级。

当上述回滚测试没有问题时,将 _IMPLEMENTATION_SLOT 的地址设置为新的实现地址。

https://forum.openzeppelin.com/t/uupsupgradeable-vulnerability-post-mortem/15680

#Solidity# #Proxy# #vulunerability# #openzeppelin#

声明:openzeppelin 版本4.1.0-4.3.1中UUPS proxy 存在致命漏洞

链接:https://guotie.github.io/post/contract-proxy/uups-proxy-vulunerability/

作者:铁叔

声明: 本博客文章除特别声明外,均采用 CC BY-NC-SA 3.0许可协议,转载请注明出处!

创作实属不易,如有帮助,那就打赏博主些许茶钱吧 ^_^
WeChat Pay

微信打赏

Alipay

支付宝打赏

hardhat solidity 常见错误
AAVE源代码分析 -- AAVE 部署及初始化
铁叔

铁叔

千里之行 始于足下

25 日志
14 分类
56 标签
GitHub twitter telegram email medium
标签云
  • Solidity
  • Defi
  • Aave
  • Compound
  • Abi
  • Dapp
  • Ethereum
  • Evm
  • Lend protocol
  • Lending
© 2010 - 2024 铁叔
Powered by - Hugo v0.119.0 / Theme by - NexT
/
0%