前言
这道题跟上道比简单了点,原理唯一的区别就是如何bypass extcodesize
,而这个网上也有解决方案,EOSGame中的FOMO3D
游戏就发生过这种攻击。
OverView
首先看合约代码:
1 | pragma solidity ^0.4.24; |
可以看到我们的目的就是绕过turingTest
,modifier
声明的作用有点类似于Python中的修饰函数。它跟_
结合起来可以实现:
1 | function temp() public { |
那么关键就是绕过require(_codeLength == 0, "sorry humans only");
,根本就是bypass extcodesize
。
Attack
extcodesize的作用是return size of the code at address,经常拿来判别直接
调用者是另一个合约还是用户,因为一个合约的code size不会是0,而用户是0.
但问题就出来当合约正在执行构造函数constructor
并部署时,其extcodesize为0
,也就是说合约完全可以通过在constructor中调用方法而绕过
该判断。所以我们只要通过不断部署合约来进行攻击就可以拿到flag。
有了上篇的基础后,这里的exp也比较好写和理解,如下:
1 | pragma solidity ^0.4.24; |
这里的error_test
是为了验证上面的漏洞,不是在构造函数constructor中调用是过不了extcodesize
的判断的。
需要注意
的地方就是原合约中使用msg.sender
代入计算,而msg.sender是合约的直接调用者,这里就是我们自己的攻击合约
的地址,所以我在exp中使用了this
来代替,this在合约中表示合约本身的地址。
1 | uint256 seed = uint256(keccak256(abi.encodePacked( |
另一个点就是这里是检测msg.sender
的账户是否大于8888,而不是tx.origin。但赌中是给tx.origin
发奖金的。所以我们还需要用tx.origin
的用户去做调用CaptureTheFlag(string b64email)
。这一步可以用web3js,也可以用metamask
,用metamask的时候需要在data段里填CaptureTheFlag(string b64email)
的abi
。用web3js实现是:
1 | let Web3 = require("web3"); |
查询当前用户的余额使用如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16pragma solidity ^0.4.24;
contract Fake3D {
mapping(address => uint256) public balance;
function airDrop() public returns (bool);
}
contract GetBalance {
Fake3D mime = Fake3D(0x4082cC8839242Ff5ee9c67f6D05C4e497f63361a);
function getbalance() view public returns(uint256) {
return mime.balance(tx.origin);
}
}
当能在攻击合约里查到余额大于8888时就发起get_flag
。
在理一下流程就是:首先通过不断部署合约让自己的账户(这里采用直接用用户发起tx.origin
)的余额大于8888,然后用用户对Fake3D
合约直接发起转账交易,把金额转到攻击成功的合约地址上,最后在攻击合约中发起get_flag。CaptureTheFlag
,脑子瓦特了。。。
这里我给攻击合约转了8900,理论上那么这个攻击合约也是可以发起get_flag
的,但实际发现会报:1
Warning! Error encountered during contract execution [Reverted]
因为这个也要一定的耐心,成功率不会很高,这里就不做get flag操作了,攻击原理跟手段get到就好。。。