1. 链一财经首页
  2. 资讯

TRON智能合约安全小课堂 | 第六期:结合示例漏洞合约,回顾前两期内容

欢迎来到

TRON智能合约安全小课堂

 

波场面向社区

征集DApp 的开源代码

结合其合约源码,以实战的方式

讲解波场智能合约开发时,需要注意的一些安全细节

本期小课堂结合一个示例漏洞合约,对前两期的内容做一个回顾。同时, 欢迎大家关注 @tron官方 twitter,踊跃投稿合约代码。

下面是从https://troneye.com (以下简称 TRON-Eye)查询到的,本次的合约代码。TRON-Eye 是来自社区的波场合约验证平台,之前的小课堂已经对 TRON-Eye 验证平台进行了详细的介绍。本次的合约是RouletteV4(地址: https://troneye.com/reveal?address=TYEGUsZKYuYRQtGjq7pMTKLJMqddnmoWpA)。 

TRON智能合约安全小课堂 | 第六期:结合示例漏洞合约,回顾前两期内容

图1 RouletteV4的合约源码


RouletteV4合约的代码很简短,核心代码如下。

1. contract RouletteV4 is Owner {

 

2.     uint256 public lastPlayed;

3.     uint256 public secretNumber;

4.     uint256 public lastBet;

5.     uint256 public betPrice = 1000;

6.     address public owner = msg.sender;

7. 

8.     struct Game {

9.         uint256 number;

10.         uint256 secret;

11.         address player;

12.     }

13. 

14.     Game[] public gamesPlayed;

15. 

16.     constructor() public {

17.         lastPlayed = now;

18.         shuffle();

19. }

20. 

21.     function play(uint256 number) payable public {

22.         require(msg.value >= betPrice && number <= 10);

23.         lastPlayed = now;

24.         

25.         Game game;

26.         game.secret = secretNumber;

27.         game.player = msg.sender;

28.         game.number = number;

29.         gamesPlayed.push(game);

30. 

31.         if (number == secretNumber) {

32.             // win!

33.             msg.sender.transfer(address(this).balance);

34.         }

35.         lastBet = msg.value;

36.         

37.         shuffle();

38.     }

39. 

40.     function kill() public {

41.         require(owner == msg.sender && now > lastPlayed + 1 days);

42.         selfdestruct(msg.sender);

43.     }

这个合约仍然是轮盘赌,合约初始化的时候, 设置了一个 1-20 的随机数:secretNumber,玩家通过调用 play() 去尝试竞猜这个数字,并设置一个新的secretNumber。为了方便演示,合约的secretNumber设置为 public,同时提供了多个查询函数。

该合约初步看起来,玩家有50%的概率猜对,只要猜对,用户就能取走合约的所有 trx;同时在lastPlayed + 1 days 时间内,合约创建者不能 kill合约。是这样吗?

No!事实上,如果你去玩这个合约的话,无论玩多少次,你永远也赢不了。你的每次投注都累计在合约里。另外,合约的创建者随时都可以 kill 合约。

为什么?

根据我们上一期所讲的,如下4行会覆盖全局的第0个~第2个变量的值。

25.       Game game;

26.       game.secret = secretNumber;

27.       game.player = msg.sender;

28.       game.number = number;

现在的关键就是,被覆盖的变量究竟是哪些。

根据上上一期所讲,solidity 的继承其实是代码拷贝,也就是说合约RouletteV4的第0个变量是 Owner 合约里的 owner,而不是自己的 lastPlayed。

讲到这里,就很容易理解,为何玩家永远赢不了,因为第27行在比对前,将secretNumber 被修改为了msg.sender。同时,它很巧妙的用26行将lastPlayed修改为了上一次的 secretNumber。等等,你肯定会问,不是说secretNumber是不对的么,而且 owner 不是会被第28行覆盖为 number 么,那么为何合约的部署者能随时kill 合约呢?

这是因为:

1. 第37行又生成了一个新的,取值范围为 [0-20)的secretNumber,这个secretNumber被下一次 play 的第26行用到;

2. 由于RouletteV4继承自 Owner,两个合约都有 owner 变量,但是RouletteV4在41行用到的 owner 是自己 第6行的 owner。

可以看到,任意玩家玩一次之后,成功的将 lastPlayed修改为一个20以内的数字。那么就能够理解为何第41行的 require() 不会 revert了。

稍微延展一下:

1. 如果调换一下第26和27行代码的先后顺序,那么合约创建者还能不能 kill 合约?

2. 第9-11行调换顺序,会导致什么样的结果?

3. 完全相同的代码,如果去掉 is Owner 的继承,又会导致什么结果呢?

欢迎关注twitter,你来告诉我答案。

本次小课堂先讲到这里。感谢TRON-Eye 提供合约验证工具。有关他们的更多信息可以直接访问他们的官网https://troneye.com。

我们继续征集后续课堂的范例源码, 非常欢迎关注我们的官方 twitter 踊跃投稿。

根据国家《关于防范代币发行融资风险的公告》,大家应警惕代币发行融资与交易的风险隐患。

本文来自LIANYI转载,不代表链一财经立场,转载请联系原作者。

发表评论

登录后才能评论