Solidity 合约安全,常见漏洞(第三篇)

2023-11-15

Solidity 合约安全,常见漏洞(第三篇)

ERC20 代币问题

如果你只处理受信任的 ERC20 代币,这些问题大多不适用。然而,当与任意的或部分不受信任的 ERC20 代币交互时,就有一些需要注意的地方。

ERC20:转账扣费

当与不信任的代币打交道时,你不应该认为你的余额一定会增加那么多。一个 ERC20 代币有可能这样实现它的转账函数,如下所示:

contract ERC20 {

  // internally called by transfer() and transferFrom()
  // balance and approval checks happen in the caller
  function _transfer(address from, address to, uint256 amount) internal returns (bool) {
    fee = amount * 100 / 99;

    balanceOf[from] -= to;
    balanceOf[to] += (amount - fee);

    balanceOf[TREASURY] += fee;

    emit Transfer(msg.sender, to, (amount - fee));
    return true;
  }
}

这种代币对每笔交易都会征收 1%的税。因此,如果一个智能合约与该代币进行如下交互,我们将得到意想不到的回退或资产被盗。

contract Stake {

  mapping(address => uint256) public balancesInContract;

  function stake(uint256 amount) public {
    token.transferFrom(msg.sender, address(this), amount);
    balancesInContract[msg.sender] += amount; //  这是错误的
  }

  function unstake() public {
    uint256 toSend = balancesInContract[msg.sender];
    delete balancesInContract[msg.sender];

    // this could revert because toSend is 1% greater than
    // the amount in the contract. Otherwise, 1% will be "stolen"// from other depositors.
    token.transfer(msg.sender, toSend);
  }
}

ERC20: rebase 的代币

Rebasing 代币由 Olympus DAO 的 sOhm 代币 和 Ampleforth 的 AMPL 代币所推广。Coingecko 维护了一个 Rebasing ERC20 代币的列表
当一个代币回溯时,总发行量会发生变化,每个人的余额会根据回溯的方向而增加或减少。
在处理 rebase 代币时,以下代码可能会被破坏:

contract WillBreak {
  mapping(address => uint256) public balanceHeld;
  IERC20 private rebasingToken

  function deposit(uint256 amount) external {
    balanceHeld[msg.sender] = amount;
    rebasingToken.transferFrom(msg.sender, address(this), amount);
  }

  function withdraw() external {
    amount = balanceHeld[msg.sender];
    delete balanceHeld[msg.sender];

    // 错误, amount 也许会超出转出范围
    rebasingToken.transfer(msg.sender, amount);
  }
}

许多合约的解决方案是简单地不允许 rebase 代币。然而,我们可以修改上面的代码,在将账户余额转给接受者之前检查 balanceOf(address(this))。那么,即使余额发生变化,它仍然可以工作。

ERC20: ERC777 在 ERC20 上的包裹

ERC20,如果按照标准实现,ERC20 代币没有转账钩子(hook),因此 transfer 和 transferFrom 不会有重入问题。
带有转账钩子的代币有应用优势,这就是为什么所有的 NFT 标准都实现了它们,以及为什么 ERC777 被最终确定。然而,这已经引起了足够的混乱,以至于 Openzeppelin 废止了 ERC777 库。
如果你只想让你的协议与那些行为像 ERC20 代币但有转账 hook 的代币兼容,那么这只是一个简单的问题,把 transfer 和 transferFrom 函数当作它们会向接收者进行一个函数调用即可。
这种 ERC777 的重入发生在 Uniswap 身上(如果你好奇,Openzeppelin 在这里记录了这个漏洞)。

ERC20: 不是所有的 ERC20 代币转账都会返回 true

