模糊测试利器 - Echidna 简介

使用 Echidna 模糊测试(Fuzzing) 提升智能合约安全性

探索使用 Echidna 提升智能合约安全性—— Echidna 是你在 Solidity 中自动检测漏洞的首选工具,确保Solidity 代码更好的健壮性和更高的安全性。

img

什么是 Fuzzing?

讲到 Fuzzing(通常称为模糊测试) 的内核,Fuzzing 包括三个关键步骤:首先,生成随机输入。接下来,使用该输入测试特定方面。如果测试用例失败,则停止并通知用户。如果通过,则使用新的输入启动新的循环进行测试。

Web3 Fuzzing 的有趣之处,特别是在智能合约中,在于我们如何处理测试某些内容。与 Web2 中传统的 Fuzzing 不同,其目标是使程序崩溃,而在 Web3 中,目标是验证用户定义的属性(不变量)。Fuzzers 生成随机输入,以检查是否可以达到指定的不正确状态。

系统属性和前提条件

系统属性概述了系统的预期行为,在给定某些前提条件时始终成立。前提条件充当屏障,指定何时应满足属性。

例子:

不变量 1:

考虑transferFunction()负责在用户之间转移代币的函数。这里的预期行为是,当从用户 A 向用户 B 转移代币时,A 的余额应该减少一定数量,而 B 的余额应该增加相同数量。然而,只有当 A 最初拥有一些代币时,这才是有意义的——这就是前提条件。如果 A 拥有一些代币,转移函数应该展现出这种行为。使用 Echidna 等工具进行 Fuzz 测试,验证这种预期行为是否成立。

不变量 2:

另一个例子是断言用户的余额不应超过代币的总供应量。如果总供应量代表所有用户余额的总和,则个别用户的余额不应超过总供应量。如果超过了,这很可能表明代码中存在错误。

Fuzzing 以其系统化的方法验证这些不变量,成为在 Web3 动态环境中确保智能合约的健壮性和安全性的宝贵工具。

Echidna 的角色

现在我们了解了 Fuzzers 在生成随机输入和测试系统属性方面的重要性,让我们深入探讨 Echidna 所扮演的关键角色。这个开源工具在人类介入的加持下,擅长在审计和人工审查过程中提供最佳结果和提高生产力。Echidna 作为基于属性的 Fuzzer,系统地生成输入,以揭示程序中的漏洞。

Echidna 的工作原理

Echidna 依赖于两个基本要素:目标合约和要测试的属性。目标合约是正在进行 Fuzzing 过程的系统——Echidna 会使用各种函数和输入不断测试它。另一方面,属性是正在被审查的特征。Echidna 与智能合约进行密切互动,评估其是否符合预期属性。实质上,我们向 Echidna 提供这两个输入,并让它挑战不变量,验证系统的关键方面。

Echidna 的关键考虑因素

  • 将 Echidna 视为外部拥有账户(EOA): 将 Echidna 视为外部实体,它像外部拥有账户一样与目标合约进行交互。这种视角有助于理解 Echidna 在测试过程中的作用。

  • Echidna 的操作:

    - Echidna 调用目标和继承合约中带有随机输入的一系列函数。

    - 它认真检查指定的属性是否成立。

    - 在这种情况下,属性解析为真值,表示预期的行为或条件。

Echidna 以其系统化的方法,证明了在确保智能合约的完整性和符合定义属性方面的宝贵价值。

安装 Echidna

如果你准备好使用 Echidna 进行智能合约安全测试,请按以下步骤开始:

GitHub 代码库:

访问 Echidna GitHub 存储库,了解其功能和文档:

img

通过 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:

  • 强调在不同输入下测试功能属性。
  • 定义特定属性并使用 Fuzzer 验证它们,通常需要使用echidna_function_name,该函数不带参数,但有布尔返回值。
  • 确保系统在不同输入场景下的一致行为。
  • 擅长发现微妙的漏洞和意外行为。

