Solidity 合约安全,常见漏洞 (上篇)
这个智能合约安全系列提供了一个广泛的列表,列出了在 Solidity 智能合约中容易反复出现的问题和漏洞。
Solidity 中的安全问题可以归结为智能合约的行为方式不符合它们的意图。这可以分为四大类:
- 资金被盗
- 资金被锁住或冻结在合约内
- 人们收到的奖励比预期的少(奖励被延迟或减少)。
- 人们收到的奖励比预期的多(导致通货膨胀和贬值)。
我们不可能对所有可能出错的事情做一个全面的列表。然而,正如传统的软件工程有常见的漏洞主题,如 SQL 注入、缓冲区超限和跨网站脚本,智能合约中也有反复出现的反模式(anti-pattern)。
智能合约的黑客和漏洞
把这个系列看作是一本书,不反复推敲,就不可能详细讨论每一个概念。
警告:这个安全系列有 1 万多字,所以可以把它收藏起来,分块阅读。
然而,这个系列可以作为一个清单,说明应该注意什么和研究什么。如果一个主题感觉不熟悉,这应该作为一个指标,说明值得花时间去练习识别该类漏洞。
重入攻击
每当一个智能合约调用另一个智能合约的函数,向其发送以太币,或向其代币转账,那么就有可能出现重入。
- 当以太币被转账时,接收合约的回退或接收函数被调用。这就把控制权交给了接收方。
- 一些代币协议通过调用一个预先确定的函数来提醒接收智能合约收到了代币。这就把控制流交给了接收函数。
- 当攻击合约收到控制权时,它不一定要调用交出控制权的同一个函数。它可以调用受害者智能合约中的不同函数(跨函数重入),甚至是不同的合约(跨合约重入)。
- 只读重入发生在合约处于中间状态时访问一个视图函数。
尽管重入可能是最知名的智能合约漏洞,但它只占发生的黑客的一小部分。安全研究员 Pascal Caversaccio(pcaveraccio)在 github 上保持着一个最新的重入攻击列表。截至 2023 年 4 月,该库中已经记录了 46 个重入攻击。
访问控制
这似乎是一个简单的错误,但忘记对谁可以调用一个敏感函数(如提取以太币或改变所有权)进行限制,这种情况经常发生,令人惊讶。
即使修改器已经写了,也会修改器没有正确实现的情况,比如下面的例子中缺少 require 语句:
// DO NOT USE!
modifier onlyMinter {
minters[msg.sender] == true_;
}
上面这个代码是这次审计中的一个真实例子:https://code4rena.com/reports/2023-01-rabbithole/#h-01-bad-implementation-in-minter-access-control-for-rabbitholereceipt-and-rabbitholetickets-contracts
这里是访问控制出错的另一种方式:
function claimAirdrop(bytes32 calldata proof[]) {
bool verified = MerkleProof.verifyCalldata(proof, merkleRoot, keccak256(abi.encode(msg.sender)));
require(verified, "not verified");
require(alreadyClaimed[msg.sender], "already claimed");
_transfer(msg.sender, AIRDROP_AMOUNT);
}
在此案例中,"alreadyClaimed "永远不会被设置为真,所以申领者可以发出多次调用该函数。