以太坊BOA检查和漏洞是什么意思?

文章来源:互联网 发布时间:2023-03-29 18:05:00

分享一个外部账户 EOA 漏洞案例。

以太坊的账户类型分为 外部账户 和 合约账户 两种类型。所谓外部账户,又称为 EOA(Externally Owned
Account),是指由用户掌握私钥的地址(用户的钱包地址一般为这种类型),而合约账户是部署到链上创建出的合约地址(多签账户的地址一般为这种类型)。

看下面的这段合约代码:

contract OnlyForEOA {

uint public flag;

// bad

modifier isNotContract(address _a){

uint len;

assembly { len :=extcodesize(_a) }

require(len==0);

_;

}

function setFlag(uint i) public isNotContract(msg.sender){

flag=i;

}

}

extcodesize 操作符返回某个地址关联的代码(code)长度,如果代码长度为0,表示为外部地址。而大于0表示为合约地址。

这段代码的本意是只允许 EOA 账户对该合约进行操作,也就是说普通用户可以直接用外部地址发起合约调用,而不允许用另一个合约进行调用。

那么上面这些代码的漏洞在哪里呢?会智能合约编程的同学可以稍微思考几分钟,再往下看。

下面的我们来部署一个攻击合约:

contract FakeEOA {

constructor(address _a) public {

OnlyForEOA c=OnlyForEOA(_a);

c.setFlag(1);

}

}

在 FakeEOA 攻击合约中,我们传入 _a 一个合约地址(准确来说是一个尚未生成的合约地址)。因为该合约地址尚未生成,当代码检查其
extcodesize 时,其结果为0。这样合约地址也被 OnlyForEOA 所接受了,这就是 OnlyForEOA 的漏洞所在。

在以太坊中,合约地址的产生是可以预先计算的。其计算逻辑为:根据发送者的地址,和交易 nonce 来确定。算法如下:

合约地址=address(uint160(uint256(keccak256(abi.encodePacked(byte(0xd6),
byte(0x94), _origin, byte(nonce))))))

讲解解决?

要解决这个问题,我们可以加上 tx.origin 的判断,如下:

require(tx.origin==msg.sender);

这样可以保证是外部账户直接调用。

另外对于 tx.origin 使用要注意相关可能的陷阱,详见之前的文章:安全|一段代码讲解偷走你的币。