🔖

Ethernaut完走の感想

2023/05/08に公開

はじめに

EthernautはSolidityで記述された脆弱性のあるスマートコントラクトへの攻撃を成功させることでクリアするゲームです。2023年5月8日現在で29個のレベルが楽しめます。サービス自体は2018年頃からあるようですが、今でも頻繁に更新されています。

そんな老舗サービスに今更ながら挑戦したので各レベルの感想をつらつらと書いていこうと思います。詳細な解答例・解説は検索するとたくさん出てくるのでそちらをご参照くださいm(_ _)m。また、ネタバレが含まれるのでご注意ください。

0. Hello Ethernaut

使い方の説明だけのページだと思っていたのですが、最後にもう一回よく見てみるとチュートリアルとしてクリアする課題がありました。Get new instanceボタンを押すとコントラクトがデプロイされます。説明にある通り、ブラウザのデベロッパーツールのコンソールでJavaScriptを入力することで問題が解けます。

以下のように説明があるので、その通りコンソールに await contract.info()と入力しエンターします。

Look into the level's info method contract.info() or await contract.info() if you're using Chrome v62.

すると、以下のような結果が出力されます。

'You will find what you need in info1().'

このレベルはこんな感じで一つの関数を実行すると次に実行する関数が指示されます。解き終わったらSubmitボタンをクリックします。正解だとコンソールにその旨メッセーがじ表示され、Go to the next levelボタンに変化します。また、Webページの内容もそのレベルで大事だったポイントに関する解説に変わります。このレベルでは内部的に使用されていたコントラクトが表示されます。

1. Fallback

ownerを奪取しろということなのでコントラクトを見てみるとreceive()の中でownerが上書きできるのでこの関数を利用しましょう。

2. Fallout

レベルの画像が大ヒントですね。そう、コンストラクタ名(Fal1out)がコントラクト名(Fallout)と一致していないです。なので、デプロイ後も普通の関数として実行できるのでownerを奪取できますね。これはSWC-118として知られる脆弱性です。こういうミスが発生するので、今からコントラクトを作るのであればconstructorキーワードをつかってコンストラクタを記述するべきです。

3. Coin Flip

以下のようなコードで生成されるsideを予測する課題です。正解ならconsecutiveWinsがインクリメントされますが、間違うとゼロにリセットされるので10連続正解する必要があります。

    uint256 coinFlip = blockValue / FACTOR;
    bool side = coinFlip == 1 ? true : false;

blockValueはブロックハッシュ、FACTORは2の255乗なので確率は1/2です。10連続正解する確率は1/1024。これは確実に運ではなく実力で解く必要がありそうです。

ポイントはブロックハッシュが予測できるかどうかです。実はこれはSWC-120として知られる脆弱性で、ブロックハッシュを乱数として使ってはいけません。というのも、マイナーはこの値を調整できるからです。

ただ、この課題を解く私はマイナーではないのでどうしようと思ってこっそり解答例を見て納得しました。そう、2. まではコンソールから関数を実行するだけでOKでしたが、このレベルからは攻撃用のコントラクトを記述する必要があったのです。

攻撃コントラクトからこのレベルのコントラクトを呼び出すと必然的にブロックハッシュは同じになります。つまり、攻撃コントラクトの内部でsideが計算できるので、それをそのまま_guessとして渡してあげれば完了ですね。

4. Telephone

またownerを奪取する課題です。chageOwnerを実行すればいいですがtx.origin != msg.senderという条件をクリアする必要があります。EOAから実行するとtx.originmsg.senderが一致してしまいます。しかし、スマートコントラクトの中から実行するとtx.originはEOA、msg.senderはコントラクトアドレスになるのでクリアできますね。

5. Token

SWC-101として知られる脆弱性を突く問題です。いわゆるオーバーフロー問題です。pragma solidity ^0.6.0;となっているので、オーバーフローが発生し得ますね。

ということで_value21を渡してやるとrequire(balances[msg.sender] - _value >= 0);を満たすことができます。

Solidityのバージョンによって特有のバグがあるので、まずはバージョンを確認するのがよさそうです。

関連記事

GitHubで編集を提案

Discussion