使用 Echidna 模糊测试(Fuzzing) 提升智能合约安全性
- 原文链接:https://allthingsfuzzy.substack.com/p/introduction-to-echidna
- 译文出自:登链翻译计划
- 译者:翻译小组 > 校对:Tiny 熊
- 本文永久链接:learnblockchain.cn/article…
探索使用 Echidna 提升智能合约安全性—— Echidna 是你在 Solidity 中自动检测漏洞的首选工具,确保Solidity 代码更好的健壮性和更高的安全性。
讲到 Fuzzing(通常称为模糊测试) 的内核,Fuzzing 包括三个关键步骤:首先,生成随机输入。接下来,使用该输入测试特定方面。如果测试用例失败,则停止并通知用户。如果通过,则使用新的输入启动新的循环进行测试。
Web3 Fuzzing 的有趣之处,特别是在智能合约中,在于我们如何处理测试某些内容。与 Web2 中传统的 Fuzzing 不同,其目标是使程序崩溃,而在 Web3 中,目标是验证用户定义的属性(不变量)。Fuzzers 生成随机输入,以检查是否可以达到指定的不正确状态。
系统属性概述了系统的预期行为,在给定某些前提条件时始终成立。前提条件充当屏障,指定何时应满足属性。
例子:
考虑transferFunction()
负责在用户之间转移代币的函数。这里的预期行为是,当从用户 A 向用户 B 转移代币时,A 的余额应该减少一定数量,而 B 的余额应该增加相同数量。然而,只有当 A 最初拥有一些代币时,这才是有意义的——这就是前提条件。如果 A 拥有一些代币,转移函数应该展现出这种行为。使用 Echidna 等工具进行 Fuzz 测试,验证这种预期行为是否成立。
另一个例子是断言用户的余额不应超过代币的总供应量。如果总供应量代表所有用户余额的总和,则个别用户的余额不应超过总供应量。如果超过了,这很可能表明代码中存在错误。
Fuzzing 以其系统化的方法验证这些不变量,成为在 Web3 动态环境中确保智能合约的健壮性和安全性的宝贵工具。
现在我们了解了 Fuzzers 在生成随机输入和测试系统属性方面的重要性,让我们深入探讨 Echidna 所扮演的关键角色。这个开源工具在人类介入的加持下,擅长在审计和人工审查过程中提供最佳结果和提高生产力。Echidna 作为基于属性的 Fuzzer,系统地生成输入,以揭示程序中的漏洞。
Echidna 依赖于两个基本要素:目标合约和要测试的属性。目标合约是正在进行 Fuzzing 过程的系统——Echidna 会使用各种函数和输入不断测试它。另一方面,属性是正在被审查的特征。Echidna 与智能合约进行密切互动,评估其是否符合预期属性。实质上,我们向 Echidna 提供这两个输入,并让它挑战不变量,验证系统的关键方面。
将 Echidna 视为外部拥有账户(EOA): 将 Echidna 视为外部实体,它像外部拥有账户一样与目标合约进行交互。这种视角有助于理解 Echidna 在测试过程中的作用。
Echidna 的操作:
- Echidna 调用目标和继承合约中带有随机输入的一系列函数。
- 它认真检查指定的属性是否成立。
- 在这种情况下,属性解析为真值,表示预期的行为或条件。
Echidna 以其系统化的方法,证明了在确保智能合约的完整性和符合定义属性方面的宝贵价值。
如果你准备好使用 Echidna 进行智能合约安全测试,请按以下步骤开始:
GitHub 代码库:
访问 Echidna GitHub 存储库,了解其功能和文档:
通过 Docker 安装:
使用以下命令轻松从 Trail of Bits 拉取 Echidna Docker 镜像:
docker pull trailofbits/eth-security-toolbox
然后,交互式地运行它,同时挂载你当前的目录:
docker run -it -v "$PWD":/home/training trailofbits/eth-security-toolbox
通过二进制文件安装:
对于那些喜欢使用二进制文件的人,你可以在官方 GitHub 发布页面找到最新版本:
其他信息:
有关安装的详细指南和对 Echidna 的更多见解,请参阅官方文档:
如果由于 crytic 模块而出现错误:
python3 -m venv venv
source venv/bin/activate # On Windows, use 'venv\Scripts\activate'
python -m pip install crytic-compile
Solc-select 安装
git clone https://github.com/crytic/solc-select.git
cd solc-select
python3 setup.py install
如果使用虚拟环境,必须设置以便 echidna 能够成功运行。
solc-select install 0.8.0
export SOLC_VERSION="0.8.0"
echo $SOLC_VERSION
基于属性的功能 Fuzzing:
基于断言的 Fuzzing:
目标合约: tokenTransfer.sol
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
contract NewOwnerContract {
address public newOwner = msg.sender;
modifier onlyNewOwner() {
require(msg.sender == newOwner, "NewOwnerContract: Caller is not the owner.");
_;
}
}
contract PausableToken is NewOwnerContract {
bool private _isPaused;
function isPaused() public view returns (bool) {
return _isPaused;
}
function pause() public onlyNewOwner {
_isPaused = true;
}
function resume() public onlyNewOwner {
_isPaused = false;
}
modifier whenNotPaused() {
require(!_isPaused, "PausableToken: Contract is paused.");
_;
}
}
contract TokenTransfer is NewOwnerContract, PausableToken {
mapping(address => uint256) public accountBalances;
function performTransfer(address to, uint256 amount) public whenNotPaused {
unchecked {
accountBalances[msg.sender] -= amount;
accountBalances[to] += amount;
}
}
}
Echidna 测试合约代码: template.sol
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
import "./tokenTransfer.sol";
/// @dev Run the template with
/// ```
/// solc-select use 0.8.0
/// echidna template.sol
/// ```
contract NewTestToken is TokenTransfer {
address echidnaTester = tx.origin;
constructor() public {
accountBalances[echidnaTester] = 10000;
}
function echidna_TestBalance() public view returns (bool) {
// added the property
return accountBalances[echidnaTester] <= 10000;
}
}
实现了 Ownable、Pausable 和 Token 合约。
测试合约代码: template.sol
使用 Echidna 为 Token 合约生成随机输入。
旨在测试特定地址的余额是否超过预定义限制(10,000 代币)的属性。
Echidna 成功识别出了溢出/下溢漏洞。
运行的终端命令:
echidna template.sol --contract NewTestToken
输出:
观察:
Echidna 成功识别出了溢出漏洞。
目标合约: YourTokenContract.sol
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
contract TokenOwner {
address public ownerAddress = msg.sender;
function setOwner() public {
ownerAddress = msg.sender;
}
modifier onlyOwner() {
require(ownerAddress == msg.sender, "Unauthorized access");
_;
}
}
contract PausableToken is TokenOwner {
bool isTokenPaused;
function getTokenPausedStatus() public view returns (bool) {
return isTokenPaused;
}
function pauseToken() public onlyOwner {
isTokenPaused = true;
}
function resumeToken() public onlyOwner {
isTokenPaused = false;
}
modifier whenTokenNotPaused() {
require(!isTokenPaused, "Token functionality is paused");
_;
}
}
contract TokenHandler is TokenOwner, PausableToken {
mapping(address => uint256) public userTokenBalances;
function transferTokens(address to, uint256 amount) public whenTokenNotPaused {
userTokenBalances[msg.sender] -= amount;
userTokenBalances[to] += amount;
}
}
Echidna 测试合约代码: template.sol
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
import "./YourTokenContract.sol";
contract TestYourTokenContract is PausableToken {
constructor() {
pauseToken(); // pause the token functionality
ownerAddress = address(0); // lose ownership
}
// Now we are in a state where the test contract is no longer the owner of the system.
function echidna_testTokenCannotBeUnpaused() public view returns (bool) {
return isTokenPaused;
}
}
YourTokenContract.sol
实现了 Ownable、Pausable 和 Token 合约。
测试合约代码: template.sol
利用 Echidna 探索合约在各种输入下的行为。
旨在识别与合约暂停和所有权相关的漏洞。
Echidna 揭示了一个关键错误,即可以通过特定函数调用失去所有权,并且可以取消合约的暂停状态。
运行的终端命令:
echidna template.sol --contract TestYourTokenContract
输出:
观察:
Echidna 成功识别出了一个关键错误,即通过调用setOwner()
和resumeToken()
函数可以失去所有权,并且可以取消合约的暂停状态。
Echidna 测试合约: template.sol
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
import "./YourTokenContract.sol";
contract TestYourTokenContract is PausableToken {
constructor() {
pauseToken(); // pause the token functionality
ownerAddress = address(0); // lose ownership
}
// Now we are in a state where the test contract is no longer the owner of the system.
function testTokenCannotBeUnpaused() public {
assert(isTokenPaused);
}
}
目标合约: YourTokenContract.sol
测试合约代码: template.sol
运行基于断言的 Fuzzing 的终端命令:
echidna template.sol --contract TestYourTokenContract --test-mode assertion
输出:
结果:
Echidna 成功识别出了代码中isTokenPaused
可以通过调用某些函数设置为 false 的关键错误。
这些示例展示了 Echidna 如何有效地发现智能合约代码中的漏洞和关键错误,使其成为安全测试的宝贵工具。
通过真实世界的示例,Echidna 在智能合约安全测试中的有效性是显而易见的。基于属性的功能 Fuzzing 和基于断言的 Fuzzing 的结合提供了全面的方法来发现漏洞和关键错误。鼓励 Solidity 开发人员采用这些先进的 Fuzzing 策略,以确保其智能合约的弹性和完整性。
本翻译由 DeCert.me 协助支持, 在 DeCert 构建可信履历,为自己码一个未来。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!