本周BeautyChain和SmartMesh两个加密币的价格大幅下跌。一个或多个攻击者利用其智能合约中的错误,凭空产生大量令牌,大量稀释令牌供应。这些事件原本可以轻松避免。本文中,我们将对这些攻击做解析,以Solidity代码实例为演示,以此来给代币投资者和开发人员对智能合约安全性做更深入的了解和做好安全防范。
事故原委
2018年4月25日,一些加密币交易所停止了至少两个ERC20代币BeautyChain和SmartMesh的存取和交易。其公告声称这些代币在其智能合约中存在安全漏洞。
这些合约已经受到攻击,凭空创造了大量的代币,导致价格大幅下跌。举例来说,这些代币的比特币价格在一小时内下跌超过90%,交易暂停前价格曲线如下图所示:
攻击者利用的漏洞为整数溢出,这在程序界和安全界很常见,并不新鲜。而且这种漏洞很容易预防,通常任何一家智能合约安全指南中都会有提到。此外,还有很多免费开源预防的软件库可供使用。然而,这种简单bug影响的合约却屡见不鲜。
有安全研究人员对此做了研究,总结了至少还有10种令牌还受此漏洞的影响。事实上,同样的漏洞已经被大量使用,通过大规模扩大其令牌的供应量并在交易所销售。如果这些合约背后的开发人员稍微有点安全意识,面对这些众所周知的漏洞,稍微有点责任心地做个判断,这些攻击就不可能发生。
攻击原理
一些Solidity智能合约容易受到整数溢出或下溢的影响。问题发生在程序中整形变量超过其使用的数据类型的最大值或最小值时发生。发生这种情况时,值会分别绕过最小或最大范围,变成到另一端的极值(穿越了)。
例如,下面的Solidity函数总是返回true:
function wrapAround() returns (bool) {
unit x=500;
unit max=2 ** 256 -1; //2**256表示2的256次方,是unit(256位无符号整数)能包含的最大值
return x + max <x;// 由于超界了,返回true
}
在数学上,这是不直观的,因为两个正数的总和总是大于第一个。然而,由于max的值是2 ^ 256 – 1,这是unit数据类型的上限,所以x + max的结果绕回到0,变为499而不是500 + 2 ^ 256 – 1。
整数下溢遵循相同的原理,但结果相反。
在本文后面的两部分中,我们将会实例说明整数溢出和下溢是的漏洞是怎么发生的。
SmartMesh令牌合约漏洞
SmartMesh令牌合约中易受攻击的函数是transferProxy:
攻击者在交易0x1abab4c8 …中利用到了此函数:
该笔交易发生在块5499035,并且在区块链中这个点:
balances [_from](balances [0xdf31a49 …])的余额为0令牌。
balances[_to](balances [0xdf31a49 …])的余额为0令牌。
balances [msg.sender](balances [0xd6a09bd …])的余额为0令牌。
_value是0x8ffff…而_freeSmt是0x70000 …1。
注意是如何个_from添加到_value极值溢出到0。0x8ffff …f + 0x70000 …1是0。这将有便于我们理解。
现在,让我们看看第一条if语句(第5行)。由于balances[_from]为0且_feeSmt + _value也为0,因此if语句的条件为false(0 <0为false),并且不会触发revert()。此检查通过,对攻击者有用,对其他人不利。
接下来,让我们来分析第二个if语句(第14行):
if (balances[_to] + _value < balances[_to] || balances[msg.sender] + _feeSmt < balances[msg.sender]) revert();
合约的开发者可能打算在这里执行溢出检查。但是在这一点上,不会发生溢出:
balances [_to]为0,因此balances [_to] + _value <balances [_to]为false
balances [msg.sender]为0,因此balances[msg.sender] + _feeSmt <余额[msg.sender]为false
因此,这两个条件都是false,不可能触发revert()。
随后,余额的状态变量将被修改为:
balances[_to] += _value;导致地址_to的所有者获得大量的令牌; balances[_from] -= _value + _feeSmt;导致_from的所有者丢失0个令牌。
BeautyChain令牌合约漏洞
虽然BeautyChain令牌合约的作者使用了SafeMath库,如果使用正确,其功能就可防止溢出漏洞,但它们并未在batchTransfer函数的特定行中执行此操作:
uint256 amount = uint256(cnt)* _value;
完整的函数代码如下:
在用于触发此漏洞的交易中,_value与uint256(cnt)相乘时发生溢出。
因此: cnt == 2 and _value == 0x8000000000000000000000000000000000000000000000000000000000000000
值溢出并变成正好0。这意味着第二个require()语句并不会抛出异常,并且平衡状态变量在for循环中大量递增。攻击者非常聪明的利用一个特定的输入组合,导致require语句或SafeMath函数不抛出任何异常,但允许exploit执行。粗心的安审人员可能会简单地认为,仅在batchTransfer中存在SafeMath库函数所以不会有漏洞发生。
安全提醒
根据代币界的说法,私钥既是权限。如果你不再拥有你的私钥,哪你也不再不拥有你的代币。除此之外,涉及到令牌问题时,这个建议还不够的。因为令牌安全还和智能合约安全有关。
记住这一点:不要轻信,一定要确认。
你不仅要保证你的私钥绝对安全,而且还要验证你跑令牌的代码,是否还有上述类似的漏洞。
加密货币交易所现在所处困境,因为确保客户资金安全的唯一途径是去审核每一个可交易令牌的每一行代码。这很乏味但是很有必要。当然这肯定是投资者做不了的事情。由于未发现和潜在的bug而可能导致亏损,这是个巨大风险。着风险如果得不到解决,加密币也就没有前途。
然而,非专业人员无法审计智能合约,因此他们别无选择,只能相信开发人员。具有讽刺意味的是,去中心化的金融脱媒(financial disintermediation)和”无信任”正是基于区块链技术的加密币所承诺的,但这在现实中的情况中却正好相反。
通过这些事件应该给每个人都敲响警钟。智能合约开发商担负起自己的安全职责,努力确保投资者的资金安全,投资者必须充分意识到缺乏安全验证的令牌可能带来的后果。然而,不幸的是唯一能确保安全的方法是自己去验证代码。
当然这对安全公司和安全从业人员来说确是个巨大的机会。现在由安全来牵头做吃力不讨好的代码审计,代码审核了,而且是金融界的要求,这会推动相关技术相关产业的发展和完善,推动相关产业的繁荣。
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。