智能合约安全问题一直是区块链技术体系中探讨得比较多的话题之一。无论是以以太坊 EVM 虚拟机为代表的智能合约体系,还是以 EOS WASM 虚拟机为代表的智能合约体系,都或多或少地暴露过不同类型的智能合约漏洞。这些漏洞不仅使得项目方和用户损失惨重,而且也让用户对区块链的安全性产生了质疑。
——题记
01 引言
在上一期中,我们介绍了一种在本体上开发智能合约时可能遇到的安全威胁,即跨合约调用攻击。相信大家对如何防范这种智能合约漏洞攻击有了一定的了解。本期中我们将介绍在本体上开发智能合约时可能遇到的另一种安全威胁以及相关防范措施。
在介绍这种安全威胁之前,我们先简单介绍下在本体区块链中,当出块节点打包交易形成区块以后,其交易执行的过程和手续费收取的方式。这个过程大概可以分为以下四步:
-
区块经过共识验证确认;
-
区块中的交易在本地执行;
-
收取每一笔交易的手续费用,若手续费不足则执行失败;
-
若执行成功,将执行的交易状态存储在本地账本中;否则,将执行的交易状态丢弃。
可以看到在区块落账的过程中,区块中交易的执行先于交易手续费用的收取。另外,我们都知道本体采用 ONG 作为交易的手续费,每一笔交易都收取至少0.01 ONG 作为交易的手续费。这个区块落账的过程和交易手续费的收取方式在正常的执行逻辑中并没有什么问题,但是对于一些特殊的情况,如果不注意智能合约的编写,可能会面临一种新的安全威胁:强制交易失败攻击。
02 强制交易失败攻击
本体区块链采用 ONT 和 ONG 双 Token 体系,并支持 OEP-4、OEP-5以及 OEP-8等多种同质化或非同质化的 Token。DApp 通常会使用一种 Token 作为该 dApp 的流转 Token,比如 ONG。可以看到,此时 ONG 不仅作为流转 Token,同时也作为交易的手续费用。开发者需要特别注意这种特殊情况下智能合约的开发,因为这可能会面临强制交易失败攻击。
我们举一个简单的例子。现有一款简单的 dApp 实时竞猜游戏,用户使用 ONG 参与竞猜并发送竞猜交易。若用户竞猜成功,则他将获得一定的 ONG 奖励,否则就失去他参与竞猜的 ONG。用户每次竞猜都将付出一定的 ONG 作为竞猜交易的手续费。下面就是这样的一个智能合约代码片段:
def guess(account, ongAmount, number):
"""
:param account: 用户地址
:param ongAmount: 参与竞猜的ONG数量
:param number:竞猜值
:return:
"""
if CheckWitness(account) == False:
return False
randomNumber = _rollANumber()
if number = randomNumber:
Require(_transferONGFromContact(account, 3*ongAmount))
# if number != randomNumber the account/player will lose all his money
return True
在该合约片段中,_rollANumber方法用于取得随机数,如果用户竞猜的数值等于该随机数,那他将获得三倍的收益。
按照本体区块链中区块的落账逻辑,将先执行用户的竞猜交易,然后收取用户竞猜交易的手续费,最后根据交易执行结果决定是否保存交易状态。在上述例子中,一个恶意用户会使用其账户下所有的 ONG 参与竞猜,若成功,他将获得奖励并以此支付手续费。但是若该用户竞猜失败,因为他将所有的 ONG 都参与竞猜,这时没有足够的 ONG 作为手续费用,该笔竞猜交易将会执行失败,用户不会失去他的 ONG。显而易见,恶意用户一直会获胜。
03 强制交易失败攻击的防范
对于这种合约攻击,智能合约开发者如何防范?一种可行的解决方案是在合约调用一开始就判断是否有足够的 ONG 作为手续费。在上述示例代码中,智能合约开发者可以通过在合约中增加一定的限制来解决这个问题,即不能让用户使用所有的 ONG 来进行竞猜,留出足够的 ONG 作为手续费。
具体代码如下:
ONGAddress = bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02')
# 0.1 ONG
MinInvokeFee = 100000000
def guess(account, ongAmount, number):
"""
:param account: 用户地址
:param ongAmount: 参与竞猜的ONG数量
:param number:竞猜值
:return:
"""
if CheckWitness(account) == False:
return False
# #=== avoid to be attacked begin ===
minAmount = ongAmount + MinInvokeFee
Require(_avoidForceTxAbortAttack(account, minAmount))
# # === avoid to be attacked end ===
randomNumber = _rollANumber()
if number = randomNumber:
Require(_transferONGFromContact(account, 3*ongAmount))
# if number != randomNumber the account/player will lose all his money
return True
def _avoidForceTxAbortAttack(account, minAmount):
param = state(account)
ongBalance = Invoke(0, ONGAddress, 'balanceOf', param)
Require(ongBalance > minAmount)
return True
可以看到,在该智能合约中开发者新增了_avoidForceTxAbortAttack 方法,用于判断该用户是否有足够的 ONG 作为竞猜费和手续费。
在合约中,将该方法放在执行逻辑的最前面,若用户账号中没有足够的 ONG 作为竞猜费和手续费,该交易会直接执行失败。通过这种方式,智能合约开发者可以防范恶意用户利用该漏洞攻击合约的情况发生。
04 后记
以上,我们讲解了在本体上开发智能合约时可能遇到的第二种安全威胁,并给出了相关解决方案。我们还将继续介绍另外的智能合约安全威胁,方便大家了解如何在本体上开发更加安全的智能合约。
本体智能合约开发者可以使用本体智能合约集成开发环境 SmartX 中深度集成的高度自动化智能合约形式化验证平台 VaaS-ONT 来“一键式”精确定位到有风险的代码位置,迅速找出原因,有效验证智能合约或区块链应用的常规安全漏洞、安全属性和功能正确性,从而显著提高安全等级。