在上面两期中,我们介绍了在本体上开发智能合约可能会遇到的两种安全威胁:跨合约调用攻击和强制交易失败攻击,并针对这两种安全威胁给出了相应的解决方案。为了更好地帮助合约开发者规避这些安全漏洞,我们将持续收集这些可能会产生安全威胁的合约案例。同时,社区开发者如果在本体上进行智能合约开发时遇到其它的安全威胁,可以和我们一起探讨,一起共同打造更加安全的智能合约体系。
这一期将介绍开发智能合约过程中可能遇到的另外一种安全威胁:存储注入攻击。在详细介绍这种安全威胁前,我们先介绍下本体的智能合约持久化存储接口 :
def Put(context, key, value)
该接口主要包含下列三个参数:
1. context - 当前智能合约的运行的上下文环境,一般情况下为当前智能合约 hash;
2. key - 当前需要存储数据的 key 值;
3. value - 当前需要存储数据的 value 值。
正常情况下,开发者使用这个接口存储持久化数据时不会有问题,但是在一些复杂应用下,可能会存在安全威胁而被攻击的情况。
存储注入攻击
开发者在编写智能合约时,通常需要使用持久化存储去存放一些需要被多次使用的数据。这时,开发者就需要调用智能合约底层提供的持久化存储方法去存储这些数据。为了更好的说明这个问题,这里我们列举一个大家比较熟悉的例子。
当我们需要发布某个符合 OEP-4协议的 token 时,需要实现 OEP-4协议中的所有接口。这里,为了简化示例,我们只列举其中的两个方法:transfer()和 totalSupply()。
SUPPLY_KEY = 'TotalSupply' def transfer(from_acct, to_acct,amount): """ Transfer amount of tokens from from_acct to to_acct :param from_acct: the account from which the amount of tokens will be transferred :param to_acct: the account to which the amount of tokens will be transferred :param amount: the amount of the tokens to be transferred, >= 0 :return: True means success, False or raising exception means failure. """ if CheckWitness(from_acct) == False or amount < 0: return False fromBalance = Get(ctx, from_acct) if amount > fromBalance: return False if amount == fromBalance: Delete(ctx, from_acct) else: Put(ctx,from_acct, fromBalance - amount) toBalance = Get(ctx, to_acct) Put(ctx, to_acct, toBalance + amount) TransferEvent(from_acct, to_acct, amount) return True def totalSupply(): """ :return: the total supply of the token """ returnGet(ctx,SUPPLY_KEY)
粗略来看,这两个方法没有任何问题,transfer()方法主要实现 from_acct 地址到 to_acct 地址的转账功能,totalSupply()主要实现获取当前资产的总发行量功能。但是,若仔细查看transfer()方法里的实现,可以发现该方法并没有对 to_acct 地址做任何的检查。
假如这时恶意用户提供的 to_acct 地址并不是正常的地址,而是一些其他的字符串,例如:“TotalSupply”等,那么此时在获取当前资产发行量时就会不准确。这看起来危害程度是有限的,因为我们只列举了这两个方法。但是若这本智能合约中使用了其它持久化存储,所有的持久化存储可能都会产生影响。
存储注入攻击的防范
对于这种合约攻击,智能合约开发者如何去防范?可以看到,主要的问题还是在持久化存储的 key 值上,我们可以将 Put 中的 key 分为两类:
静态 key 值 - key 值直接在合约内部定义的值;
动态 key 值 - key 值需要从外部传入的值。
对于静态 key,因为在合约内部定义,所以并不会有变动。而动态的 key 值由于从外部传入,所以 key 值不可预测。在某些特殊的情况下,若动态的 key 值和某个静态 key 值相同,就会形成我们上面所说的存储注入攻击的情况。针对这种情况,开发者可以为 key 值增加 prefix 字段来防范这种攻击。
SUPPLY_KEY = 'TotalSupply' BALANCE_PREFIX = bytearray(b'\x01') def transfer(from_acct, to_acct, amount): """ Transfer amount of tokens from from_acct to to_acct :param from_acct: the account from which the amount of tokens will be transferred :param to_acct: the account to which the amount of tokens will be transferred :param amount: the amount of the tokens to be transferred, >= 0 :return: True means success, False or raising exception means failure. """ if CheckWitness(from_acct) == False or amount < 0: return False fromKey = concat(BALANCE_PREFIX, from_acct) fromBalance = Get(ctx, fromKey) if amount > fromBalance: return False if amount == fromBalance: Delete(ctx, fromKey) else: Put(ctx, fromKey, fromBalance - amount) toKey = concat(BALANCE_PREFIX, to_acct) toBalance = Get(ctx, toKey) Put(ctx, toKey, toBalance + amount) TransferEvent(from_acct, to_acct, amount) return True def totalSupply(): """ :return: the total supply of the token """ returnGet(ctx,SUPPLY_KEY)
可以看到,我们为动态传入的 to_acct 地址在持久化存储时增加了 prefix 字段。这样在存储时,就不会出现和其它持久化存储混乱的情况。当然,更好的做法可以给所有的持久化存储都加上 prefix 字段。这个例子中,由于只有两个持久化存储,我们只需要保证它们互相不受影响即可。
后记
以上我们讲解了第三种在本体上开发智能合约时可能遇到的安全威胁,并给出了相关解决方案。另外,本体智能合约开发者可以使用本体智能合约集成开发环境 SmartX 中深度集成的高度自动化智能合约形式化验证平台 VaaS-ONT 来“一键式”精确定位到有风险的代码位置,迅速找出原因,有效验证智能合约或区块链应用的常规安全漏洞、安全属性和功能正确性,从而显著提高安全等级。