智能合约漏洞攻击事件
Applications on Ethereum manage financial value, making security absolutely crucial. As a nascent, experimental technology, smart contracts have certainly had their fair share of attacks.
以太坊上的应用程序管理财务价值,因此安全性绝对至关重要。 作为一种新兴的实验性技术,智能合约当然具有应有的攻击份额。
To help prevent further attacks, I’ve constructed a list of nearly all known attacks and vulnerabilities. Though this list may cover known attacks, new exploits are still being discovered regularly, and, as such, this should only be the beginning of your research into smart contract security as an engineer.
为了帮助防止进一步的攻击,我构建了几乎所有已知攻击和漏洞的列表。 尽管此列表可能涵盖已知的攻击,但仍会定期发现新的攻击,因此,这仅是您以工程师身份开始研究智能合约安全的开始。
This list can also be found on GitHub.
该列表也可以在GitHub上找到 。
进攻 (Attacks)
In this section, we’ll look at known attacks that can be used to exploit smart contract vulnerabilities.
在本节中,我们将研究可用于利用智能合约漏洞的已知攻击。
前端运行又称为事务排序依赖性 (Front-running aka transaction-ordering dependence)
The University of Concordia considers front-running to be “a course of action where an entity benefits from prior access to privileged market information about upcoming transactions and trades.” This knowledge of future events in a market can lead to exploitation.
康科迪亚大学认为,“领先是一种行动,实体可以从事先获得的有关即将发生的交易和交易的特权市场信息中受益”。 对市场中未来事件的了解可以导致剥削。
For example, knowing a very large purchase of a specific token is going to occur, a bad actor can purchase that token in advance and sell the token for a profit when the oversized buy order increases the price.
例如,如果知道将要大量购买特定代币,那么坏演员可以提前购买该代币并在超大买单增加价格时出售该代币以获利。
Front-running attacks have long been an issue in financial markets, and due to blockchain’s transparent nature, the problem is coming up again in cryptocurrency markets.
长期以来,前端攻击一直是金融市场中的一个问题,由于区块链的透明性,该问题在加密货币市场中再次出现。
Since the solution to this problem varies on a per-contract basis, it can be hard to protect against. Possible solutions include batching transactions and using a precommit scheme (i.e., allowing users to submit details at a later time).
由于此问题的解决方案因合同而异,因此很难避免。 可能的解决方案包括批处理事务并使用预提交方案(即,允许用户稍后提交详细信息)。
限制气体的DoS (DoS with block gas limit)
In the Ethereum blockchain, the blocks all have a gas limit. One of the benefits of a block gas limit is it prevents attackers from creating an infinite transaction loop, but if the gas usage of a transaction exceeds this limit, the transaction will fail. This can lead to a DoS attack in a couple different ways.
在以太坊区块链中,所有区块都有瓦斯限制。 限制气体限制的好处之一是,它可以防止攻击者创建无限的事务循环,但是,如果事务的气体使用量超过此限制,则事务将失败。 这可能以几种不同的方式导致DoS攻击 。
Unbounded operations
无限运营
A situation in which the block gas limit can be an issue is in sending funds to an array of addresses. Even without any malicious intent, this can easily go wrong. Just by having too large an array of users to pay can max out the gas limit and prevent the transaction from ever succeeding.
限制气体限制可能是一个问题,就是将资金发送到一系列地址。 即使没有恶意,这也很容易出错。 只是因为有太多的用户来支付,才可以最大限度地提高用气限额,并阻止交易成功。
This situation can also lead to an attack. Say a bad actor decides to create a significant amount of addresses, with each address being paid a small amount of funds from the smart contract. If done effectively, the transaction can be blocked indefinitely, possibly even preventing further transactions from going through.
这种情况也可能导致攻击。 假设一个坏演员决定创建大量地址,每个地址都从智能合约中获得少量资金。 如果有效完成,则可以无限期地阻止交易,甚至可能阻止进一步的交易进行。
An effective solution to this problem would be to use a pull-payment system over the current push-payment system. To do this, separate each payment into its own transaction and have the recipient call the function.
解决此问题的有效方法是在当前的推付式支付系统上使用预付式支付系统。 为此,请将每笔付款分成自己的交易,然后让收款人调用该功能。
If, for some reason, you really need to loop through an array of unspecified length, at least expect it to potentially take multiple blocks, and allow it to be performed in multiple transactions — as seen in this example:
如果出于某种原因,如果您确实确实需要遍历一个未指定长度的数组,则至少希望它可能占用多个块,并允许它在多个事务中执行-如以下示例所示:
Consensys Consensys的示例Block stuffing
块馅
In some situations, your contract can be attacked with a block gas limit even if you don’t loop through an array of unspecified length. An attacker can fill several blocks before a transaction can be processed by using a sufficiently high gas price.
在某些情况下,即使您未遍历未指定长度的数组,您的合同也可能受到限制气体的攻击。 攻击者可以通过使用足够高的汽油价格来填充交易之前的几个区块。
This attack is done by issuing several transactions at a very high gas price. If the gas price is high enough and the transactions consume enough gas, they can fill entire blocks and prevent other transactions from being processed.
这种攻击是通过以很高的汽油价格发行几笔交易来完成的。 如果天然气价格足够高,并且交易消耗了足够的天然气,它们会填满整个区块并阻止其他交易被处理。
Ethereum transactions require the sender to pay gas to disincentivize spam attacks, but in some situations, there can be enough incentive to go through with such an attack. For example, a block stuffing attack was used on a gambling Dapp, Fomo3D. The app had a countdown timer, and users could win a jackpot by being the last to purchase a key — except every time a user bought a key, the timer would be extended. An attacker bought a key then stuffed the next 13 blocks in a row so they could win the jackpot.
以太坊交易需要发件人付费以抑制垃圾邮件攻击,但在某些情况下,可以有足够的动机来进行此类攻击。 例如,在赌博Dapp Fomo3D上使用了块填充攻击。 该应用程序具有倒数计时器,通过最后一次购买钥匙,用户可以赢得大奖-除非用户每次购买钥匙,否则计时器都会延长。 攻击者购买了一把钥匙,然后连续塞满了接下来的13个区块,这样他们才能赢得大奖。
To prevent such attacks from occurring, it’s important to carefully consider whether it’s safe to incorporate time-based actions in your application.
为了防止发生此类攻击,请务必仔细考虑将基于时间的操作合并到应用程序中是否安全。
带有(意外)还原的DoS (DoS with (unexpected) revert)
DoS (denial-of-service) attacks can occur in functions when you try to send funds to a user and the functionality relies on that fund transfer being successful.
当您尝试向用户发送资金并且功能依赖于成功进行资金转移时,DoS(拒绝服务)攻击可能会在功能中发生。
This can be problematic in the case that the funds are sent to a smart contract created by a bad actor, since they can simply create a fallback function that reverts all payments.
在将资金发送到由不良参与者创建的智能合约的情况下,这可能会出现问题,因为它们可以简单地创建一个可退回所有付款的后备功能。
For example:
例如:
Consensys Consensys的示例As you can see in this example, if an attacker bids from a smart contract with a fallback function reverting all payments, they can never be refunded, and, thus, no one can ever make a higher bid.
如本例所示,如果攻击者通过具有回退功能的智能合约出价来还原所有付款,则它们将永远无法退款,因此,没有人可以提出更高的出价。
This can also be problematic without an attacker present. For example, you may want to pay an array of users by iterating through the array, and, of course, you’d want to make sure each user is properly paid. The problem here is if one payment fails, the function is reverted and no one is paid.
如果没有攻击者在场,这也可能会带来问题。 例如,您可能希望通过遍历数组来向用户支付费用,当然,您要确保每个用户都得到了适当的支付。 这里的问题是,如果一次付款失败,该功能将被还原并且没有人付款。
Consensys Consensys的示例An effective solution to this problem would be to use a pull-payment system over the current push-payment system. To do this, separate each payment into its own transaction, and have the recipient call the function.
解决此问题的有效方法是在当前的推付式支付系统上使用预付式支付系统。 为此,请将每笔付款分成自己的交易,然后让收款人调用该功能。
Consensys Consensys的示例强制将以太币发送给合同 (Forcibly sending Ether to a contract)
Occasionally, it’s unwanted for users to be able to send Ether to a smart contract. Unfortunately for these circumstances, it’s possible to bypass a contract fallback function and forcibly send Ether.
有时候,用户不需要将Ether发送到智能合约。 不幸的是,在这些情况下,可以绕过合同后备功能并强制发送以太坊。
Consensys Consensys的示例Though it seems like any transaction to the Vulnerable contract should be reverted, there are actually a couple ways to forcibly send Ether.
虽然似乎应该撤销与弱势合同的任何交易,但实际上有两种方法可以强制发送以太坊。
The first method is to call the selfdestruct
method on a contract with the Vulnerable contract address set as the beneficiary. This works because selfdestruct
will not trigger the fallback function.
第一种方法是在以“易受攻击的合同”地址设置为受益人的合同上调用“ selfdestruct
方法。 这是selfdestruct
因为selfdestruct
不会触发后备功能。
Another method is to precompute a contract’s address and send Ether to the address before the contract is even deployed. Surprisingly enough, this is possible.
另一种方法是预先计算合同的地址,并在部署合同之前将Ether发送到该地址。 令人惊讶的是,这是可能的。
煤气不足 (Insufficient gas griefing)
Griefing is a type of attack often performed in video games, where a malicious user plays a game in an unintended way to bother other players, aka trolling. This type of attack is also used to prevent transactions from being performed as intended.
悲伤是视频游戏中经常发生的一种攻击,恶意用户以一种意想不到的方式玩游戏来打扰其他玩家,也就是巨魔。 此类攻击还用于防止按预期执行事务。
This attack can be done on contracts which accept data and use it in a subcall on another contract. This method is often used in multisignature wallets as well as transaction relayers. If the subcall fails, either the whole transaction is reverted or execution is continued.
可以对接受数据并在另一个合同的子调用中使用它的合同进行此攻击。 这种方法通常用于多重签名钱包以及交易中继器中。 如果子调用失败,则将还原整个事务或继续执行。
Let’s consider a simple relayer contract as an example. As shown below, the relayer contract allows someone to make and sign a transaction without having to execute the transaction. Often this is used when a user can’t pay for the gas associated with the transaction.
让我们以一个简单的中继器合同为例。 如下所示,中继合同允许某人进行交易并签署交易,而不必执行交易。 当用户无法支付与交易相关的汽油时,通常会使用此功能。
Consensys Consensys的示例The user who executes the transaction, the forwarder, can effectively censor transactions by using just enough gas that the transaction executes but not enough gas for the subcall to succeed.
执行事务的用户( 转发器)可以通过仅使用足以执行事务的气体而不使用足以使子调用成功的气体来有效地审查事务。
There are two ways this could be prevented. The first solution would be to only allow trusted users to relay transactions. The other solution is to require the forwarder provides enough gas, as seen below.
有两种方法可以防止这种情况发生。 第一种解决方案是仅允许受信任的用户中继事务。 另一个解决方案是要求货运代理提供足够的气体,如下所示。
Consensys Consensys的示例再入 (Reentrancy)
Reentrancy is an attack that can occur when a bug in a contract function can allow a function interaction to proceed multiple times when it should otherwise be prohibited. This can be used to drain funds from a smart contract if used maliciously. In fact, reentrancy was the attack vector used in the DAO hack.
重入合同是合约功能中的错误可能导致功能交互多次进行的攻击,而在其他情况下应禁止这种交互。 如果被恶意使用,这可以用于从智能合约中抽出资金。 实际上,重入是DAO hack中使用的攻击媒介。
Single-function reentrancy
单功能折返
A single-function reentrancy attack occurs when a vulnerable function is the same function an attacker is trying to recursively call.
当易受攻击的功能与攻击者试图递归调用的功能相同时,就会发生单功能重新进入攻击。
Consensys Consensys的示例Here, we can see the balance is only modified after the funds have been transferred. This can allow a hacker to call the function many times before the balance is set to 0, effectively draining the smart contract.
在这里,我们可以看到余额仅在转帐后才被修改。 这可以使黑客在余额设置为0之前多次调用该函数,从而有效地耗尽了智能合约。
Cross-function reentrancy
跨功能再入
A cross-function reentrancy attack is a more complex version of the same process. Cross-function reentrancy occurs when a vulnerable function shares a state with a function an attacker can exploit.
跨功能再入攻击是同一过程的更复杂版本。 当易受攻击的功能与攻击者可以利用的功能共享状态时,就会发生跨功能重新进入。
Consensys Consensys的示例In this example, a hacker can exploit this contract by having a fallback function call transfer()
to transfer spent funds before the balance is set to 0 in the withdraw()
function.
在此示例中,黑客可以通过在调用withdraw()
函数将余额设置为0之前具有回退功能调用transfer()
来转移用完的资金来利用此合同。
Reentrancy prevention
防止再入
When transferring funds in a smart contract, use send
or transfer
instead of call
. The problem with using call
is unlike the other functions, it doesn't have a gas limit of 2300. This means call
can be used in external function calls, which can be used to perform reentrancy attacks.
在智能合约中转移资金时,请使用send
或transfer
而不是call
。 使用所述问题call
是不像其他的功能,它不具有2300这装置的气体限制call
可以在外部函数调用中使用,其可以被用于执行重入攻击。
Another solid prevention method is to mark untrusted functions.
另一种可靠的预防方法是标记不信任的功能 。
Consensys Consensys的示例In addition, for optimum security use the checks-effects-interactions pattern. This is a simple rule of thumb for ordering smart contract functions.
此外,为了获得最佳安全性,请使用“ 检查-效果-交互”模式 。 这是订购智能合约功能的简单经验法则。
The function should begin with checks — e.g., require
and assert
statements.
该函数应从检查开始-例如require
和assert
语句。
Next, the effects of the contract should be performed — e.g., state modifications.
接下来,应执行合同的效力 -例如,修改状态。
Finally, we can perform interactions with other smart contracts — e.g., external function calls.
最后,我们可以与其他智能合约进行交互 ,例如,外部函数调用。
This structure is effective against reentrancy because the modified state of the contract will prevent bad actors from performing malicious interactions.
这种结构可有效防止重入,因为合同的修改状态将防止不良行为者执行恶意互动。
Consensys Consensys的示例Since the balance is set to 0 before any interactions are performed, if the contract is called recursively, there is nothing to send after the first transaction.
由于在执行任何交互操作之前将余额设置为0,因此如果递归调用合同,则在第一个事务之后将没有任何要发送的内容。
漏洞 (Vulnerabilities)
In this section, we’ll look at known smart contract vulnerabilities and how they can be avoided. Nearly all vulnerabilities listed here can be found in the Smart Contract Weakness Classification.
在本节中,我们将研究已知的智能合约漏洞以及如何避免它们。 此处列出的几乎所有漏洞都可以在“ 智能合约弱点分类”中找到 。
整数上溢和下溢 (Integer overflow and underflow)
In solidity, integer types have maximum values. For example:
实际上,整数类型具有最大值。 例如:
uint8
=> 255
uint8
=> 255
uint16
=> 65535
uint16
=> 65535
uint24
=> 16777215
uint24
=> 16777215
uint256
=> (2^256) - 1
uint256
=>(2 ^ 256)-1
Overflow and underflow bugs can occur when you exceed the maximum value (overflow) or when you go below the minimum value (underflow). When you exceed the maximum value, you go back down to zero, and when you go below the minimum value, it brings you back up to the maximum value.
当您超过最大值(溢出)或低于最小值(下溢)时,可能会发生溢出和下溢错误。 当您超过最大值时,您将返回到零,而当您低于最小值时,它将使您返回到最大值。
Since smaller integer types — like uint8
, uint16
, etc. — have smaller maximum values, it can be easier to cause an overflow; thus, they should be used with greater caution.
由于较小的整数类型(如uint8
, uint16
等)具有较小的最大值,因此更容易引起溢出; 因此,应谨慎使用它们。
Likely, the best available solution to overflow and underflow bugs is to use the OpenZeppelin SafeMath library when performing mathematical operations.
可能的最佳解决溢出和下溢错误的方法是在执行数学运算时使用OpenZeppelin SafeMath库 。
时间戳依赖性 (Timestamp dependence)
The timestamp of a block, accessed by now
or block.timestamp
, can be manipulated by a miner. There are three considerations you should take into account when using a timestamp to execute a contract function.
now
或block.timestamp
访问的一个块的时间戳可由矿工操作。 使用时间戳执行合同功能时,应考虑三个因素。
Timestamp manipulation
时间戳操纵
If a timestamp is used in an attempt to generate randomness, a miner can post a timestamp within 15 seconds of block validation, giving them the ability to set the timestamp as a value that’d increase their odds of benefiting from the function.
如果使用时间戳来尝试产生随机性,则矿工可以在块验证的15秒内发布时间戳,从而使他们能够将时间戳设置为一个值,从而增加他们从该功能中受益的几率。
For example, a lottery application may use the block timestamp to pick a random bidder in a group. A miner may enter the lottery then modify the timestamp to a value that gives them better odds at winning the lottery.
例如,彩票应用可以使用块时间戳来选择组中的随机投标人。 矿工可以进入彩票,然后将时间戳修改为一个值,使他们有更大的几率赢得彩票。
Timestamps should thus not be used to create randomness.
因此,不应将时间戳用于创建随机性。
The 15-second rule
15秒规则
Ethereum’s reference specification, the “Yellow Paper,” doesn’t specify a limit as to how much blocks can change in time — it just has to be bigger than the timestamp of it’s parent. This being said, popular protocol implementations reject blocks with timestamps greater than 15 seconds in the future, so as long as your time-dependent event can safely vary by 15 seconds, it’s safe to use a block timestamp.
以太坊的参考规范“黄皮书”并没有规定可以改变多少块的时间限制,它必须大于其父级的时间戳。 话虽这么说,流行的协议实现在将来会拒绝时间戳大于15秒的块,因此,只要与时间相关的事件可以安全地相差15秒,就可以安全地使用块时间戳。
Don’t use block.number
as a timestamp
不要使用 block.number
作为时间戳
You can estimate the time difference between events using block.number
and the average block time. But block times may change and break the functionality, so it's best to avoid this use.
您可以使用block.number
和平均块时间来估计事件之间的时间差。 但是块时间可能会更改并破坏功能,因此最好避免这种用法。
通过tx.origin授权 (Authorization through tx.origin)
tx.origin
is a global variable in Solidity which returns the address that sent a transaction. It's important you never use tx.origin
for authorization since another contract can use a fallback function to call your contract and gain authorization since the authorized address is stored in tx.origin
. Consider this example:
tx.origin
是tx.origin
中的全局变量,它返回发送事务的地址。 重要的是,切勿使用tx.origin
进行授权,因为另一个合同可以使用回退功能来调用您的合同并获得授权,因为授权地址存储在tx.origin
。 考虑以下示例:
Here we can see the TxUserWallet
contract authorizes the transferTo()
function with tx.origin
.
在这里我们可以看到TxUserWallet
合同授权transferTo()
与函数tx.origin
。
Now, if someone were to trick you into sending Ether to the TxAttackWallet
contract address, they could steal your funds by checking tx.origin
to find the address that sent the transaction.
现在,如果有人诱骗您TxAttackWallet
发送到TxAttackWallet
合约地址,他们可以通过检查tx.origin
来查找发送交易的地址来窃取您的资金。
To prevent this kind of attack, use msg.sender
for authorization.
为了防止这种攻击,请使用msg.sender
进行授权。
浮法 (Floating pragma)
It’s considered best practice to pick one compiler version and stick with it. With a floating pragma, contracts may accidentally be deployed using an outdated or problematic compiler version — which can cause bugs, putting your smart contract’s security in jeopardy. For open-source projects, the pragma also tells developers which version to use should they deploy your contract. The chosen compiler version should be thoroughly tested and considered for known bugs.
选择一个编译器版本并坚持使用它是最佳实践。 使用浮动实用程序时,可能会使用过时或有问题的编译器版本意外地部署合同,这可能会导致错误,从而危及智能合约的安全性。 对于开源项目,该实用程序还会告诉开发人员部署您的合同时应使用哪个版本。 所选的编译器版本应经过全面测试,并考虑是否存在已知错误。
The exception in which it’s acceptable to use a floating pragma is in the case of libraries and packages. Otherwise, developers would need to manually update the pragma to compile locally.
对于库和软件包,可以使用浮动编译指示例外。 否则,开发人员将需要手动更新编译指示以在本地编译。
功能默认可见性 (Function default visibility)
Function visibility can be specified as either public, private, internal, or external. It’s important to consider which visibility is best for your smart contract function.
可以将功能可见性指定为公共,私有,内部或外部。 重要的是要考虑哪种可视性最适合您的智能合约功能。
Many smart contract attacks are caused by a developer forgetting or forgoing to use a visibility modifier. The function is then set as public by default, which can lead to unintended state changes.
许多智能合约攻击是由开发人员忘记或放弃使用可见性修饰符引起的。 然后默认情况下将该功能设置为public,这可能导致意外的状态更改。
过时的编译器版本 (Outdated compiler version)
Developers often find bugs and vulnerabilities in existing software and make patches. For this reason, it’s important to use the most recent compiler version possible. See bugs from past compiler versions here.
开发人员通常会发现现有软件中的错误和漏洞并进行修补。 因此,尽可能使用最新的编译器版本很重要。 在此处查看以前的编译器版本中的错误。
未经检查的回电值 (Unchecked call-return value)
If the return value of a low-level call is not checked, the execution may resume even if the function call throws an error. This can lead to unexpected behaviour and break the program logic. A failed call can even be caused by an attacker, who may be able to further exploit the application.
如果未检查低级调用的返回值,则即使函数调用引发错误,执行也可以恢复。 这可能导致意外行为并破坏程序逻辑。 失败的呼叫甚至可能是由攻击者造成的,他们可以进一步利用该应用程序。
In Solidity, you can either use low-level calls such as address.call()
, address.callcode()
, address.delegatecall()
, and address.send()
, or you can use contract calls such as ExternalContract.doSomething()
. Low-level calls will never throw an exception — instead they will return false
if they encounter an exception, whereas contract calls will automatically throw.
在Solidity中,您可以使用低级调用,例如address.call()
, address.callcode()
, address.delegatecall()
和address.send()
,也可以使用合约调用,例如ExternalContract.doSomething()
。 低级调用永远不会引发异常-相反,如果遇到异常,它们将返回false
,而合同调用将自动引发。
In the case that you use low-level calls, be sure to check the return value to handle possible failed calls.
如果您使用低级调用,请确保检查返回值以处理可能的失败调用。
无保护的以太坊撤离 (Unprotected Ether withdrawal)
Without adequate access controls, bad actors may be able to withdraw some or all of the Ether from a contract. This can be caused by misnaming a function intended to be a constructor, giving anyone access to reinitialize the contract. To avoid this vulnerability, only allow withdrawals to be triggered by those authorized or as intended, and name your constructor appropriately.
没有适当的访问控制,不良行为者可能能够从合同中撤出部分或全部以太币。 这可能是由于错误地命名了要用作构造函数的函数,从而使任何人都可以重新初始化合同。 为避免此漏洞,仅允许由授权的或按预期的方式触发提款,并适当命名您的构造函数。
无保护的自毁指令 (Unprotected selfdestruct instruction)
In contracts that have a selfdestruct
method, if there are missing or insufficient access controls, malicious actors can self-destruct the contract. It's important to consider whether self-destruct functionality is absolutely necessary. If it’s necessary, consider using a multisig authorization to prevent an attack.
在具有合同selfdestruct
方法,如果缺少或不足的访问控制,恶意者可以自毁合同。 重要的是要考虑自毁功能是否绝对必要。 如果有必要,请考虑使用多重签名授权来防止攻击。
This attack was used in the Parity attack. An anonymous user located and exploited a vulnerability in the “library” smart contract, making themselves the contract owner. The attacker then proceeded to self-destruct the contract. This led to funds being blocked in 587 unique wallets, holding a total of 513,774.16 Ether.
此攻击用于奇偶校验攻击 。 一位匿名用户定位并利用了“库”智能合约中的漏洞,从而使自己成为合约的所有者。 然后,攻击者开始自毁合同。 这导致资金被冻结在587个唯一的钱包中,总共持有513,774.16个以太币。
状态变量默认可见性 (State variable default visibility)
It’s common for developers to explicitly declare function visibility but not so common to declare variable visibility. State variables can have one of three visibility identifiers: public
, internal
, or private
. Luckily, the default visibility for variables is internal and not public, but even if you intend on declaring a variable as internal, it's important to be explicit so there are no incorrect assumptions as to who can access the variable.
对于开发人员来说,显式声明函数可见性是很常见的,但声明变量可见性并不常见。 状态变量可以具有以下三个可见性标识符之一: public
, internal
或private
。 幸运的是,变量的默认可见性是内部的,而不是公共的,但是即使您打算将变量声明为内部的,也必须明确,因此对于谁可以访问该变量没有错误的假设。
未初始化的存储指针 (Uninitialized storage pointer)
Data is stored in the EVM as either storage
, memory
, or calldata
. It’s important the two are well understood and correctly initialized. Incorrectly initializing data-storage pointers, or simply leaving them uninitialized, can lead to contract vulnerabilities.
数据作为storage
, memory
或calldata
存储在EVM中。 理解并正确初始化这两者很重要。 错误地初始化数据存储指针,或者只是简单地不对其进行初始化,都可能导致合约漏洞。
As of Solidity 0.5.0
, uninitialized storage pointers are no longer an issue since contracts with uninitialized storage pointers will no longer compile. This being said, it's still important to understand what storage pointers you should be using in certain situations.
从Solidity 0.5.0
,未初始化的存储指针不再是问题,因为与未初始化的存储指针的协定将不再编译。 话虽如此,了解在某些情况下应该使用哪些存储指针仍然很重要。
断言违规 (Assert violation)
In Solidity 0.4.10
, the following functions were created: assert()
, require()
, and revert()
. Here, we'll discuss the assert function and how to use it.
在0.4.10
,创建了以下函数: assert()
, require()
和revert()
。 在这里,我们将讨论assert函数以及如何使用它。
Formally said, the assert()
function is meant to assert invariants; informally said, assert()
is an overly assertive bodyguard that protects your contract but steals your gas in the process. Properly functioning contracts should never reach a failing assert statement. If you've reached a failing assert statement, you've either improperly used assert()
or there is a bug in your contract that puts it in an invalid state.
正式地说, assert()
函数用于声明不变量; 非正式地说, assert()
是一个过分自信的保镖,可以保护您的合同,但会在过程中窃取您的气体。 正常运行的合同永远不应到达失败的断言声明。 如果到达了失败的assert语句,则说明您使用了assert()
方式不正确,或者合同中存在将其置于无效状态的错误。
If the condition checked in the assert()
is not actually an invariant, it's suggested that you replace it with a require()
statement.
如果在assert()
检查的条件实际上不是不变的,则建议您将其替换为require()
语句。
使用过时的功能 (Use of deprecated gunctions)
As time goes by, functions in Solidity are deprecated and often replaced with better functions. It’s important to not use deprecated functions, as it can lead to unexpected effects and compilation errors.
随着时间的流逝,Solidity中的功能已被弃用,并经常被更好的功能所取代。 重要的是不要使用不推荐使用的函数,因为它可能导致意外的结果和编译错误。
Here’s a list of deprecated functions and alternatives. Many alternatives are simple aliases and won’t break current behaviour if used as a replacement for its deprecated counterpart.
这是不推荐使用的功能和替代品的列表。 许多替代方法都是简单的别名,如果将其用作不推荐使用的替代方法,则不会破坏当前行为。
Deprecated functions and alternatives 不推荐使用的功能和替代品委托给不受信任的被叫者 (Delegatecall to untrusted callee)
Delegatecall
is a special variant of a message call. It’s almost identical to a regular message call except the target address is executed in the context of the calling contract and msg.sender
and msg.value
remain the same. Essentially, delegatecall
delegates other contracts to modify the calling contract's storage.
Delegatecall
呼叫是消息呼叫的特殊变体。 它与常规消息调用几乎相同,除了目标地址在调用协定的上下文中执行且msg.sender
和msg.value
保持不变。 本质上, delegatecall
调用委托其他合同来修改主叫合同的存储。
Since delegatecall
gives so much control over a contract, it's very important to only use this with trusted contracts, such as your own. If the target address comes from user input, be sure to verify that it’s a trusted contract.
由于delegatecall
调用对合同具有如此多的控制权,因此仅将它与受信任的合同(例如您自己的合同)一起使用非常重要。 如果目标地址来自用户输入,请确保验证它是受信任的合同。
签名延展性 (Signature malleability)
Often, people assume the use of a cryptographic signature system in smart contracts verifies that signatures are unique; however, this isn’t the case. Signatures in Ethereum can be altered without the private key and remain valid. For example, elliptic-key cryptography consists of three variables — v, r, and s — and if these values are modified in just the right way, you can obtain a valid signature with an invalid private key.
人们通常会假设在智能合约中使用了加密签名系统,以验证签名是唯一的。 但是,事实并非如此。 以太坊中的签名可以在没有私钥的情况下进行更改并保持有效。 例如,椭圆密钥密码术由三个变量v , r和s组成 ,如果以正确的方式修改了这些值,则可以获得具有无效私钥的有效签名。
To avoid the problem of signature malleability, never use a signature in a signed message hash to check if previously signed messages have been processed by the contract because malicious users can find your signature and recreate it.
为避免签名可延展性的问题,切勿在签名消息哈希中使用签名来检查合同是否已处理先前签名的消息,因为恶意用户可以找到并重新创建您的签名。
构造函数名称不正确 (Incorrect constructor name)
Before Solidity 0.4.22
, the only way to define a constructor was by creating a function with the contract name. In some cases, this was problematic. For example, if a smart contract is reused with a different name but the constructor function isn't also changed, it simply becomes a regular, callable function.
在0.4.22
之前,定义构造函数的唯一方法是使用合同名称创建函数。 在某些情况下,这是有问题的。 例如,如果智能合约以不同的名称重用,但构造函数也未更改,则它将变成常规的可调用函数。
Now with modern versions of Solidity, you can define the constructor with the constructor
keyword, effectively deprecating this vulnerability. Thus, the solution to this problem is simply to use modern Solidity compiler versions.
现在,在现代版本的Solidity中,您可以使用constructor
关键字定义构造constructor
,从而有效弃用此漏洞。 因此,解决此问题的方法只是使用现代的Solidity编译器版本。
阴影状态变量 (Shadowing state variables)
It’s possible to use the same variable twice in Solidity, but it can lead to unintended side effects. This is especially difficult regarding working with multiple contracts. Take the following example:
在Solidity中有可能两次使用相同的变量,但可能导致意外的副作用。 对于使用多个合同,这尤其困难。 请看以下示例:
Example of shadowing state variables 遮蔽状态变量的示例Here, we can see SubContract
inherits SuperContract
, and the variable a
is defined twice with different values. Now, say we use a
to perform some function in SubContract
. Functionality inherited from SuperContract
will no longer work since the value of a
has been modified.
在这里,我们可以看到SubContract
继承了SuperContract
,并且变量a
两次定义了不同的值。 现在,假设我们使用a
在SubContract
执行某些功能。 由于已经修改了a
的值,因此从SuperContract
继承的功能将不再起作用。
To avoid this vulnerability, it’s important we check the entire smart contract system for ambiguities. It’s also important to check for compiler warnings, as they can flag these ambiguities as long as they’re in the smart contract.
为避免此漏洞,重要的是我们检查整个智能合约系统是否存在歧义。 检查编译器警告也很重要,因为只要它们在智能合约中,它们就可以标记这些歧义。
链属性的随机性来源较弱 (Weak sources of randomness from chain attributes)
In Ethereum, there are certain applications that rely on random-number generation for fairness. However, random-number generation is very difficult in Ethereum, and there are several pitfalls worth considering.
在以太坊中,某些应用程序依赖于随机数生成以保持公平。 但是,在以太坊中,随机数的生成非常困难,并且有一些陷阱值得考虑。
Using chain attributes such as block.timestamp
, blockhash
, and block.difficulty
can seem like a good idea, as they often produce pseudorandom values. The problem, however, lies in the ability of a miner to modify these values. For example, in a gambling app with a multimillion-dollar jackpot, there’s sufficient incentive for a miner to generate many alternative blocks, only choosing the block that’ll result in a jackpot for the miner. Of course, it comes at a substantial cost to control the blockchain like that, but if the stakes are high enough, this can certainly be done.
使用诸如block.timestamp
, blockhash
和block.difficulty
类的链属性似乎是个好主意,因为它们通常会产生伪随机值。 然而,问题在于矿工修改这些值的能力。 例如,在具有数百万美元头奖的赌博应用中,矿工有足够的动力来生成许多替代区块,只选择会导致矿工头奖的区块。 当然,要像这样控制区块链会付出巨大的代价,但是如果赌注足够高,那肯定可以做到。
To avoid miner manipulation in random-number generation, there are a few solutions:
为了避免在随机数生成中操纵矿工,有一些解决方案:
- A commitment scheme such as RANDAO, a DAO where the random number is generated by all participants in the DAO 承诺方案,例如RANDAO,DAO,其中随机数由DAO中的所有参与者生成
- External sources via oracles — e.g., Oraclize 通过Oracle的外部资源-例如Oraclize
- Using Bitcoin block hashes, as the network is more decentralized and blocks are more expensive to mine 使用比特币区块哈希,因为网络更加分散,区块的开采成本更高
缺少针对签名重放攻击的保护 (Missing protection against signature-replay attacks)
Sometimes in smart contracts, it’s necessary to perform signature verification to improve usability and gas cost. However, consideration needs to be taken when implementing signature verification.
有时在智能合约中,有必要执行签名验证以提高可用性和气体成本。 但是,在实施签名验证时需要考虑。
To protect against signature-replay attacks, the contract should only be allowing new hashes to be processed. This prevents malicious users from replaying another users signature multiple times.
为了防止签名重放攻击,合同应仅允许处理新的哈希。 这样可以防止恶意用户多次重播另一个用户的签名。
To be extra safe with signature verification, follow these recommendations:
为了更加安全地进行签名验证,请遵循以下建议:
- Store every message hash processed by the contract — then check messages hashes against the existing ones before executing the function 存储合同处理的每个消息哈希,然后在执行功能之前对照现有哈希检查消息哈希
- Include the address of the contract in the hash to ensure the message is only used in a single contract 在哈希中包括合同的地址,以确保消息仅在单个合同中使用
- Never generate the message hash including the signature. See “Signature malleability.” 切勿生成包含签名的消息哈希。 请参阅“签名延展性”。
违反要求 (Requirement violation)
The require()
method is meant to validate conditions, such as inputs or contract state variables, or to validate return values from external contract calls. For validating external calls, inputs can be provided by callers or they can be returned by callees. In the case an input violation has occured by the return value of a callee, likely one of two things has gone wrong:
require()
方法用于验证条件,例如输入或合同状态变量,或验证来自外部合同调用的返回值。 为了验证外部呼叫,可以由呼叫者提供输入,也可以由被叫者返回输入。 如果被调用方的返回值发生输入冲突,则可能是以下两种情况之一出了问题:
- There’s a bug in the contract that provided the input. 合同中有一个错误提供了输入。
- The requirement condition is too strong. 要求条件太强。
To solve this issue, first consider whether the requirement condition is too strong. If necessary, weaken it to allow any valid external input. If the problem isn’t the requirement condition, there must be a bug in the contract providing external input. Ensure this contract is not providing invalid inputs.
为了解决这个问题,首先要考虑需求条件是否太强。 如有必要,请减弱它以允许任何有效的外部输入。 如果问题不是必需条件,则合同中必须存在提供外部输入的错误。 确保此合同未提供无效输入。
写入任意存储位置 (Write to an arbitrary storage location)
Only authorized addresses should have access to write to sensitive storage locations. If there isn’t proper authorization checks throughout the contract, a malicious user may be able to overwrite sensitive data. However, even if there are authorization checks for writing to sensitive data, an attacker may still be able to overwrite the sensitive data via insensitive data. This could give an attacker access to overwrite important variables such as the contract owner.
只有授权地址才能访问敏感存储位置。 如果整个合同中没有适当的授权检查,则恶意用户可能会覆盖敏感数据。 但是,即使存在用于写入敏感数据的授权检查,攻击者仍可能能够通过不敏感数据覆盖敏感数据。 这可能使攻击者可以覆盖重要的变量,例如合同所有者。
To prevent this from occurring, we not only want to protect sensitive data stores with authorization requirements, but we also want to ensure that writes to one data structure cannot inadvertently overwrite entries of another data structure.
为了防止这种情况的发生,我们不仅要保护具有授权要求的敏感数据存储,而且还要确保对一个数据结构的写入不会无意间覆盖另一数据结构的条目。
继承顺序不正确 (Incorrect inheritance order)
In Solidity, it’s possible to inherit from multiple sources, which, if not properly understood, can introduce ambiguity. This ambiguity is known as the diamond problem: If two base contracts have the same function, which one should be prioritized? Luckily, Solidity handles this problem gracefully — that is as long as the developer understands the solution.
在Solidity中,可以从多个来源继承,如果没有正确理解,可能会引起歧义。 这种歧义被称为钻石问题:如果两个基本合同具有相同的功能,那么哪个优先? 幸运的是,只要开发人员了解解决方案,Solidity就可以很好地处理此问题。
The solution Solidity provides to the diamond problem is by using reverse-C3 linearization. This means that it will linearize the inheritance from right to left so the order of inheritance matters. It’s suggested to start with more general contracts and end with more specific contracts to avoid problems.
Solidity为钻石问题提供的解决方案是使用反向C3线性化。 这意味着它将使继承从右到左线性化,因此继承的顺序很重要。 建议从更一般的合同开始,以更具体的合同结束,以避免出现问题。
具有函数类型变量的任意跳转 (Arbitrary jump with a function-type variable)
Function types are supported in Solidity. This means a variable of type function can be assigned to a function with a matching signature. The function can then be called from the variable just like any other function. Users shouldn’t be able to change the function variable, but in some cases, this is possible.
Solidity支持功能类型。 这意味着可以将类型为function的变量分配给具有匹配签名的函数。 然后可以像其他任何函数一样从变量中调用该函数。 用户不应该能够更改功能变量,但是在某些情况下,这是可能的。
If the smart contract uses certain assembly instructions, mstore
for example, an attacker may be able to point the function variable to any other function. This may give the attacker the ability to break the functionality of the contract — and, perhaps, even drain the contract funds.
如果智能合约使用某些组装指令(例如mstore
,则攻击者可能能够将功能变量指向任何其他功能。 这可能使攻击者能够破坏合同的功能,甚至可能耗尽合同资金。
Since inline assembly is a way to access the EVM at a low level, it bypasses many important safety features. So it’s important to only use assembly if it’s necessary and properly understood.
由于内联汇编是从底层访问EVM的一种方法,因此它绕过了许多重要的安全功能。 因此,只有在必要且正确理解的情况下才使用汇编程序,这一点很重要。
存在未使用的变量 (Presence of unused variables)
Although it’s allowed, it’s best practice to avoid unused variables. Unused variables can lead to a few different problems:
尽管允许,但最佳做法是避免使用未使用的变量。 未使用的变量可能会导致一些不同的问题:
- Increase in computations (unnecessary gas consumption) 计算量增加(不必要的气体消耗)
- Indication of bugs or malformed data structures 指示错误或格式错误的数据
- Decreased code readability 代码可读性降低
It’s highly recommended to remove all unused variables from a code base.
强烈建议从代码库中删除所有未使用的变量。
意外的以太平衡 (Unexpected Ether balance)
Since it’s always possible to send Ether to a contract — see “Forcibly sending Ether to a smart contract” — if a contract assumes a specific balance, it’s vulnerable to attack.
由于始终可以将以太币发送到合同中(请参阅“强制将以太币发送到智能合约”),如果合同承担特定的余额,则很容易受到攻击。
Say we have a contract that prevents all functions from executing if there’s any Ether stored in the contract. If a malicious user decides to exploit this by forcibly sending Ether, they’ll cause a DoS, rendering the contract unusable. For this reason, it’s important to never use strict equality checks for the balance of Ether in a contract.
假设我们有一个合同,如果合同中存储了任何以太币,则该合同将阻止所有功能执行。 如果恶意用户决定通过强行发送Ether来利用此漏洞,则将引发DoS,使合同不可用。 因此,重要的是永远不要对合同中的以太币余额使用严格的相等性检查。
未加密的机密 (Unencrypted secrets)
Ethereum smart contract code can always be read. Treat it as such. Even if your code is not verified on Etherscan, attackers can still decompile or even just check transactions to and from it to analyze it.
以太坊智能合约代码始终可以被读取。 这样对待它。 即使未在Etherscan上验证您的代码,攻击者仍然可以反编译甚至检查与它之间的事务以进行分析。
One example of a problem here would be having a guessing game, where the user has to guess a stored private variable to win the Ether in the contract. This is, of course, extremely trivial to exploit (to the point you shouldn’t try it because it’s almost certainly a honeypot contract that’s much trickier).
这里的一个问题示例是猜谜游戏,用户必须猜测存储的私有变量才能赢得合同中的以太币。 当然,这是极其琐碎的利用(要点是,您不应该尝试使用它,因为几乎可以肯定,蜜罐合同要棘手得多)。
Another common problem here is using unencrypted off-chain secrets, such as API keys, with Oracle calls. If your API key can be determined, malicious actors can either simply use it for themselves or take advantage of other vectors such as exhausting your allowed API calls and forcing the Oracle to return an error page which may or may not lead to problems, depending on the structure of the contract.
这里的另一个常见问题是在Oracle调用中使用未加密的链下机密(例如API密钥)。 如果可以确定您的API密钥,则恶意行为者可以简单地自己使用它或利用其他媒介,例如耗尽您允许的API调用并强迫Oracle返回错误页面,这可能会或可能不会导致问题,具体取决于合同的结构。
错误的合同检测 (Faulty contract detection)
Some contracts don’t want other contracts to interact with them. A common way to prevent this is to check whether the calling account has any code stored in it. However, contract accounts initiating calls during their construction won’t yet show they store code, effectively bypassing the contract detection.
有些合同不希望其他合同与之交互。 防止这种情况的常见方法是检查主叫帐户中是否存储了任何代码。 但是,在构建合同期间发起呼叫的合同帐户仍不会显示它们存储代码,从而有效地绕过了合同检测。
畅通无阻的区块链依赖 (Unclogged blockchain reliance)
Many contracts rely on calls happening within a certain period of time, but Ethereum can be spammed with very high Gwei transactions for a decent amount of time, relatively cheaply.
许多合约都依赖于在一定时间内发生的通话,但是以很高的Gwei交易可以在相当长的时间内(相对便宜)向以太坊发送垃圾邮件。
For example, Fomo3D (a countdown game where the last investor wins the jackpot, but each investment adds time to the countdown) was won by a user who completely clogged the blockchain for a small period of time, disallowing others from investing until the timer ran out and he won (see “DoS with block gas limit”).
例如,Fomo3D(倒数游戏,最后一位投资者赢得了头奖,但每项投资都增加了倒数时间)是由用户完全阻塞了区块链一小段时间的用户赢得的,不允许其他人在定时器运行之前进行投资出去,他就赢了(请参阅“具有限制气体限制的DoS”)。
There are many croupier gambling contracts nowadays that rely on past blockhashes to provide RNG. This isn’t a terrible source of RNG for the most part, and they even account for the pruning of hashes that happens after 256 blocks. But at that point, many of them simply null the bet. This would allow someone to make bets on many of these similarly functioning contracts with a certain result as the winner for them all, check the croupier’s submission while it’s still pending, and, if it’s unfavorable, simply clog the blockchain until pruning occurs and they could get their bets returned.
如今,有许多经纪人赌博合同依靠过去的哈希来提供RNG。 在大多数情况下,这不是可怕的RNG来源,甚至还考虑了256个块后发生的哈希删除。 但是到那时,他们中的许多人根本就没有下注。 这将使某人可以对许多这些功能相似的合同下注,并以一定的结果作为所有人的赢家,在主持人仍未决的情况下检查主持人的提交,并且如果不利,则只需阻塞区块链,直到进行修剪即可,得到他们的赌注。
不遵守标准 (Inadherence to standards)
In terms of smart contract development, it’s important to follow standards. Standards are set to prevent vulnerabilities, and ignoring them can lead to unexpected effects.
在智能合约开发方面,遵循标准很重要。 设置标准是为了防止漏洞,而忽略这些漏洞可能会导致意想不到的后果。
Take for example Binance’s original BNB token. It was marketed as an ERC20 token, but it was later pointed out it wasn’t actually ERC-20 compliant for a few reasons:
以币安的原始BNB令牌为例。 它以ERC20代币形式销售,但后来指出它实际上不符合ERC-20的原因如下:
- It prevented sending to 0x0 它阻止了发送到0x0
- It blocked transfers of 0 value 它阻止了0值的转移
- It didn’t return true or false for success or fail 它没有为成功或失败返回true或false
The main cause for concern with this improper implementation is that if it’s used with a smart contract that expects an ERC-20 token, it’ll behave in unexpected ways. It could even get locked in the contract forever.
担心这种不正确实现的主要原因是,如果将它与期望使用ERC-20令牌的智能合约一起使用,它将以意想不到的方式工作。 它甚至可能永远被锁定在合同中。
Although standards aren’t always perfect and may someday become antiquated, they foster the most secure smart contracts.
尽管标准并不总是很完美,并且有一天可能会过时,但它们促进了最安全的智能合约。
结论 (Conclusion)
As you can see, there are many ways in which your smart contracts can be exploited. It’s vital you fully understand each attack vector and vulnerability before building.
如您所见,可以通过多种方式利用智能合约。 在构建之前,您必须充分了解每个攻击媒介和漏洞,这一点至关重要。
Special thanks to RobertMCForster for many excellent contributions.
特别感谢RobertMCForster所做的许多杰出贡献。
进一步阅读 (Further Reading)
https://github.com/ethereum/wiki/wiki/Safety
https://github.com/ethereum/wiki/wiki/Safety
https://swcregistry.io/
https://swcregistry.io/
https://eprint.iacr.org/2016/1007.pdf
https://eprint.iacr.org/2016/1007.pdf
https://www.dasp.co/
https://www.dasp.co/
https://consensys.github.io/smart-contract-best-practices/
https://consensys.github.io/smart-contract-best-practices/
https://github.com/sigp/solidity-security-blog
https://github.com/sigp/solidity-security-blog
https://solidity.readthedocs.io/en/latest/bugs.html
https://solidity.readthedocs.io/en/latest/bugs.html
翻译自: https://medium.com/better-programming/the-encyclopedia-of-smart-contract-attacks-vulnerabilities-dfc1129fdaac
智能合约漏洞攻击事件