就在距离最新的以太坊君士坦丁堡升级不到两周的时候,以太坊基金会传来消息,其开发人员发现了新的漏洞。开发人员Jason
Carver称,“一个名为Create2的新功能可以允许开发人员替换自毁契约,从而更改规则。”,但是据悉,最新的升级日期并不会因为这一漏洞而改变。那么,Create2这个新功能到底引入了怎样的问题?为什么发现了一个“漏洞”,以太坊升级却照常进行呢?接下来,还是请我们的老朋友,北京链安的安全专家Hardman为大家做深入解析
事实上,这不是君士坦丁堡升级第一次出现安全漏洞问题,但是不同于上一次君士坦丁堡升级引发的SStore的可重入问题。SStore安全问题是对已有操作码的内部逻辑更新,引发了新老以太坊版本发生可重入问题。此次Create2
属于新引入的操作码,特定情形下可能引发一个hijack类安全问题。
Create2 opcode和Create opcode目的都是用来产生新的合约并部署,但不同的地方在于,使用Create2可以自由地控制合约产生的地址,但是Create不支持。
Create的合约部署地址产生方式为 keccak256(RLP(sender_address,nonce))。
可以看到,Create计算新的合约部署地址会受到交易发起人(即sender address)和发起人当下的nonce值决定。以太坊中nonce与创建交易有关,nonce由系统自行维护,自增。Create操作码无法支持将合约部署到一个已有的合约地址。
Create2的合约部署地址产生方式为 keccak256( 0xff ++sender ++ salt ++ keccak256(init_code))) 。
其中,0xff是用来区分新旧地址产生的方式,旧的方式因为会先对sender和nonce做rlp编码,编码完后最前面会是一个0x01~0xff之间的值,但0xff只有在被编码的数据级大才有可能出现,所以新的方式在最前面加了一个0xff来和旧的方式编码做区分。
init_code是要部署的合约代码的内容,在这里是计算合约代码内容的hash
salt是使用者可以任意指定的值,用于把同一份合约部署在不同地址(如果没有salt值,而且开发者部署的init_code又都一样,则同一份合约无法被部署到不同的地址)。
Create2引入的意义是开发者可以对合约部署的地址做控制,并且不需要真实部署有合约就可以拥有合约地址。如果使用Create,想要拥有一个合约地址就必须部署合约,但部署合约需要花费大量gas。
基于Create2的这个特性,便可能发生如下2种情况。
情况1 Create2合约部署地址发生碰撞:
开发者A使用Create2向地址1部署了一个以太坊合约m。此时合约m的构造函数会将合约内容hash与地址1关联
开发者B使用Create2很容易再向地址1部署一个以太坊合约n。
系统此时会发现地址1已经与keccak256(init_code)关联,并且keccak256(init_code)不为0,开发者B调用Create2部署合约返回失败。
情况1已经在eip-1014中有官方说明,不会引发安全问题。
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md
情况2 使用create2部署合约到被销毁的合约地址:
某个公共合约m包含自定义的destruct接口或者使用了默认的sefldestruct接口。
公共合约m被调用销毁。此时sefldestruct接口底层实现会将nonce或者keccak256(init_code) 置0。此时公共合约m的地址1与nonce或者keccak256(init_code)解除关联。
合约Owner或者第三方攻击者使用Create2向该公共地址部署一个相同接口但是接口内容恶意的合约。
依赖该公共合约的目标合约被部署的恶意合约攻击。
具体来看,需要满足以上的4个条件,目标合约才有可能被攻击。也就是说,理论上存在这一的可能,一个本来具有正常无害业务逻辑的合约,可能因为Owner权限被转交,或者合约开发方本来就存在恶意钓鱼歧途,是的合约Owner通过Create2从合约功能角度来个“狸猫换太子“,将合约功能替换成具有恶意行为的逻辑,使得不知情的合约执行者中招。
目前来看以太坊官方不会对Create2新特性做官方代码改动,因为某种程度上这不是一个纯粹的技术漏洞,而是一个技术特性可能被滥用的“社会攻击“问题。在Create2新特性引入之后,北京链安提醒您,如果您的合约依赖的第三方公共合约使用了Create2特性,或者您使用了Create2特性提供了第三方公共合约,请按照情况2的攻击模型对合约安全情况进行排查