这个代码的问题何在?
下面代码是君士坦丁堡硬分叉之前没有可重入漏洞的一个代码段,它会在分叉后导致可重入性。我们的Github页面(https://github.com/ChainSecurity/constantinople-reentrancy)展示了包括攻击合约在内的完整源代码。
这个代码会被一种意想不到的方式攻击:它模拟了一个安全的资金共享服务。双方可以共同接收资金,决定如何分成,如果他们达成一致,则可以获得支付。攻击者将创建一个对,其中第一个地址是下图列出的攻击合约地址,第二个地址则是任何攻击者账户。攻击者将在这个对存入一些以太币。
当攻击者在自己的合约中调用attack函数时,在同一个交易中就会发生以下事件:
1.攻击者利用updateSplit 函数设置当前的分成,从而确保之后的升级将会降低费用。这是君士坦丁堡升级带来的效应。攻击者通过这种方式设置分成,他的第一个地址(即攻击合约地址)就能接收到所有的以太币。
2.攻击合约调用splitFunds 函数,该调用将进行检查*等,并通过transfer()函数把其中存储的所有以太币发送到攻击合约。
3.通过回退函数,攻击者再次执行分成,这一次将所有以太币分配给他的第二个地址,即其自己的账户。
4.通过不断执行splitFunds 函数,在第一个地址中存储的所有以太币都被转移到了攻击者账户中。
简单来说,攻击者可以不断从PaymentSharer合约中盗取其他用户的以太币。
**为什么这种漏洞变得可利用了?**
在君士坦丁堡硬分叉前,一个存储指令至少需要5000 gas 手续费。这远远超过了在利用transfer()函数或send()函数调用合约时可用的2300 gas津贴。
在君士坦丁堡分叉后,会改变“脏(dirty)”存储器槽(storage slot)的存储指令只需200 gas。如果在交易执行期间对一个存储器槽做出修改,那么这个存储器槽就会被标记为“dirty”。如上所示,这可以通过在攻击合约调用一些改变所需变量的公共函数来实现。然后,通过使被攻击合约调用攻击合约,例如使用msg.sender.transfer(…)函数,攻击合约就可以利用gas津贴成功操纵这个被攻击合约的变量。
被攻击合约必须满足以下先决条件:
1.首先必须存在函数A,在调用transfer/send函数后将执行状态修改的指令。有时这种指令可能是不明显的,例如,第二次transfer的调用或与另一个智能合约的互动。
2.必须存在攻击者可调用的函数B ,能够实现(a)改变状态;(b)其状态改变与函数A的状态改变冲突。
3.执行函数B 的手续费必须小于1600gas (2300 gas 津贴 — CALL指令花费的700 gas)。
我的智能合约是否易受攻击?
要测试一个智能合约是否易受攻击:
(a)检查在transfer事件后是否存在其他指令。
(b)检查这些指令是否修改了存储状态。最常见的修改是通过分配一些存储变量。如果你在调用另一个合约,比如代币的transfer函数,检查并列出所有被修改的变量。
(c)检查在你的合约中是否有任何非管理员可访问的方式使用了这些变量。
(d)检查这些方式本身是否修改了存储状态。
(e)检查这种方式的手续费是否低于2300 gas,记住,SSTORE指令理论上只需要花费200 gas。
如果上述所有情况皆符合,那么攻击者很可能可以对你的合约发起恶意攻击,使其陷入糟糕的状态。总的来说,这也再次提醒了我们“检查-生效-交互”(Checks-Effects-Interactions)模式的重要性。
目前存在易受攻击的智能合约吗?
利用eveem.org提供的数据,我们检查了主要以太坊区块链,并未发现易受攻击的智能合约。我们也在与ethsecurity.org工作组的成员合作,将此次检查扩展到尚未反编译的复杂智能合约,尤其是去中心化交易所。去中心化交易所经常向不可信账户调用以太币转移函数,之后存储状态被修改,这些交易所就可能会变得易受攻击。
据称,目前为止还没有发现合约因为该漏洞而造成损失,但是这显然是一个极大的隐患。幸好,该漏洞在君士坦丁堡升级之前被发现,以太坊团队决定推迟升级时间,从这一点也可以看出项目小区化运营的巨大力量~
本文转载公众号:区块链研究实验室,社区开始招募社区志愿者,有意的可以给我发私信。