博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
智能合约编程/Dapp漏洞 --Unexpected Ether
阅读量:6273 次
发布时间:2019-06-22

本文共 3970 字,大约阅读时间需要 13 分钟。

hot3.png

当ether被送到合约后,要么执行fallback函数或者执行合约里指定的其他函数。但是在智能合约中有2种异常情况。在这两种异常情况下,不用执行合约里的任何代码就可以操作合约里的ether。智能合约编程时,如果认为只有执行合约代码才能操作ether的话,就会导致合约收到攻击:ether可以被强制性的送到一个指定的合约。

 

攻击原理

在程序设计中,有一种通用的技术,即预先设置几个不变的状态,在相应的程序执行完后,再来确认状态没有发生变化。一个普通的例子就是ERC20 token标准里的totalSupply变量, 因为没有函数会去修改totalSupply,开发程序员可以在transfer()里加上一个检查:在确信totalSupply不变的前提下,保证程序的正常执行。

 

有一个不变的状态,特别容易被开发程序员用到,但是也特别容易被外部用户操纵。这个状态就是当前合约里的ether数量。通常,作为刚刚接触Solidity的程序员,往往会有一个迷思:只有合约里payable的函数才能发送和接受ether。这个迷思导致一个虚假,实际上是不正确的关于合约中ether数量的想定。最明显的带有漏洞的用法就是this.balance。像下面所显示的,对this.balance不当使用会导致很严重的漏洞。

 

在两种情况下,ether会被强制性的送给一个合约,既不会用到payable 修饰符也不会执行任何合约代码。下面我们具体讨论。

 

异常情况1:自毁/自杀(Self Destruct / Suicide

智能合约可以实现selfdestruct(address)函数,这个函数会删除合约地址里的所有二进制代码并且把合约里所有的ether送到一个可以指定参数的地址。如果指定的地址也是一个智能合约的话,就不会调用合约里任何的函数(包含fallback) 因而, selfdestruct()函数能被用来强制性的发送ether到任何合约,而不会执行合约里的任何代码:合约里的任何payable 函数都不会执行。这意味着:攻击者可以创建一个带有selfdestruct()的合约,同时发送ether给这个合约,调用selfdestruct(target),然后在强制性的发送ether给一个指定的合约。这篇文章有很详细的描述:

 

异常情况2:Pre-sent Ether

第二种方法是预导入(pre-load)带有ether的合约。合约地址是确定的:合约地址是基于创建合约地址的哈希以及创建合约的交易的nonce两个信息计算而来。

address = sha3(rlp.encode([account_address,transaction_nonce]))

这意味着,任何人都可以在合约被创建前计算合约的地址,并发送ether给那个地址。然后当合约真正被创建是,合约就会有一个非零的ether余额

 

我们可以通过下面一个简单的合约代码来看看如何利用上面的知识来找到合约里的漏洞。

 

EtherGame.sol

contract EtherGame {       uint public payoutMileStone1 = 3 ether;    uint public mileStone1Reward = 2 ether;    uint public payoutMileStone2 = 5 ether;    uint public mileStone2Reward = 3 ether;    uint public finalMileStone = 10 ether;    uint public finalReward = 5 ether;       mapping(address => uint) redeemableEther;    // users pay 0.5 ether. At specific milestones, credit their accounts    function play() public payable {        require(msg.value == 0.5 ether); // each play is 0.5 ether        uint currentBalance = this.balance + msg.value;        // ensure no players after the game as finished        require(currentBalance <= finalMileStone);        // if at a milestone credit the players account        if (currentBalance == payoutMileStone1) {            redeemableEther[msg.sender] += mileStone1Reward;        }        else if (currentBalance == payoutMileStone2) {            redeemableEther[msg.sender] += mileStone2Reward;        }        else if (currentBalance == finalMileStone ) {            redeemableEther[msg.sender] += finalReward;        }        return;    }       function claimReward() public {        // ensure the game is complete        require(this.balance == finalMileStone);        // ensure there is a reward to give        require(redeemableEther[msg.sender] > 0);        redeemableEther[msg.sender] = 0;        msg.sender.transfer(redeemableEther[msg.sender]);    } }

 

这个智能合约是一个简单的游戏:玩家发送0.5给合约,并争取成为第一个完成3个里程碑任务的人。里程碑任务是以ether标价的:完成第一个里程碑任务的第一个人(比如合约总额达到5 ether时的第一人),就会在游戏结束后拿回一部分的ether。当最终的里程碑任务(比如合约总额达到10ether时)达成时,游戏结束,相应的玩家拿到奖赏。

 

问题在于合约里这几行代码:

  …uint currentBalance = this.balance + msg.value;        // ensure no players after the game as finished        require(currentBalance <= finalMileStone);       …          require(this.balance == finalMileStone);        …

 

攻击者可以通过selfdestruct()来强制性的送小额的ether(比如0.1个ether)给合约,这样的话,所有将来的玩家都将不能达成里程碑任务。因为所有正规的玩家都只会发送0.5或者0.5倍数的ether。一旦合约接受了上面的0.1ether,this。balance将永远不会为0.5的倍数。所以下列的if条件永远都不会是true

…// if at a milestone credit the players account        if (currentBalance == payoutMileStone1) {            redeemableEther[msg.sender] += mileStone1Reward;        }        else if (currentBalance == payoutMileStone2) {            redeemableEther[msg.sender] += mileStone2Reward;        }        else if (currentBalance == finalMileStone ) {            redeemableEther[msg.sender] += finalReward;        }         …

 

更要命的是,如果一个报复心很强的攻击者,如果错过了一个里程碑任务的话,他可以强制性的送10 ether (或者让合约余额超过finalMileStone数目的ether),这就将永远锁住合约里的所有奖励。这是因为claimReward()函数会因为下面的require语句总是回退(revert) (this.balance 总是大于 finalMileStone).

// ensure the game is complete        require(this.balance == finalMileStone);

 

 

转载于:https://my.oschina.net/gavinzheng731/blog/2998693

你可能感兴趣的文章
spring boot集成mongodb最简单版
查看>>
DELL EqualLogic PS存储数据恢复全过程整理
查看>>
《Node.js入门经典》一2.3 安装模块
查看>>
《Java 开发从入门到精通》—— 2.5 技术解惑
查看>>
Linux 性能诊断 perf使用指南
查看>>
实操分享:看看小白我如何第一次搭建阿里云windows服务器(Tomcat+Mysql)
查看>>
Sphinx 配置文件说明
查看>>
数据结构实践——顺序表应用
查看>>
python2.7 之centos7 安装 pip, Scrapy
查看>>
机智云开源框架初始化顺序
查看>>
Spark修炼之道(进阶篇)——Spark入门到精通:第五节 Spark编程模型(二)
查看>>
一线架构师实践指南:云时代下双活零切换的七大关键点
查看>>
ART世界探险(19) - 优化编译器的编译流程
查看>>
玩转Edas应用部署
查看>>
music-音符与常用记号
查看>>
sql操作命令
查看>>
zip 数据压缩
查看>>
Python爬虫学习系列教程
查看>>
【数据库优化专题】MySQL视图优化(二)
查看>>
【转载】每个程序员都应该学习使用Python或Ruby
查看>>