ERC20 规范规定,ERC20 代币在转账成功时必须返回 true。因为大多数 ERC20 的实现不可能失败,除非授权不足或转账的金额太多,大多数开发者已经习惯于忽略 ERC20 代币的返回值,并假设一个失败的 trasfer 将被回退。
坦率地说,如果你只与一个你知道其行为的受信任的 ERC20 代币打交道,这并不重要。但在处理任意的 ERC20 代币时,必须考虑到这种行为上的差异。
在许多合约中都有一个隐含的期望,即失败的转账应该总是回退,而不是返回错误,因为大多数 ERC20 代币没有返回错误的机制,所以这导致了很多混乱。
使这个问题更加复杂的是,一些 ERC20 代币并不遵循返回 true 的协议,特别是 Tether。一些代币在转账失败后会回退,这将导致回退的结果冒泡到调用者。因此,一些库包裹了 ERC20 代币的转账调用,以回退恢复并返回一个布尔值。下面是一些实现方法:
参考:Openzeppelin SafeTransferSolady SafeTransfer (大大地提高了 Gas 效率)

ERC20: 地址投毒

这不是一个智能合约的漏洞,但为了完整起见,我们在这里提到它。
转账零代币是 ERC20 规范所允许的。这可能会导致前端应用程序的混乱,并可能欺骗用户,让他们错误的以为他们最近将代币发送给了某地址。Metamask在这个线程中有更多关于这个问题的内容。

ERC20: 查看代码,规避跑路

(在 web3 术语中,“rugged"意味着’'跑路”, 直译是"从你脚下拉出地毯" 。)
没有什么能阻止有人在 ERC20 代币上添加函数,让他们随意创建、转账和销毁代币–或自毁或升级。所以从根本上说,ERC20 代币的 “无需信任” 程度是有限制的。

借贷协议中的逻辑错误

当考虑到基于 DeFi 协议的借贷如何被破坏时,考虑在软件层面传播的 bug 并影响商业逻辑层面是很有帮助的。形成和完成一个债券合约有很多步骤。这里有一些需要考虑的攻击向量。

贷款人损失的方式

  • 使到期本金减少(可能为零)而不进行任何支付的漏洞。
  • 当贷款没有偿还或抵押物降到阈值以下时,买方的抵押物不能被清算。
  • 如果协议有一个转移债务所有权的机制,这可能是一个从贷款人那里偷取债券的方式。
  • 贷款本金或付款的到期日被不适当地移到以后的日期。

借款人损失的方式

  • 偿还本金时没有减少本金债务的 bug。
  • 一个 bug 或 gas 攻击使用户无法进行支付。
  • 本金或利率被非法提高。
  • 预言机的操纵导致抵押物贬值。
  • 贷款本金或付款的到期日被不适当地移到一个较早的日期。

如果抵押品从协议中被抽走,那么贷款人和借款人都会损失,因为借款人没有动力去偿还贷款,而借款人则会损失本金。
正如上面所看到的,DeFi 协议被 "黑 "的范围比从协议中抽走一堆钱(通常成为新闻的那类事件)要多得多。

抵押(staking)协议中的漏洞

成为新闻的那种黑客是抵押协议被黑掉数百万美元,但这并不是唯一要面对的问题,抵押协议可能面临的问题有:

  • 奖励能否延迟支付,或过早地被索取?
  • 奖励能否被不适当地减少或增加?在更糟糕的情况下,能否阻止用户获得任何奖励?
  • 人们能否索取不属于他们的本金或奖励,在最坏的情况下,会耗尽协议所有资金?
  • 存放的资产会不会被卡在协议中(部分或全部),或被不适当地延迟提取?
  • 相反,如果质押需要时间承诺,用户是否可以在承诺时间之前提取?
  • 如果支付的是不同的资产或货币,其价值是否可以在相关的智能合约范围内被操纵?如果协议 mint 自己的代币来奖励流动性提供者或质押者,这一点是相关的。
  • 如果存在预期和披露出的本金损失的风险因素,这种风险是否可以被不适当地操纵?
  • 协议的关键参数是否有管理、中心化或治理风险?

需要关注的关键是代码中涉及 "资金退出 "部分的代码。
还有一个 "资金入口 "的漏洞也要寻找。

  • 有权参与协议中的资产抵押的用户能否被不适当地阻止?

用户收到的奖励有一个隐含的风险回报和一个预期的资金时间价值。明确这些假设是什么,以及协议会怎样偏离预期是很有帮助的。

未检查的返回值

有两种方法来调用外部智能合约:1)用接口定义调用函数;2)使用.call 方法。如下图所示:

