以太坊随机数安全全面分析(二)
字数 1528 2025-08-22 12:22:43

以太坊随机数安全全面分析(二)

一、前言

本文继续前文的讲解,深入分析以太坊智能合约中随机数生成的安全问题。在前文中我们介绍了区块链中由公开变量做种子而引起的安全问题,本文将详细介绍四种随机数漏洞类型及其利用方法。

二、基于区块哈希的随机数问题

2.1 基本问题描述

许多合约使用block.blockhash(block.number-1)作为生成随机数的变量。这种方法存在严重问题,因为攻击者可以调用相同的方法来预测或复制该随机数。

2.2 典型案例分析

LuckyDoubler合约示例

uint256 constant private FACTOR = 1157920892373161954235709850086879078532699846656405640394575840079131296399;

function rand(uint max) returns (uint256 result){
    uint256 factor = FACTOR * 100 / max;
    uint256 lastBlockNumber = block.number - 1;
    uint256 hashVal = uint256(block.blockhash(lastBlockNumber));
    return uint256((uint256(hashVal) / factor)) % max;
}

问题分析

  1. 使用block.number - 1获取上一个区块编号
  2. 通过block.blockhash()获取该区块的哈希值
  3. 使用FACTOR常数进行数学运算生成"随机数"

安全隐患

  • 区块信息是公开的,攻击者可以预先计算相同的随机数
  • 在交易执行前,攻击者可以预先运行相同的计算逻辑

三、未来区块哈希值的利用

3.1 方法描述

一些合约尝试使用未来区块哈希作为随机数源,步骤如下:

  1. 玩家下注,合约记录下注时的区块编号
  2. 稍后调用合约查询中奖号码
  3. 合约从存储中检索保存的block.number并获取其blockhash用于生成随机数

3.2 存在的问题

  1. 区块哈希存储限制

    • 以太坊只能保存最近的256个区块的哈希值
    • 超过256个区块的哈希值返回0
    • 如果第二次调用超过256个区块,随机种子变为0,可被预测
  2. 著名案例

    • SmartBillions彩票被黑,损失400 ETH
    • 原因是合约对block.number验证不充分

3.3 CTF示例分析

强网杯babybet题目

function bet(var arg0) {
    // ...省略部分代码...
    var0 = block.blockHash(block.number + ~0x00);
    var1 = var0 % 0x03;
    if (var1 != arg0) {
        // 处理失败逻辑
    } else {
        // 处理成功逻辑
    }
}

攻击方法

function process() public {
    target.profit();
    bytes32 guess = block.blockhash(block.number - 0x01);
    uint guess1 = uint(guess) % 0x03;
    target.bet(guess1);
}

四、私有种子作为哈希的安全问题

4.1 问题描述

一些合约尝试使用"私有"变量作为随机数生成的种子,例如:

bytes32 _a = block.blockhash(block.number - pointer);
for (uint i = 31; i >= 1; i--) {
    if ((uint8(_a[i]) >= 48) && (uint8(_a[i]) <= 57)) {
        return uint8(_a[i]) - 48;
    }
}

4.2 安全隐患

  1. 区块链数据本质上是公开透明的
  2. 所谓的private变量可以通过web3.js等方法读取
  3. 攻击者可以获取pointer的值并预测随机数

五、私有变量查看方法

5.1 基本方法

使用web3.js的getStorageAt函数可以读取合约存储中的任何数据,包括private变量。

示例合约

pragma solidity ^0.4.18;
contract Vault {
    bool public locked;
    bytes32 private password;
    // ...其他代码...
}

读取方法

web3.eth.getStorageAt("合约地址", 1, function(x, y) {
    console.warn(web3.toAscii(y))
});

5.2 存储布局测试

测试合约

pragma solidity ^0.4.10;
contract attack{
    uint private a;
    uint public b;
    uint private c;
    bool public locked;
    bool private locked1;
    // ...初始化代码...
}

存储布局

  1. 变量按照声明顺序存储
  2. 布尔值true存储为1,false存储为0
  3. private和public变量在存储上没有区别

六、Front-running攻击

6.1 攻击原理

  1. 矿工根据gas价格选择交易执行顺序
  2. gas价格高的交易优先执行
  3. 攻击者可以观察待处理交易池,针对特定交易发起更高gas价格的交易

