Ethernaut完走の感想
はじめに
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.origin
とmsg.sender
が一致してしまいます。しかし、スマートコントラクトの中から実行するとtx.origin
はEOA、msg.sender
はコントラクトアドレスになるのでクリアできますね。
5. Token
SWC-101として知られる脆弱性を突く問題です。いわゆるオーバーフロー問題です。pragma solidity ^0.6.0;
となっているので、オーバーフローが発生し得ますね。
ということで_value
に21
を渡してやるとrequire(balances[msg.sender] - _value >= 0);
を満たすことができます。
Solidityのバージョンによって特有のバグがあるので、まずはバージョンを確認するのがよさそうです。
Discussion