contract A {
  uint256 public x;

  function setx(uint256 _x) external {
    require(_x > 10, "x must be bigger than 10");
    x = _x;
  }
}

interface IA {
  function setx(uint256 _x) external;
}

contract B {
  function setXV1(IA a, uint256 _x) external {
    a.setx(_x);
  }

  function setXV2(address a, uint256 _x) external {
    (bool success, ) =
    a.call(abi.encodeWithSignature("setx(uint256)", _x));
    // success is not checked!
  }
}

在合约 B 中,如果 _x 小于 10,setXV2 会默默地失败。当一个函数通过.call 方法被调用时,被调用者可以回退,但父函数不会回退。必须检查返回成功的值,并且代码行为必须相应地分支。

msg.value 在一个循环中

在循环中使用 msg.value 是很危险的,因为这可能会让发起者 重复使用 msg.value。
这种情况可能会出现在 payable 的 multicalls 中。Multicalls 使用户能够提交一个交易列表,以避免重复支付 21,000 的 Gas 交易费。然而,msg.value 在通过函数循环执行时被 “重复使用”,有可能使用户双花。
这就是Opyn Hack的根本原因。

私有变量

私有变量在区块链上仍然是可见的,所以敏感信息不应该被存储在那里。如果它们不能被访问,验证者如何能够处理取决于其值的交易?私有变量不能从外部的 Solidity 合约中读取,但它们可以使用以太坊客户端在链外读取。
要读取一个变量,你需要知道它的存储槽。在下面的例子中,myPrivateVar 的存储槽是 0。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract PrivateVarExample {
  uint256 private myPrivateVar;

  constructor(uint256 _initialValue) {
    myPrivateVar = _initialValue;
  }
}

下面是读取已部署的智能合约的私有变量的 javascript 代码

const Web3 = require("web3");
const PRIVATE_VAR_EXAMPLE_ADDRESS = "0x123..."; // Replace with your contract address

async function readPrivateVar() {
  const web3 = new Web3("http://localhost:8545"); // Replace with your provider's URL

  // Read storage slot 0 (where 'myPrivateVar' is stored)
  const storageSlot = 0;
  const privateVarValue = await web3.eth.getStorageAt(
    PRIVATE_VAR_EXAMPLE_ADDRESS,
    storageSlot
  );

  console.log("Value of private variable 'myPrivateVar':",
              web3.utils.hexToNumberString(privateVarValue));
}

readPrivateVar();

不安全的代理调用

委托调用(Delegatecall)不应该被用于不受信任的合约,因为它把所有的控制权都交给了委托接受者。在这个例子中,不受信任的合约偷走了合约中所有的以太币。

contract UntrustedDelegateCall {
  constructor() payable {
    require(msg.value == 1 ether);
  }

  function doDelegateCall(address _delegate, bytes calldata data) public {
    (bool ok, ) = _delegate.delegatecall(data);
    require(ok, "delegatecall failed");
  }

}

contract StealEther {
  function steal() public {
    // you could also selfdestruct here
    // if you really wanted to be mean
    (bool ok,) =
    tx.origin.call{value: address(this).balance}("");
    require(ok);
  }

  function attack(address victim) public {
    UntrustedDelegateCall(victim).doDelegateCall(
      address(this),
      abi.encodeWithSignature("steal()"));
  }
}

升级与代理有关的 bug