基于断言的 Fuzzing:

  • 专注于使用 assert()函数验证代码中的显式断言。
  • 在关键点插入条件进行系统测试。
  • 提供了针对特定函数的灵活性,而不改变逻辑。
  • 有效捕获意外状态,确保代码的弹性。

使用Echidna示例

基于属性的功能 Fuzzing 示例-1

目标合约: 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;
    }
}
  1. 目标合约: tokeTransfer.sol
  • 实现了 Ownable、Pausable 和 Token 合约。

  • 测试合约代码: template.sol

  • 使用 Echidna 为 Token 合约生成随机输入。

  • 旨在测试特定地址的余额是否超过预定义限制(10,000 代币)的属性。

  • Echidna 成功识别出了溢出/下溢漏洞。

运行的终端命令:

echidna template.sol --contract NewTestToken

输出:

img

观察:

Echidna 成功识别出了溢出漏洞。

基于属性的功能 Fuzzing 示例-2

目标合约: 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;
    }
}
  1. 目标合约: YourTokenContract.sol
  • 实现了 Ownable、Pausable 和 Token 合约。

  • 测试合约代码: template.sol

  • 利用 Echidna 探索合约在各种输入下的行为。

  • 旨在识别与合约暂停和所有权相关的漏洞。

  • Echidna 揭示了一个关键错误,即可以通过特定函数调用失去所有权,并且可以取消合约的暂停状态。

运行的终端命令:

echidna template.sol --contract TestYourTokenContract

输出:

img

观察:

Echidna 成功识别出了一个关键错误,即通过调用setOwner()resumeToken()函数可以失去所有权,并且可以取消合约的暂停状态。

基于断言的 Fuzzing:

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

  • 实现了 Ownable、Pausable 和 Token 合约。

测试合约代码: template.sol

  • 专注于在代码中断言特定条件。
  • 包括调用带有显式断言的函数,以验证合约的行为。
  • 提供了针对特定函数或条件的灵活性,而不改变基础逻辑。
  • 这是捕获意外代码状态并确保对各种输入和场景的弹性的强大方法。

运行基于断言的 Fuzzing 的终端命令:

echidna template.sol --contract TestYourTokenContract --test-mode assertion

输出:

img

结果:

Echidna 成功识别出了代码中isTokenPaused可以通过调用某些函数设置为 false 的关键错误。

这些示例展示了 Echidna 如何有效地发现智能合约代码中的漏洞和关键错误,使其成为安全测试的宝贵工具。

要点

  • 作为基于属性的 Fuzzer,Echidna 通过系统地针对指定属性进行测试,在智能合约安全性方面发挥了关键作用。
  • 平衡的测试方法,包括单元测试、手动测试、自动化测试工具和 Echidna,有助于构建健壮的安全姿态。
  • 基于属性的功能 Fuzzing 和基于断言的 Fuzzing 是强大的技术,可以发现微妙的漏洞并验证显式断言,增强整体安全测试策略。
  • 真实世界的示例展示了 Echidna 识别漏洞和关键错误的能力,使其成为 Solidity 开发人员的宝贵工具。

结论

通过真实世界的示例,Echidna 在智能合约安全测试中的有效性是显而易见的。基于属性的功能 Fuzzing 和基于断言的 Fuzzing 的结合提供了全面的方法来发现漏洞和关键错误。鼓励 Solidity 开发人员采用这些先进的 Fuzzing 策略,以确保其智能合约的弹性和完整性。


本翻译由 DeCert.me 协助支持, 在 DeCert 构建可信履历,为自己码一个未来。

点赞 0
收藏 3
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
翻译小组
翻译小组
0x9e64...7c84
大家看到好的文章可以在 GitHub 提 Issue: https://github.com/lbc-team/Pioneer/issues 欢迎关注我的 Twitter: https://twitter.com/UpchainDAO