智能合约编程/Dapp漏洞 -- 未初始化的存储指针/Unintialised Storage Pointers
时间: 2019-03-21来源:OSCHINA
前景提要
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
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 } }

这个简单的名字登录合约只有一个函数。但合约被解锁以后,任何人都可以登录一个名字 (作为一个a bytes32的哈希),同时把名字映射到一个地址上。这个合约一开始是上锁的,而且第23行的require语句防止在上锁状态下的新增名字。但是在合约里有一个漏洞,允许合约在上锁状态下能进行名字登录。
为了讨论这个漏洞,我们必须要理解Storage是如何工作的。Storage槽是线性排布的,所以unlocked存在slot 0, registeredNameRecord 存在slot 1, resolve存在slot 2 依次类推。每个存储槽是32字节。Boolean类型变量unlocked 会被存储为0x000...0 (64个0)(如果是false)或者0x000...1(63个 0) (如果是true)
newRecord变量默认存储在Storage。漏洞在于newRecord没有被初始化。因为它默认是Storage,它就变成了一个指向Storage的指针。因为它没有被初始化,所以它指向slot 0 (unlocked存储的地方)。其后,程序设置nameRecord.name为_name,设置nameRecord.mappedAddress为_mappedAddress, 这两个赋值语句其实修改了slot 0和slot 1。实际上也修改了unlocked和registeredNameRecord所在的存储槽 。 这意味着unlocked可以在register()里被直接修改, 因而,如果_name的最后的字节不为零,它就将修改storage slot 0的最后一个字节,从而直接修改unlocked 为 true。我们可以在Remix里试一下,函数将会通过如果我们使用下面的参数的话_name: 0x0000000000000000000000000000000000000000000000000000000000000001
防护技术
Solidity编译器对未初始化的变量会推出一个警告。开发程序员要仔细的研读那些编译器产生的警告。显式的给变量加上memory或者storage修饰符是一个很好的习惯,以保证复杂类型按预想的运行。
pragma solidity ^0.4.18; contract HodlFraud { uint ownerAmount; uint numberOfPayouts; address owner; struct HoldRecord { uint amount; uint unlockTime; } mapping (address => HoldRecord) balance; function HodlFraud () public payable { owner = msg.sender; ownerAmount = msg.value; } function payIn(uint holdTime) public payable { require(msg.value > 0); HoldRecord newRecord; newRecord.amount += msg.value; newRecord.unlockTime = now + holdTime; balance[msg.sender] = newRecord; } function withdraw () public { require(balance[msg.sender].unlockTime < now && balance[msg.sender].amount > 0); msg.sender.transfer(balance[msg.sender].amount); balance[msg.sender].amount = 0; numberOfPayouts++; } function ownerWithdrawal () public { require(msg.sender == owner && ownerAmount > 0); msg.sender.transfer(ownerAmount); ownerAmount = 0; } }

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

热门排行