我们无法在一个章节中对这个话题进行公正的解释。大多数升级错误通常可以通过使用 Openzeppelin 的hardhat 插件和阅读它所保护的问题来避免出错。
作为一个快速的总结,以下是与智能合约升级有关的问题:

  • 自毁(self-destruct)和委托调用(delegatecall)不应该在执行合约中使用。
  • 必须注意在升级过程中,存储变量不能相互覆盖
  • 在执行合约中应避免调用外部库,因为不可能预测它们会如何影响存储访问。
  • 部署者决不能忽视调用初始化函数
  • 在基类合约中没有包括间隙(gap)变量,以防止在基类合约中加入新的变量时发生存储碰撞(这由 hardhat 插件自动处理)。
  • 不可变(immutable)变量中的值在升级时不会被保留
  • 非常不鼓励在构造函数中做任何事情,因为未来的升级必须执行相同的构造函数逻辑以保持兼容性。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Solidity 合约安全,常见漏洞(第三篇) 的相关文章

  • arcgis10之获取面要素中心点坐标

    第一步 获取中心的文件 第二步 新建两个存储中心点做坐标的字段 第三步 计算要要素中心的xy坐标 同理 计算中心点Y坐标即可
  • 定义表单规则(判断两次密码输入是否一致)

    主要代码 required true validator validateRepassword trigger change export default name form data var validateRepassword rule
  • 微信小程序入门-随机人脸生成

    微信小程序入门 随机人脸生成 开发背景 工具准备 微信小程序开发过程 开发背景 this person does not exist 这个网站起源于英伟达公司研究人员们打造的AI机器人 其能够生成随机且极为逼真的人脸图像 而实际上这些人脸并
  • 转:Cookie详解

    没怎么坐过客户端相关的工作 所以写爬虫的时候 很多概念都很模糊 学习起来很困难 现在想攻坚一下 所以找了一下cookies相关的内容 HTTP cookies 通常又称作 cookies 早期Web开发面临的最大问题之一是如何管理状态 服务
  • 关于“Could not open ServletContext resource [/WEB-INF/applicationContext.xml]”解决方案

    问题产生 最近学了Maven 并尝试将以前的项目 springmvc myabtis 重构成Maven项目 Maven项目推荐各种资源文件都放在src java resources目录下 所以我自然把spring的配置文件 包括spring
  • blender模型和材质导入UE4的工作流

    UE4设置 打开UE4 设置 gt 插件 搜索script 启用 然后编辑 gt 项目设置 找到python 是否远程执行打勾 Blender设置 然后下载Blender to UE4的插件 作者地址https github com ana
  • ios的input点击时有阴影

    最近做项目时遇到一个问题 就是h5页面 在ios浏览器中打开的时候 上面的input框点击的时候会出现一片阴影 然后一闪而过 对功能没什么阴影 就是不太美观 经过试验发现 当input使用默认边框时 也会有闪 因为有边框的原故 闪的不太明显

