请选择 进入手机版 | 继续访问电脑版
开启辅助访问
链路首页链路财经目前收录 币种 : 4908 交易所 : 310钱包 : 17 24H 交易量 : $43,403,137,051 总市值 : $245,388,183,835
2019
02/21
08:00
分享
评论
  • 未初始化存储指针


    EVM 既用 storage 来存储,也用 memory 来存储。强烈建议开发合约时弄懂存储的方式和函数局部变量的默认类型。因为不恰当地初始化变量可能产生有漏洞的合约。


    漏洞

    函数内的局部变量根据它们的类型默认用 storage 或 memory 存储。未初始化的局部 storage变量可能会指向合约中的其他意外存储变量,从而导致有意(即,开发人员故意将它们放在那里进行攻击)或无意的漏洞。

    我们来考虑以下相对简单的名称注册器合约:

    // A Locked Name Registrar
    contract NameRegistrar {

        bool public unlocked = false;  // registrar locked, no name updates

        struct NameRecord { // map hashes to addresses
            bytes32 name;  
            address mappedAddress;
        }

        mapping(address => NameRecord) public registeredNameRecord; // records who registered names 
        mapping(bytes32 => address) public resolve; // resolves hashes to addresses

        function register(bytes32 _name, address _mappedAddress) public {
            // set up the new NameRecord
            NameRecord newRecord;
            newRecord.name = _name;
            newRecord.mappedAddress = _mappedAddress; 

            resolve[_name] = _mappedAddress;
            registeredNameRecord[msg.sender] = newRecord; 

            require(unlocked); // only allow registrations if contract is unlocked
        }
    }


    这个简单的名称注册器只有一个功能。当合约是 unlocked 状态时,任何用户都可以注册一个名称(以 bytes32 哈希值的形式)并将该名称映射到地址。不幸的是,这个注册器一开始是被锁定的,并且在 [23] 行的 require 函数禁止 register() 添加姓名记录。但是,这个合约中存在一个漏洞,让用户可以不管 unlocked 运行注册器。

    为了讨论这个漏洞,首先我们需要了解存储(Storage)在 Solidity 中的工作方式。作为一个高度抽象的概述(没有任何适当的技术细节——我建议阅读 Solidity 文档以进行适当的审查),状态变量按它们出现在合约中的顺序存储在合约的 Slot 中(它们可以被组合在一起,但在本例中不可以,所以我们不用担心)。因此, unlocked 存在 slot 0 中, registeredNameRecord 存在 slot 1 中, resolve 在 slot 2 中,等等。这些 slot 的大小是 32 字节(映射会让事情更加复杂,但我们暂时忽略)。如果 unlocked 是 false ,其布尔值看起来会是 0x000...0(64 个 0,不包括 0x );如果是 true ,则其布尔值会是 0x000...1 (63 个 0)。正如你所看到的,在这个特殊的例子中,存储上存在着很大的浪费。

    我们需要的另一部分知识,是 Solidity 会在将复杂的数据类型,比如 structs ,初始化为局部变量时,默认使用 storage 来存储。因此,在 [16] 行中的 newRecord 默认为storage。合约的漏洞是由 newRecord 未初始化导致的。由于它默认为 storage,因此它成为指向 storage 的指针;并且由于它未初始化,它指向 slot 0(即 unlocked 的存储位置)。请注意,[17] 行和[18] 行中,我们将 _name 设为 nameRecord.name 、将 _mappedAddress 设为 nameRecord.mappedAddress 的操作,实际上改变了 slot 0 和 slot 1 的存储位置,也就是改变了 unlocked 和与 registeredNameRecord 相关联的 slot。

    这意味着我们可以通过 register() 函数的 bytes32 _name 参数直接修改 unlocked 。因此,如果 _name 的最后一个字节为非零,它将修改 slot 0 的最后一个字节并直接将 unlocked 转为 true 。就在我们将 unlocked 设置为 true 之时,这样的 _name 值将传入 [23] 行的 require() 函数。在Remix中试试这个。注意如果你的 _name 使用下面形式,函数会通过: 0x0000000000000000000000000000000000000000000000000000000000000001

    预防技术

    Solidity 编译器会在出现未经初始化的存储变量时发出警告,因此开发人员在构建智能合约时应小心注意这些警告。当前版本的 mist(0.10)不允许编译这些合约。在处理复杂类型时,明确使用 memory 或 storage 以保证合约行为符合预期一般是很好的做法。


    真实案例


    钓鱼:OpenAddressLottery 和 CryptoRoulette

    有人部署了一个名为 OpenAddressLottery(合约代码)的钓鱼合约,它使用未初始化的存储变量以从一些可能的黑客手上吊取 ether。


    同时欢迎进入我们知识星球社区;



    扫描下放二维码添加我,拉您进入技术交流群



主题帖 141 关注 0 粉丝 0
情感指数

链路大数据分析置信度 13.42 %

TA的主题帖
主题相关
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表