发生场景: 以太坊转帐。
原因:发送的对象不知是外部帐户(用户地址)还是合约帐户。如果是合约帐户,则有被重入攻击的风险。
重入攻击发生在以太坊转帐的时候,msg.sender.call.value(_amount)(); 这条代码有重入风险。以太坊有两种帐户类型:一是外部帐户(用户地址),另一个是合约帐户 。如果是外部帐户,代码可以正常执行。但如果是合约的话,这条代码就有问题了。msg.sender.call.value(_amount)(); 会触发这个合约的fallback函数!如果fallback函数有恶意代码,那乐子就大啰!比如这样:
fallback() payable external {
if(address(ibank).balance > 0){
ibank.withdraw(); //这就相当于重复不停地取以太坊了!
}
}
发生的原因在于以太坊转帐的函数上:sendValue(1 ether)和call{value: 1 ether}("")都有重入的风险,它们没有gas的限制,很可能会触发外部合约的攻击代码。安全的方法是使用transfer转帐。
eg:
//bank.sol
function withdraw() public payable {
// msg.sender.call.value(_amount)(); //重入攻击 已修复
// msg.sender.sendValue(1 ether); //重入攻击
msg.sender.call{value: 1 ether}(""); //会被重入攻击
// payable(msg.sender).transfer(1 ether); //不会重入攻击
}
//attack.sol
function iWithdraw() public payable {
ibank.withdraw();
}
fallback() payable external {
if(address(ibank).balance > 0){
ibank.withdraw();
}
}
//有时用receive也是可以的
receive() payable external {
if(address(ibank).balance > 0){
ibank.withdraw();
}
}