随机推荐

  • Springboot前后端数据传递的序列化数据格式:json字符串

    Springboot进行前后端数据传递格式json字符串的简单理解 1 对象的序列化和反序列化都什么时候用 当你想把内存中的对象保存到磁盘上的文件或者数据库中时 当你想用套接字在网络上传送对象时 当你想通过RMI传输对象时就牵扯到对象的序列
  • Java Scanner类中next()和nextLine()的区别

    next next 一定要读取到有效字符后才可以结束输入 对输入有效字符之前遇到的空格键 Tab键或Enter键等结束符 next 方法会自动将其去掉 只有在输入有效字符之后 next 方法才将其后输入的空格键 Tab键或Enter键等视为
  • Windows 安装 RabbitMq 和 Erlang

    1 安装Erlang 音乐RabbitMq是基于Erlang开发的 所以先要安装这个环境 下载地址 32位 64位 其他版本自己找 官网 下载完之后无脑安装直接一直下一步 2 配置Erlang环境变量 2 1 新建ERLANG HOME 把
  • unity如何解决协程开启频繁导致的程序卡顿

    unity如何解决协程开启频繁导致的程序卡顿 一 协程 协程并不会在Unity中开辟新的线程来执行 其执行仍然发生在主线程中 当我们有较为耗时的操作时 可以将该操作分散到几帧或者几秒内完成 而不用在一帧内等这个操作完成后再执行其他操作 二
  • 去掉页眉横线三法 (WORD)

    在使用WORD中 我们时常会用到页眉 但是加上页眉后 在页眉下往往有一横线 可是我们有时根本不需要这条横线 但它删都删不掉 怎么办呢 小弟在此奉上一计 一首先 打开一文档就不用说了 点击 视图 页眉和页脚 然后光标定位在页眉中 点击 格式
  • oracle在线日志损坏,前在线日志文件损坏与ora-600 [4000]处理

    这次又是一台机器上面有两个实例A和B 又是由于非当前的在线日志文件的状态是处于closed状态的 裸设备 于是dba将A节点的非当前在线日志文件填加到了B节点上面去了 于是在A节点日志发生切换时 导致了当前在线日志文件损坏 一般情况下当前在
  • 前端性能优化之页面加载

    牛客在线求职答疑中心 35799 牛客在线求职答疑中心 华为oppe 联洲国际3个offer该如何选择 牛客在线求职答疑中心 35799 牛客在线求职答疑中心 华为oppe 联洲国际3个offer该如何选择 想问一下紫光同芯这家公司 他们的
  • 【计算机网络】(五)网络层之ip地址+数据封装

    1 ip地址 1 1 IP地址一些概述 Internet protocol 互联网协议 IP地址 其实就是互联网协议里使用的地址 一台电脑 一个服务器都是一台主机 IP地址是主机唯一的标识 保证主机间正常通信 一种网络编码 用来确定网络中一
  • python os 模块

    os 模块 可以通过os模块调用系统命令 获得路径 获取操作系统的类型等都是使用该模块 1 通过os 获取系统类型 os name import os print os name nt nt 代表windows posix linux gt
  • C++ set容器及其常见操作

    文章目录 前言 一 什么是set容器 二 set容器的特征 三 set容器的常见操作 四 使用步骤 1 引入头文件 2 set容器的定义 3 set的插入和删除 4 set的遍历 5 set size 总结 前言 使用集合框架不仅能提高我们
  • c++动静编译的区别

    动态编译和静态编译的区别 动态编译决定了在程序运行时才会连接库文件 需要部署的坏境安装对应库 程序体积小 静态编译在编译时就连接好库文件了 所有库文件都打包进程序了 所以体积大 不过移植性好 demo 静态编译 test h ifndef
  • 使用docker创建fdfs并使用

    1 拉取镜像 docker pull delron fastdfs 2 使用docker镜像构建tracker容器 docker run dti network host name tracker v var fdfs tracker va
  • linux执行使分区生效的命令,Linux硬盘分区生效命令partprobe

    在Linux中使用fdisk命令进行分区时 有时会遇到 WARNING Re reading the partition table failed with error 16 Device or resource busy The kern
  • nodejs知识系列:npm查询包的所有版本及安装指定版本

    说明 在添加依赖或者安装本地环境时 有时候不支持最新的安装包 需要自己指定版本号 博主最近在win7开发nestjs和angular经常遇到 解决方案 npm view 目标包名 versions json或cnpm view 目标包名 v
  • python矩阵交换两行_Python 实现交换矩阵的行示例

    Python 实现交换矩阵的行示例 如下所示 TODO r1 r2 直接修改参数矩阵 无返回值 def swapRows M r1 r2 M r1 M r2 M r2 M r1 pass 以上这篇Python 实现交换矩阵的行示例就是小编分
  • 人工智能-统计机器学习- 自适应提升算法

    监督学习 Boosting adaptive boosting 自适应提升 对于一个复杂的分类任务 可以将其分解为 若干子任务 然后将若干子任务完成方法综合 最终完成该复杂任务 我们将这若干子任务称为弱分类器 weak classifier
  • 敏捷子弹(摘自《代码之道》第二章)

    最近面试了几家公司 感觉大家对采纳Scrum流程还是挺感兴趣的 5年前 我翻译了 代码之道 这本书 其中 第二章有一篇文章谈到了敏捷方法 文章的后半部分还对Scrum做了重点介绍 作者原是微软员工 他的一些观点和建议难免会结合微软公司的实践
  • 二分查找模板

    二分查找模板 基础模板 适用于查找某个在数组中的数的位置 def searchInsert self nums List int target int gt int n len nums l 0 注意1 r n 1 注意2 while l
  • JS_socket.io简单使用

    安装socket io npm install save socket io demo目录 index js node modules package json views index html 服务端代码 index js const h
  • Solidity 合约安全,常见漏洞(第三篇)

    Solidity 合约安全 常见漏洞 第三篇 ERC20 代币问题 如果你只处理受信任的 ERC20 代币 这些问题大多不适用 然而 当与任意的或部分不受信任的 ERC20 代币交互时 就有一些需要注意的地方 ERC20 转账扣费 当与不信