SWC智能合约漏洞库

在线工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器

SWC-120/基于链属性的弱随机性

生成随机数的能力在各种应用中都非常有用。一个明显的例子是博彩DApp,其中使用伪随机数 生成器选择获胜者。但是,在以太坊中创建足够强大的随机性来源非常具有挑战性。例如, 使用block.timestamp是不安全的,因为矿工可以选择在几秒钟内提供任何时间戳,而仍然使 他的区块被其他人接受。使用blockhash、block.difficulty以及其他字段也是不安全的,因为 它们是由矿工控制。如果下注很高,那么该矿工可以在短时间内通过租用硬件来挖掘很多区块, 选择所需的区块哈希值,然后放弃所有其他区块。

CWE漏洞分类

CWE-330:使用弱随机值

整改方案

  • 使用commitment方案,例如RANDAO。
  • 通过预言机/oracle使用外部随机性源,例如Oraclize。请注意,此方法需要信任oracle, 因此使用多个oracle是合理的。
  • 使用比特币区块哈希,因为它们的开采成本更高。

参考文献

合约示例

guess_the_random_number.sol

/*
 * @source: https://capturetheether.com/challenges/lotteries/guess-the-random-number/
 * @author: Steve Marx
 */

pragma solidity ^0.4.21;

contract GuessTheRandomNumberChallenge {
    uint8 answer;

    function GuessTheRandomNumberChallenge() public payable {
        require(msg.value == 1 ether);
        answer = uint8(keccak256(block.blockhash(block.number - 1), now));
    }

    function isComplete() public view returns (bool) {
        return address(this).balance == 0;
    }

    function guess(uint8 n) public payable {
        require(msg.value == 1 ether);

        if (n == answer) {
            msg.sender.transfer(2 ether);
        }
    }
}

guess_the_random_number.yaml

description: Guess the number from a predictable on chain source
issues:
- id: SWC-120
  count: 1
  locations:
  - bytecode_offsets: {}
    line_numbers:
      guess_the_random_number.sol: [14]

guess_the_random_number_fixed.sol

/*
 * @source: https://capturetheether.com/challenges/lotteries/guess-the-random-number/
 * @author: Steve Marx
 */

pragma solidity ^0.4.25;

contract GuessTheRandomNumberChallenge {
    uint8 answer;
    uint8 commitedGuess;
    uint commitBlock;
    address guesser;

    function GuessTheRandomNumberChallenge() public payable {
        require(msg.value == 1 ether);
    }

    function isComplete() public view returns (bool) {
        return address(this).balance == 0;
    }

    //Guess the modulo of the blockhash 20 blocks from your guess
    function guess(uint8 _guess) public payable {
        require(msg.value == 1 ether);
        commitedGuess = _guess;
        commitBlock = block.number;
        guesser = msg.sender;
    }
    function recover() public {
      //This must be called after the guessed block and before commitBlock+20's blockhash is unrecoverable
      require(block.number > commitBlock + 20 && commitBlock+20 > block.number - 256);
      require(guesser == msg.sender);

      if(uint(blockhash(commitBlock+20)) == commitedGuess){
        msg.sender.transfer(2 ether);
      }
    }
}

guess_the_random_number_fixed.yaml

description: Guess the number from a predictable on chain source
issues:
- id: SWC-120
  count: 0
  locations: []

old_blockhash.sol

pragma solidity ^0.4.24;

//Based on the the Capture the Ether challange at https://capturetheether.com/challenges/lotteries/predict-the-block-hash/
//Note that while it seems to have a 1/2^256 chance you guess the right hash, actually blockhash returns zero for blocks numbers that are more than 256 blocks ago so you can guess zero and wait.
contract PredictTheBlockHashChallenge {

    struct guess{
      uint block;
      bytes32 guess;
    }

    mapping(address => guess) guesses;

    constructor() public payable {
        require(msg.value == 1 ether);
    }

    function lockInGuess(bytes32 hash) public payable {
        require(guesses[msg.sender].block == 0);
        require(msg.value == 1 ether);

        guesses[msg.sender].guess = hash;
        guesses[msg.sender].block  = block.number + 1;
    }

    function settle() public {
        require(block.number > guesses[msg.sender].block);

        bytes32 answer = blockhash(guesses[msg.sender].block);

        guesses[msg.sender].block = 0;
        if (guesses[msg.sender].guess == answer) {
            msg.sender.transfer(2 ether);
        }
    }
}

old_blockhash.yaml

description: Weak Sources of Randomness
issues:
- id: SWC-120
  count: 1
  locations:
  - bytecode_offsets:
      '0x80e6291d79fe0fafbb9c90d16e87a28fc8666591fb40a875ec974b95462002d1': [442]
    line_numbers:
      old_blockhash.sol: [29,33]

old_blockhash_fixed.sol

pragma solidity ^0.4.24;

//Based on the the Capture the Ether challange at https://capturetheether.com/challenges/lotteries/predict-the-block-hash/
//Note that while it seems to have a 1/2^256 chance you guess the right hash, actually blockhash returns zero for blocks numbers that are more than 256 blocks ago so you can guess zero and wait.
contract PredictTheBlockHashChallenge {

    struct guess{
      uint block;
      bytes32 guess;
    }

    mapping(address => guess) guesses;

    constructor() public payable {
        require(msg.value == 1 ether);
    }

    function lockInGuess(bytes32 hash) public payable {
        require(guesses[msg.sender].block == 0);
        require(msg.value == 1 ether);

        guesses[msg.sender].guess = hash;
        guesses[msg.sender].block  = block.number + 1;
    }

    function settle() public {
        require(block.number > guesses[msg.sender].block +10);
        //Note that this solution prevents the attack where blockhash(guesses[msg.sender].block) is zero
        //Also we add ten block cooldown period so that a minner cannot use foreknowlege of next blockhashes
        if(guesses[msg.sender].block - block.number < 256){
          bytes32 answer = blockhash(guesses[msg.sender].block);

          guesses[msg.sender].block = 0;
          if (guesses[msg.sender].guess == answer) {
              msg.sender.transfer(2 ether);
          }
        }
        else{
          revert("Sorry your lottery ticket has expired");
        }
    }
}

old_blockhash_fixed.yaml

description: Weak Sources of Randomness
issues:
- id: SWC-120
  count: 0
  locations: []

random_number_generator.sol

pragma solidity ^0.4.25;

// Based on TheRun contract deployed at 0xcac337492149bDB66b088bf5914beDfBf78cCC18.
contract RandomNumberGenerator {
  uint256 private salt =  block.timestamp;

  function random(uint max) view private returns (uint256 result) {
    // Get the best seed for randomness
    uint256 x = salt * 100 / max;
    uint256 y = salt * block.number / (salt % 5);
    uint256 seed = block.number / 3 + (salt % 300) + y;
    uint256 h = uint256(blockhash(seed));
    // Random number between 1 and max
    return uint256((h / x)) % max + 1;
  }
}

random_number_generator.yaml

description: Weak Sources of Randomness
issues:
- id: SWC-120
  count: 4
  locations:
  - bytecode_offsets: {}
    line_numbers:
      random_number_generator.sol: [5]
  - bytecode_offsets: {}
    line_numbers:
      random_number_generator.sol: [10]
  - bytecode_offsets: {}
    line_numbers:
      random_number_generator.sol: [11]
  - bytecode_offsets: {}
    line_numbers:
      random_number_generator.sol: [12]