外部调用与其他合约的组合以及底层区块链的多用户性质,造成了各种潜在的solidity陷阱,用户通过竞争代码的执行得到了非预期的状态。「重新入口」漏洞就是这种竞争条件的一个例子。
在这一部分,我们将更广泛地讨论可能发生在以太坊区块链上的不同竞争条件。
坑点分析
与大多数主链一样,在以太坊中只有当矿工解决了一个共识机制(PoW),这些交易才被认为是有效的。生成该区块的矿工也会选择将哪些交易包含在该区块中,这通常是由交易的gasPrice决定的。
这里就有一个潜在的攻击向量。攻击者可以监视可能包含问题解决方案的交易池,修改或撤销攻击者的权限或更改合约中对攻击者不利的状态。然后攻击者可以从这个交易获得数据,创建一个自己的交易,并且以更高的价格创建自己的交易,并将该交易包含在原始数据之前的区块中。
让我们通过一个例子来看看这个坑是怎么产生的: image
代码16
想象一下,这份合约包含了1000个以太币。用户如果能够找到一个哈希:
0xb5b5b97fafd9855eec9b41f74dfb6c38f5951f9a3ecd7f44d5479b630ee0a的sha3
就可以提交解决方案并得到1000以太币。
让我们假设一个用户发现的解决方案是 「Ethereum!」,他们将「Ethereum!」 作为参数调用solve()。不幸的是,攻击者已经很聪明地观察到任何提交解决方案者的交易池。他们看到了这个解决方案,检查了它的有效性,然后提交一个比原始交易价格更高的交易。
由于gasPrice更高,生成该区块的矿工可能会给攻击者更多的优先权,并在原始提交者之前先接受了他们的交易。攻击者会拿走1000以太币,而导致解决了这个问题的用户反而一无所获。
避坑技巧
有两类人可以执行这些正在运行的非法预先交易攻击:用户(他们修改交易的gasPrice)和矿工本身(他们可以按照他们认为合适的方式在一个区块中重新对交易排序)。
对于第一类来说,他们的合约比第二类合约要糟糕得多,因为矿工只有在解决了一个区块时才能进行攻击,而对于任何一个专门针对某个特定区块的矿工来说,这种攻击都是不可能的实现的。
我们可以将列出一些防坑措施。
首先,我们可以采用在合约中创建逻辑,为gasPrice设置一个上限。这使得用户无法提高gasPrice,这可以避免因提高gasPrice获得超出上限的优先交易顺序。这种预防措施只能减少第一类攻击者(任意使用者)。
在这种情况下,矿工仍然可以攻击合约,因为他们可以无论gasPrice如何,都可以随心所欲地在他们所在区块内进行交易。
另外,还有另一个方法是尽可能使用commit-reveal。这种方案要求用户使用隐藏的信息(通常是哈希)发送交易。在将交易包含在一个区块之后,用户发送一个交易来显示发送的数据(显式阶段)。这种方法使得矿工和用户无法确定交易的内容,因此不能对交易进行预警。
然而,这种方法不能隐藏交易的价值,智能合约允许用户发送交易,其提交的数据包括了他们愿意花费的以太币数量。然后用户可以发送任意值的交易。在这个阶段,用户可以获得交易中发送的金额与他们愿意支出金额之间的差额。
真实案例:ERC20与Bancor
在以太坊上发币要遵循ERC20标准,这个标准有一个潜在的预先非法交易漏洞,这一漏洞源自approve()函数。
该标准指定的approve()函数为:
这个函数允许用户授权其他用户代表他们转移代币。当Alice授权她的朋友Bob花费100个代币时,这个最大的漏洞就显现出来了。不过后来Alice想要撤回这个授权,所以她创建了一个交易,将Bob的配额设置为50个代币。
Bob一直在仔细地观察这条链,他看到了这个交易,并建立了一个自己花费100个代币的交易。比起Alice,他的gasPrice更高,交易的优先级也更高。一些approve()函数的实现允许鲍勃转移他的100个代币,然后当Alice的交易被提交时,将鲍勃的交易批准为50个代币,实际上让Bob获得了150个代币。
另一个著名的案例是Bancor。Ivan Bogatty和他的团队记录了最初Bancor实现中的一次的攻击,他在自己的博客详细的记录了这次攻击。从本质上来说,代币的价格是根据交易价值来确定的,用户可以观察Bancor交易的交易池,然后从价格差异中获利。目前Bancor的团队已经解决了这次攻击。