Web3系列教程之高级篇-9:检测看似合法但实际上是恶意的合约

在加密货币世界中,你会经常听到关于看起来合法的合约是如何成为大骗局背后的原因。黑客是如何从一个看起来合法的合约中执行恶意代码 的呢?

我们今天将学习一种方法。

会发生什么?

将有三个合约--Attack.solHelper.solGood.sol。用户将能够使用Good.sol输 入一个资格列表,它将进一步调用Helper.sol来跟踪所有符合条件的用户。

Attack.sol将被设计成可以操纵资格名单的方式,让我们看看如何

构建

让我们构建一个示例,您可以在其中体验攻击是如何发生的。

  • • 要设置一个Hardhat项目,请打开终端,在一个新的文件夹中执行这些命令
npm init --yesnpm install --save-dev hardhat
  • • 如果你不是在mac上,请做这个额外的步骤,也安装这些库 :)
npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
  • • 在你安装Hardhat的同一目录下运行。
npx hardhat
  • • 选择Create a basic sample project
  • • 对已指定的Hardhat Project root按回车键
  • • 如果你想添加一个.gitignore,请按回车键。
  • • 按回车键Do you want to install this sample project's dependencies with npm (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers)?

现在你有一个准备好的hardhat项目了!

首先,在contracts目录内创建一个新文件,名为Good.sol

//SPDX-License-Identifier: MITpragma solidity ^0.8.4;import "./Helper.sol";contract Good {    Helper helper;    constructor(address _helper) payable {        helper = Helper(_helper);    }    function isUserEligible() public view returns(bool) {        return helper.isUserEligible(msg.sender);    }    function addUserToList() public  {        helper.setUserEligible(msg.sender);    }    fallback() external {}    }

创建Good.sol后,在contracts目录内创建一个新文件,命名为Helper.sol

//SPDX-License-Identifier: MITpragma solidity ^0.8.4;contract Helper {    mapping(address => bool) userEligible;    function isUserEligible(address user) public view returns(bool) {        return userEligible[user];    }    function setUserEligible(address user) public {        userEligible[user] = true;    }    fallback() external {}}

我们将在contracts目录中创建的最后一个合约是Attack.sol

//SPDX-License-Identifier: MITpragma solidity ^0.8.4;contract Attack {    address owner;    mapping(address => bool) userEligible;    constructor() {        owner = msg.sender;    }    function isUserEligible(address user) public view returns(bool) {        if(user == owner) {            return true;        }        return false;    }    function setUserEligible(address user) public {        userEligible[user] = true;    }        fallback() external {}}

你会注意到,关于Attack.sol的事实是,它将生成与Helper.sol相同的ABI,尽管它里面有不同的代码。这是因为ABI只包含公共变量、函数和事件的函数定义。所以Attack.sol可以被类型化为Helper.sol

现在,由于Attack.sol可以被打造成Helper.sol,恶意的所有者可以用Attack.sol的地址而不是Helper.sol来部署Good.sol,用户会认为他确实在使用Helper.sol来创建资格名单。

在我们的案例中,该骗局将发生如下情况。诈骗者首先会用Attack.sol的地址部署Good.sol。然后,当用户使用addUserToList函数进入资格列表时,由于Helper.solAttack.sol中该函数的代码相同,所以工作正常。

当用户试图用他的地址调用isUserEligible时,将观察到真正的颜色,因为现在这个函数总是返回false,因为它调用Attack.solisUserEligible函数,该函数总是返回false,除非是主人自己,这本来是不应该发生的。

让我们试着写一个测试用例,看看这个骗局是否真的有效,在test文件夹中创建一个新文件,名为attack.js

const { expect } = require("chai");const { BigNumber } = require("ethers");const { ethers, waffle } = require("hardhat");describe("Attack", function () {  it("Should change the owner of the Good contract", async function () {    // Deploy the Attack contract    const attackContract = await ethers.getContractFactory("Attack");    const _attackContract = await attackContract.deploy();    await _attackContract.deployed();    console.log("Attack Contract's Address", _attackContract.address);    // Deploy the good contract    const goodContract = await ethers.getContractFactory("Good");    const _goodContract = await goodContract.deploy(_attackContract.address, {      value: ethers.utils.parseEther("3"),    });    await _goodContract.deployed();    console.log("Good Contract's Address:", _goodContract.address);    const [_, addr1] = await ethers.getSigners();    // Now lets add an address to the eligibility list    let tx = await _goodContract.connect(addr1).addUserToList();    await tx.wait();    // check if the user is eligible    const eligible = await _goodContract.connect(addr1).isUserEligible();    expect(eligible).to.equal(false);  });});

要运行这个测试,打开你的终端,指向这个级别的目录根,执行这个命令。

npx hardhat test

如果你的所有测试都通过了,这就意味着骗局成功了,用户将永远不会被确定为合格者

预防

将外部合约的地址公开,同时让你的外部合约得到验证,以便所有用户可以查看代码

创建一个新的合约,而不是在构造函数中把一个地址类型化为一个合约。因此,与其做Helper(_helper),将_helper地址类型化到一个可能是也可能不是Helper的合约中,不如使用new Helper()创建一个明确的新的helper合约实例。

案例:

contract Good {    Helper public helper;    constructor() {        helper = new Helper();    }}

看,还有很多要学的东西吧?

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章