一、前言

分析了如此多的合约与攻击案例后,我发现随机数是经常出现的一个话题。在CTF题目中经常能见到随机数的预测。

以太坊作为数字货币的初始平台之一,已经在市面上进行了极广的普及。对于以太坊来说,其经常应用在ERC20、轮盘、彩票、游戏等应用中,并利用Solidity完成对合约的编写。作为区块链的应用,以太坊同样是去中心化的、透明的。所以许多赌博游戏、随机数预测等相关应用需要精心设计,否则就会产生危害。

本文详细的将以太坊中的随机数安全问题进行归类,并通过样例对各个类别的安全问题进行演示操作,方便读者进行进一步的分析解读。

二、随机数问题归类

我们在这里将随机数分类为四个大的方向。

  • 随机数使用区块中的公共变量作为随机数种子

  • 随机数使用过去的区块的区块哈希

  • 随机数结合哈希与私人设置的值作为种子

  • 随机数结合区块链机制而导致的安全问题

我将在下文中对这四类问题进行分析总结,并对合约进行演示讲解。

三、基于区块变量的随机数安全问题

根据有漏洞的合约以及常见的CTF题目,我们总结了几种被用于生产随机数的区块变量,如下:

  • now 该变量为当前时间戳信息。
contract test{
    event block(uint);
    function run() public{
        block(now);
    }
}

  • block.coinbase 代表挖当前区块的矿工地址

  • block.difficulty 表示这个区块的挖矿难度

  • block.gaslimit 表示交易中所限制的最大的gas值

  • block.number表示当前区块的高度

  • block.timestamp表示当前区块何时被挖出来的

这些区块变量可以被矿工进行计算,所以我们不能轻易的使用这些变量作为生成随机数的种子。并且,这些变量可以通过区块得到,当攻击者得到这些公共信息后,可以肆无忌惮的进行计算以达到预测随机数的效果。

下面我们看此类型的几个样例:

首先为一个轮盘类型的应用代码。

/**
 *Submitted for verification at Etherscan.io on 2016-06-28
*/

contract Lottery {
    event GetBet(uint betAmount, uint blockNumber, bool won); 

    struct Bet {
        uint betAmount;
        uint blockNumber;
        bool won;
    }

    address private organizer;
    Bet[] private bets;

    // Create a new lottery with numOfBets supported bets.
    function Lottery() {
        organizer = msg.sender;
    }

    // Fallback function returns ether
    function() {
        throw;
    }

    // Make a bet
    function makeBet() {
        // Won if block number is even
        // (note: this is a terrible source of randomness, please don't use this with real money)
        bool won = (block.number % 2) == 0; 

        // Record the bet with an event
        bets.push(Bet(msg.value, block.number, won));

        // Payout if the user won, otherwise take their money
        if(won) { 
            if(!msg.sender.send(msg.value)) {
                // Return ether to sender
                throw;
            } 
        }
    }

    // Get all bets that have been made
    function getBets() {
        if(msg.sender != organizer) { throw; }

        for (uint i = 0; i < bets.length; i++) {
            GetBet(bets[i].betAmount, bets[i].blockNumber, bets[i].won);
        }
    }

    // Suicide :(
    function destroy() {
        if(msg.sender != organizer) { throw; }

        suicide(organizer);
    }
}

该合约的关键点在makeBet()函数中。

// Make a bet
    function makeBet() {
        // Won if block number is even
        // (note: this is a terrible source of randomness, please don't use this with real money)
        bool won = (block.number % 2) == 0; 

        // Record the bet with an event
        bets.push(Bet(msg.value, block.number, won));

        // Payout if the user won, otherwise take their money
        if(won) { 
            if(!msg.sender.send(msg.value)) {
                // Return ether to sender
                throw;
            } 
        }
    }

在该函数中,用户会在调用该函数的同时获得一个won的bool变量,该变量通过对2进行取余操作来获取是否为true或者false。当won为基数的时候,合约向参与者进行赚钱。

然而这里的block.number可以进行预测,我们可以写攻击合约,当block.number满足条件时调用函数,当不满足的时候放弃执行该函数,这样就可以做到百分百命中。

第二个例子与block.timestamp有关。

/**
点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