以太坊智能合约安全漏洞(2):溢出 | 赠书活动

2019-04-12 16:42:01 浏览数 (1)

题图摄于加州太平洋海岸

英文原文已有若干个中文翻译版本,但译文和原文都有错漏或不足。本文系基于译者(Henry)的理解,重新翻译并做了修正,并且加入了个人的注解,力求让读者更容易领会原文的含义。为不影响阅读体验,注解没有单独标出, 可当译文 “笔记”来阅读。 本期继续“转发即挖矿”的赠书活动,欢迎参与。

本文首发于哈希1024社区:

https://hash1024.org/topics/97

算术运算的上溢/下溢

和绝大多数编程语言一样,以太坊虚拟机(EVM)中的整数类型是有一定的范围的。例如,uint8 只能存储 [0,255] 范围内的数(无符号8位二进制数)。尝试将 256 存储到 uint8 将得到0。如果程序员不留意,没有对用户的输入进行校验就进行计算,将可能导致变量数值超出它们数据类型的有效范围,因此 Solidity 中的变量可被利用。

这个不仅仅是 Solidity 的问题,各类变成语言都有可能出现类似的漏洞。 漏洞描述 当某个操作把超出变量数据类型范围的数值写入变量时,则会发生上溢出或者下溢出。在大学里计算机组成原理的课程里面应该有相关的内容。 例如,从一个值为 0、类型为 uint8(8位的无符号整数,即只有正数)的变量减1,将得到数值 255,这称为下溢。

我们在uint8的范围之下给变量赋值,结果是这个变量成为了 uint8 类型可存储的最大数值。类似地,将 2 ^ 8 = 256 加到 uint8 类型的变量将使变量值保持不变,因为我们已经绕过uint的整个周期范围。这类似于在三角函数的自变量角度添加2π,其值不变,即 :

sin(x)= sin(x 2π)

变量加上大于数据类型范围的数值称为溢出。为清楚起见,将 257 添加到当前具有零值的 uint8 将得到数值1。固定类型变量的周期循环有时是有益的。如果我们在最大可能的数值之上加上数值,我们将从 0 重新开始计数。如果从 0 开始减去一个数值,姜得到该类型的最大数值。

这样的漏洞允许攻击者滥用代码并创建意外的逻辑流程。例如,考虑下面的时间锁定合约。(请关注公众号:亨利笔记)

TimeLock.sol:

该合约旨在像时间保险库一样,用户可以将以太币存入合约,并且锁定至少一周。如果他们选择,用户可以将等待时间延长到1周以上。但是一旦存入,用户可以确保他们的以太安全地被锁定至少一周。这个合约写得正确吗?(请关注公众号:亨利笔记)

如果用户被迫交出他们的私钥(想想被当成人质的情况),这样的合约可能很有用,可确保在短时间内以太无法被取出。如果用户在此合约中锁定了100个以太币,并将其密钥交给攻击者,则无论 lockTime 如何,攻击者都可以使用溢出来接收以太币。 攻击者可以确定他们拥有密钥的地址当前的 lockTime(一个公共变量)。我们称之为 userLockTime 。然后,他们可以调用 increaseLockTime 函数,并传递数值 2 ^ 256 - userLockTime 作为参数。此数值将被添加到当前 userLockTime并导致溢出,将 lockTime [msg.sender] 重置为0。这样,攻击者只需简单地调用 withdraw 函数来获取其奖励。(请关注公众号:亨利笔记)

让我们看另一个例子,这个来自 Ethernaut 挑战。 剧透警告:如果您正在玩 Ethernaut 挑战,下文是某个级别通关方案。

这是一个简单的 Token 合约,它采用 transfer() 函数,允许参与者移动他们的Token。你能看到合约中的错误吗? 缺陷来自 transfer() 函数。可以使用下溢来绕过第[13]行上的 require 语句。假设有个没有余额的用户,他可使用任何非零 _value 调用 transfer() 函数,并通过第[13]行的 require 语句。(本文首发哈希1024社区:hash1024.org )

这是因为 balances [msg.sender] 为零(且是 uint256类型),因此减去任何正数(不包括 2 ^ 256 )将导致正数,因为我们上面描述的下溢现象。对于第[14]行也是如此,余额将记成正数。因此,在此示例中,由于下溢漏洞,我们获得免费 Token。

预防技术 防止上溢/下溢漏洞的传统技术是使用或建立标准数学运算的替代数学库;加法,减法和乘法(除法除外,因为它不会导致上溢/下溢,EVM会在除以 0 时回滚)。 OppenZepplin 在构建和审核安全库方面做得非常出色,可以被以太坊社区使用。特别值得一提,他们的安全数学库可用于避免上溢/下溢漏洞的参考库。 为了演示如何在 Solidity 中使用这些库,让我们使用 Open Zepplin 的 SafeMath 库来更正 TimeLock 合约。无溢出的合约变成为这样:

请注意,所有标准数学运算都已替换为 SafeMath 库中定义的运算。 TimeLock合约不再执行任何能够执行上溢/下溢的操作。

参考文献:

1. 英文原文:

https://blog.sigmaprime.io/solidity-security.html

0 人点赞