Hackit

Posted on Feb 09, 2022Read on Mirror.xyz

智能合约安全初探之撸爆小学生

一、前言

春节假期的最后一天,网上突然出现了这样一条消息:RND 创立者、12岁小学生黄正,在网上发布了智能合约创建和部署的视频教程。别的暂且不谈,但这 RND 是什么呢?原来是小学生在以太坊上发布的一个 ERC20 代币 (0x1c7E83f8C581a967940DBfa7984744646AE46b29) ,合约中制定了一系列空投规则:每个地址都可以领取,领取的越早给的代币越多。这引得网友们争相领取,跑得快的也收益颇丰。这么来看的话,每个钱包都可以领取,如果自己搞100个钱包,则可以领取100次,岂不美哉?但考虑到需要分发以太币及领取空投的GAS费,叠加到一起也不是小数目。然而小学生百密一疏,合约中有一处可以利用的地方,最终导致“科学家”在链上生成众多智能合约,实现了RND代币的“批量领取”。本文基于RND代码及 Robot DAO 群友 GCC (Github地址 :github.com/GGCCCC )的代码,对本次事件进行复现。

二、RND 代币合约概览

这是一个基于ERC20标准的代币,完整代码可到etherscan上自行查阅。领取空投的具体实现如下(token.sol):

可以看到,代码中规定:如果一个地址在360天之内、且之前未曾领取过空投,则按照return_claim_number() 计算出来的数额给予RND代币空投。

看起来似乎没什么问题,但是小学生忽略了一个方面:外部控制账户(EOA,也就是俗称的“钱包”)与智能合约账户(Contract)都能够参与空投的领取。在有些场景,这算不上什么大问题;而在前段时间 GameFi 火热的时候,各种盲盒、抽卡层出不穷,这时就体现出差异了:用户使用自己钱包发起交易去抽卡,这笔交易广播出去之后,用户就无法再干预了,只能等待抽卡结果;而如果用智能合约发起交易,就可在合约里面加上逻辑判断,如果抽卡结果不是自己想要的,就可以直接revert,除了损失一点gas费之外没有任何支出,直到抽出自己想要的结果。这样的攻击案例层出不穷,也对游戏生态造成了很大破坏。有一种解决方案是加上 msg.sender == tx.origin 的判断,规定项目合约的调用者必须是交易最初发起者,这样就较好地防止了用合约来重复抽卡的行为。

言归正传,既然RND的代码中没有限制智能合约,那么我们就部署一个合约来批量撸一波空投吧!(本次测试目标为笔者部署在Rinkeby测试网的RND rinkeby.etherscan.io/token/0xcb33f7fb101e377a4b0e19fd647f391fad14d0b5,并非针对主网RND项目。批量领取合约参考了 GCC 的代码:github.com/GGCCCC/airdrop_multi_claim

三、部署Exploit合约进行批量领取

开发工具我们选用 Hardhat 。首先要安装 Node.js 。然后打开新的终端,输入以下命令来配置Hardhat 环境:

mkdir rndExploit

cd rndExploit

npm init --yes

npm install --save-dev hardhat

安装完毕后,在 rndExploit 目录中运行:

npx hardhat

用键盘选择 Create a basic sample project ,然后一路按Enter:

合约不复杂,实现了claim和transfer。但本次操作的重点在于new这个关键字,我们要用它来创建很多新合约,然后让这些新合约批量领取空投后再转移给我们。我们直接在Harhat的sample代码中修改(./contracts/Greeter.sol)。代码如下:

然后稍加修改./scripts/sample-script.js。我们把合约部署和调用写到同一个脚本中,连部署带调用,方便 。这里我们向合约传入参数 10 ,意为创建10个子合约来进行空投的领取:

最后对 ./hardhat.config.js 进行设置 ,以部署到Rinkeby测试网:

一切设置完毕后,在 rndExploit 目录中运行

npx hardhat run scripts/sample-script.js --network rinkeby

稍等片刻,命令行中显示

“Compiling 1 file with 0.8.7 Solidity compilation finished successfully Greeter deployed to: 0x0074bdB3da306F9051f172E7646d1dF2959b4443”

程序部署完毕,我们查看链上记录:

rinkeby.etherscan.io/tx/0x343a4e09f91acb5b8178471cfa7f7295765ad135f43acdd2d6e1896f4c3ed222

可以发现已经成功Claim了10次空投。

声明:本文代码均作为演示之用,不对安全性做出承诺。

HardHat工程源码见:

https://github.com/0xNezha/mutiClaimRND