6.2 应用场景

  1. 彩票合约

    • 观察oracle的随机数生成交易
    • 在oracle交易前插入更高gas的交易
    • 提前知道随机数结果并下注
  2. "Last is me!"游戏

    • 观察其他玩家的"坐下"交易
    • 在倒计时即将结束时发起更高gas的交易
    • 确保自己是最后一个"坐下"的玩家

6.3 著名案例

  1. ZeroNights ICO Hacking Contest中的题目
  2. Fomo3D等游戏合约中的类似问题

七、防御建议

  1. 避免使用区块变量(block.number, block.timestamp, blockhash)作为随机数源
  2. 使用链外随机数生成器(Oracle)如Chainlink VRF
  3. 采用承诺-揭示(commit-reveal)方案
  4. 对于必须使用链上随机数的情况,结合多个不可控因素
  5. 注意私有变量的实际可见性,不要依赖其保密性

八、参考链接

  1. LuckyDoubler合约
  2. SmartBillions合约
  3. ZerNights ICO竞赛分析
  4. 以太坊随机数预测
以太坊随机数安全全面分析(二) 一、前言 本文继续前文的讲解,深入分析以太坊智能合约中随机数生成的安全问题。在前文中我们介绍了区块链中由公开变量做种子而引起的安全问题,本文将详细介绍四种随机数漏洞类型及其利用方法。 二、基于区块哈希的随机数问题 2.1 基本问题描述 许多合约使用 block.blockhash(block.number-1) 作为生成随机数的变量。这种方法存在严重问题,因为攻击者可以调用相同的方法来预测或复制该随机数。 2.2 典型案例分析 LuckyDoubler合约示例 : 问题分析 : 使用 block.number - 1 获取上一个区块编号 通过 block.blockhash() 获取该区块的哈希值 使用FACTOR常数进行数学运算生成"随机数" 安全隐患 : 区块信息是公开的,攻击者可以预先计算相同的随机数 在交易执行前,攻击者可以预先运行相同的计算逻辑 三、未来区块哈希值的利用 3.1 方法描述 一些合约尝试使用未来区块哈希作为随机数源,步骤如下: 玩家下注,合约记录下注时的区块编号 稍后调用合约查询中奖号码 合约从存储中检索保存的block.number并获取其blockhash用于生成随机数 3.2 存在的问题 区块哈希存储限制 : 以太坊只能保存最近的256个区块的哈希值 超过256个区块的哈希值返回0 如果第二次调用超过256个区块,随机种子变为0,可被预测 著名案例 : SmartBillions彩票被黑,损失400 ETH 原因是合约对block.number验证不充分 3.3 CTF示例分析 强网杯babybet题目 : 攻击方法 : 四、私有种子作为哈希的安全问题 4.1 问题描述 一些合约尝试使用"私有"变量作为随机数生成的种子,例如: 4.2 安全隐患 区块链数据本质上是公开透明的 所谓的private变量可以通过web3.js等方法读取 攻击者可以获取pointer的值并预测随机数 五、私有变量查看方法 5.1 基本方法 使用web3.js的 getStorageAt 函数可以读取合约存储中的任何数据,包括private变量。 示例合约 : 读取方法 : 5.2 存储布局测试 测试合约 : 存储布局 : 变量按照声明顺序存储 布尔值true存储为1,false存储为0 private和public变量在存储上没有区别 六、Front-running攻击 6.1 攻击原理 矿工根据gas价格选择交易执行顺序 gas价格高的交易优先执行 攻击者可以观察待处理交易池,针对特定交易发起更高gas价格的交易 6.2 应用场景 彩票合约 : 观察oracle的随机数生成交易 在oracle交易前插入更高gas的交易 提前知道随机数结果并下注 "Last is me!"游戏 : 观察其他玩家的"坐下"交易 在倒计时即将结束时发起更高gas的交易 确保自己是最后一个"坐下"的玩家 6.3 著名案例 ZeroNights ICO Hacking Contest中的题目 Fomo3D等游戏合约中的类似问题 七、防御建议 避免使用区块变量(block.number, block.timestamp, blockhash)作为随机数源 使用链外随机数生成器(Oracle)如Chainlink VRF 采用承诺-揭示(commit-reveal)方案 对于必须使用链上随机数的情况,结合多个不可控因素 注意私有变量的实际可见性,不要依赖其保密性 八、参考链接 LuckyDoubler合约 SmartBillions合约 ZerNights ICO竞赛分析 以太坊随机数预测