Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
SoliditySecurity — Solidity セキュリティ | Kitploit
ツール/GitHubGitHub/al1ex/soliditysecurity
脆弱性分析コード分析学習と教育厳選リソース
GitHubal1ex/soliditysecurity

SoliditySecurity

Solidity セキュリティ

リポジトリを見る
355年前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

これは何か

この投稿は、Solidity開発者が過去に犯した過ちを比較的詳細かつ最新の情報に基づいて紹介する入門記事であり、将来の開発者が歴史を繰り返さないようにすることを目的としています。

目次

1. 再入

  • 脆弱性
  • 予防策
  • 実世界の例: The DAO

2. 算術オーバーフロー/アンダーフロー

  • 脆弱性
  • 予防策
  • 実世界の例: PoWHCとバッチ転送オーバーフロー (CVE-2018-10299)

3. 予期しないEther

  • 脆弱性
  • 予防策
  • 実世界の例: 不明

4. Delegatecall

  • 脆弱性
  • 予防策
  • 実世界の例: Parity Multisig Wallet (2度目のハック)

5. デフォルトの可視性

  • 脆弱性
  • 予防策
  • 実世界の例: Parity MultiSig Wallet (最初のハック)

6. エントロピーの錯覚

  • 脆弱性
  • 予防策
  • 実世界の例: PRNGコントラクト

7. 外部コントラクト参照

  • 脆弱性
  • 予防策
  • 実世界の例: 再入ハニーポット

8. ショートアドレス/パラメータ攻撃

  • 脆弱性
  • 予防策
  • 実世界の例: 不明

9. チェックされていないCALL戻り値

  • 脆弱性
  • 予防策
  • 実世界の例: EtherpotとKing of the Ether

10. 競合状態 / フロントランニング

  • 脆弱性
  • 予防策
  • 実世界の例: ERC20とBancor

11. サービス拒否 (DoS)

  • 脆弱性
  • 予防策
  • 実世界の例: GovernMental

12. ブロックタイムスタンプ操作

  • 脆弱性
  • 予防策
  • 実世界の例: GovernMental

13. コンストラクタに注意

  • 脆弱性
  • 予防策
  • 実世界の例: Rubixi

14. 未初期化ストレージポインタ

  • 脆弱性
  • 予防策
  • 実世界の例: ハニーポット: OpenAddressLotteryとCryptoRoulette

15. 浮動小数点数と数値精度

  • 脆弱性
  • 予防策
  • 実世界の例: Ethstick

16. tx.origin認証

  • 脆弱性
  • 予防策
  • 実世界の例: 不明

Ethereumの癖

  • キーレスEther
  • ワンタイムアドレス
  • 単一トランザクションのエアドロップ

興味深い暗号関連のハック/バグ一覧

参考文献 / 関連資料

  • Ethereum Wiki - 安全性
  • Solidity Docs - セキュリティに関する考慮事項
  • Consensus - Ethereumスマートコントラクトのベストプラクティス
  • Ethereumのセキュリティ脆弱性、ハック、およびその修正の歴史
  • 分散型アプリケーションセキュリティプロジェクト (DASP) 2018年のトップ10
  • Ethereumスマートコントラクトへの攻撃の調査
  • Ethereumスマートコントラクトのセキュリティ
  • Underhanded Solidity Contestから学んだ教訓

1. 再入

Ethereumスマートコントラクトの特徴の1つは、他の外部コントラクトのコードを呼び出して利用できることです。コントラクトは通常、etherも扱うため、さまざまな外部ユーザーアドレスにetherを送信することがよくあります。外部コントラクトを呼び出したり、アドレスにetherを送信したりする操作では、コントラクトが外部呼び出しを行う必要があります。これらの外部呼び出しは攻撃者に乗っ取られる可能性があり、攻撃者はコントラクトにさらなるコード(フォールバック関数を介するなど)を実行させます。これには、コントラクト自身への呼び戻しも含まれます。このようにして、コード実行はコントラクトに"再入"します。この種の攻撃は、悪名高いDAOハックで使用されました。

再入攻撃の詳細については、Reentrancy Attack On Smart Contracts と Consensus - Ethereum Smart Contract Best Practices を参照してください。

脆弱性

この攻撃は、コントラクトが未知のアドレスにetherを送信したときに発生する可能性があります。攻撃者は、フォールバック関数 に悪意のあるコードを含むコントラクトを外部アドレスに注意深く構築できます。したがって、コントラクトがこのアドレスにetherを送信すると、悪意のあるコードが呼び出されます。通常、悪意のあるコードは脆弱なコントラクト上の関数を実行し、開発者が想定していない操作を行います。"再入"という名前は、外部の悪意のあるコントラクトが脆弱なコントラクト上の関数を呼び戻し、脆弱なコントラクト上の任意の場所でコード実行に"再入"するという事実に由来します。

これを明確にするために、預金者が週に1 etherしか引き出せないEthereumの金庫として機能する、シンプルな脆弱なコントラクトを考えてみましょう。

EtherStore.sol:```solidity contract EtherStore {

root@kitploit:~
uint256 public withdrawalLimit = 1 ether;
mapping(address => uint256) public lastWithdrawTime;
mapping(address => uint256) public balances;

function depositFunds() public payable {
    balances[msg.sender] += msg.value;
}

function withdrawFunds (uint256 _weiToWithdraw) public {
    require(balances[msg.sender] >= _weiToWithdraw);
    // limit the withdrawal
    require(_weiToWithdraw <= withdrawalLimit);
    // limit the time allowed to withdraw
    require(now >= lastWithdrawTime[msg.sender] + 1 weeks);
    require(msg.sender.call.value(_weiToWithdraw)());
    balances[msg.sender] -= _weiToWithdraw;
    lastWithdrawTime[msg.sender] = now;
}

}

root@kitploit:~
このコントラクトには2つの公開関数があります。`depositFunds()` と `withdrawFunds()` です。`depositFunds()` 関数は単純に送信者の残高を増加させます。`withdrawFunds()` 関数は、送信者が引き出すweiの金額を指定できるようにします。この関数は、要求された引き出し金額が1 ether未満であり、かつ過去1週間以内に引き出しが行われていない場合にのみ成功します。本当にそうでしょうか?...

脆弱性は、ユーザーに要求された金額のetherを送信する \[17\] 行目にあります。次のようなコントラクトを作成する悪意のある攻撃者を考えてみてください。

Attack.sol:```solidity
import "EtherStore.sol";

contract Attack {
  EtherStore public etherStore;

  // initialise the etherStore variable with the contract address
  constructor(address _etherStoreAddress) {
      etherStore = EtherStore(_etherStoreAddress);
  }

  function pwnEtherStore() public payable {
      // attack to the nearest ether
      require(msg.value >= 1 ether);
      // send eth to the depositFunds() function
      etherStore.depositFunds.value(1 ether)();
      // start the magic
      etherStore.withdrawFunds(1 ether);
  }

  function collectEther() public {
      msg.sender.transfer(this.balance);
  }

  // fallback function - where the magic happens
  function () payable {
      if (etherStore.balance > 1 ether) {
          etherStore.withdrawFunds(1 ether);
      }
  }
}

この悪意のあるコントラクトが、私たちの EtherStore コントラクトをどのように悪用できるか見てみましょう。攻撃者は、EtherStore コントラクトのアドレスをコンストラクタのパラメータとして、上記のコントラクト (ここではアドレス 0x0...123 とします) を作成します。これにより、パブリック変数 etherStore が初期化され、攻撃したいコントラクトを指すようになります。

次に攻撃者は、1 以上の任意の量の ether を指定して pwnEtherStore() 関数を呼び出します。この例では 1 ether とします。この例では、他の多数のユーザーがこのコントラクトに ether を預けているため、現在の残高は 10 ether であると仮定します。その場合、以下のような流れになります:

  1. Attack.sol - 行 [15] - EtherStore コントラクトの depositFunds() 関数が、msg.value を 1 ether (および大量の gas) として呼び出されます。送信者 (msg.sender) は、私たちの悪意のあるコントラクト (0x0...123) になります。したがって、balances[0x0..123] = 1 ether となります。

  2. Attack.sol - 行 [17] - 次に、悪意のあるコントラクトは EtherStore コントラクトの withdrawFunds() 関数をパラメータ 1 ether で呼び出します。これまでの引き出しがないため、(EtherStore コントラクトの行 [12]-[16] の) すべての要件を満たします。

  3. EtherStore.sol - 行 [17] - コントラクトは 1 ether を悪意のあるコントラクトに送り返します。

  4. Attack.sol - 行 [25] - 悪意のあるコントラクトに送られた ether により、フォールバック関数が実行されます。

  5. - コントラクトの残高合計は でしたが、現在は なので、この if 文は成立します。

最終結果として、攻撃者は EtherStore コントラクトから (1 を除く) すべての ether を、単一のトランザクションで瞬時に引き出したことになります。

予防テクニック

スマートコントラクトにおける潜在的な再入 (re-entrancy) 脆弱性を回避するのに役立つ、一般的なテクニックがいくつかあります。1 つ目は、( 可能な限り) 外部コントラクトに ether を送信するときに、組み込みの transfer() 関数を使用することです。transfer 関数は外部呼び出しに 2300 gas しか送らないため、宛先アドレス/コントラクトが別のコントラクトを呼び出す (つまり、送信元コントラクトに再入する) のに十分ではありません。

2 つ目は、状態変数を変更するすべてのロジックが、コントラクトから ether を送信する (または任意の外部呼び出しを行う) 前に実行されるようにすることです。EtherStore の例では、EtherStore.sol の行 [18] と [19] を行 [17] の前に置く必要があります。不明なアドレスへの外部呼び出しを実行するコードは、局所化された関数またはコード実行の最後の操作として配置するのが良い習慣です。これは checks-effects-interactions パターンとして知られています。

3 つ目は、ミューテックス (mutex) を導入することです。つまり、コード実行中にコントラクトをロックし、再入呼び出しを防ぐ状態変数を追加します。

これらのテクニックをすべて (3 つすべては不要ですが、デモンストレーションの目的で行います) EtherStore.sol に適用すると、再入のないコントラクトになります:```solidity contract EtherStore {

root@kitploit:~
// initialise the mutex
bool reEntrancyMutex = false;
uint256 public withdrawalLimit = 1 ether;
mapping(address => uint256) public lastWithdrawTime;
mapping(address => uint256) public balances;

function depositFunds() public payable {
    balances[msg.sender] += msg.value;
}

function withdrawFunds (uint256 _weiToWithdraw) public {
    require(!reEntrancyMutex);
    require(balances[msg.sender] >= _weiToWithdraw);
    // limit the withdrawal
    require(_weiToWithdraw <= withdrawalLimit);
    // limit the time allowed to withdraw
    require(now >= lastWithdrawTime[msg.sender] + 1 weeks);
    balances[msg.sender] -= _weiToWithdraw;
    lastWithdrawTime[msg.sender] = now;
    // set the reEntrancy mutex before the external call
    reEntrancyMutex = true;
    msg.sender.transfer(_weiToWithdraw);
    // release the mutex after the external call
    reEntrancyMutex = false;
}

}

root@kitploit:~
<h3 id="re-example">実世界の例: The DAO</h3>

[The DAO](https://en.wikipedia.org/wiki/The_DAO_(organization))(分散型自律組織)は、イーサリアムの初期開発において発生した主要なハッキングの1つでした。当時、このコントラクトは1億5,000万米ドル以上を保持していました。再入がこの攻撃において主要な役割を果たし、最終的にEthereum Classic(ETC)を生み出したハードフォークにつながりました。DAOエクスプロイトの優れた分析については、[Phil Daian氏の投稿](http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/)を参照してください。

<h2 id="ouflow"><span id="SP-2">2. 算術オーバーフロー/アンダーフロー</span></h2>

イーサリアム仮想マシン(EVM)は、整数に対して固定サイズのデータ型を規定しています。これは、整数変数が表現できる数値の範囲が限られていることを意味します。例えば、`uint8` は \[0,255\] の範囲の数値しか格納できません。`uint8` に `256` を格納しようとすると、結果は `0` になります。注意を払わない場合、Solidityの変数は、ユーザー入力がチェックされておらず、格納先のデータ型の範囲外の数値を生じる計算が実行されると、悪用される可能性があります。

算術オーバーフロー/アンダーフローの詳細については、[スマートコントラクトを保護する方法](https://medium.com/loom-network/how-to-secure-your-smart-contracts-6-solidity-vulnerabilities-and-how-to-avoid-them-part-1-c33048d4d17d)、[Ethereumスマートコントラクトのベストプラクティス](https://consensys.github.io/smart-contract-best-practices/known_attacks/#integer-overflow-and-underflow)、および[イーサリアム、Solidity、整数オーバーフロー: 1970年のようにブロックチェーンをプログラミングする](https://randomoracle.wordpress.com/2018/04/27/ethereum-solidity-and-integer-overflows-programming-blockchains-like-1970/)を参照してください。

<h3 id="ou-vuln">脆弱性</h3>

オーバーフロー/アンダーフローは、固定サイズの変数が、その変数のデータ型の範囲外の数値(またはデータ)を格納する必要がある操作が実行されると発生します。

例えば、値として `0` を格納している `uint8`(8ビットの符号なし整数、つまり正の値のみ)変数から `1` を引くと、結果は `255` になります。これがアンダーフローです。`uint8` の範囲を下回る数値を代入したため、結果は*ラップアラウンド*し、`uint8` が格納できる最大の数値になります。同様に、`uint8` に `2^8=256` を加えても、`uint` 全体を一周することになるため、変数は変わりません(数学好きの方には、三角関数の角度に $2\pi$ を加えるのと似ています。$\sin(x) = \sin(x+2\pi)$)。データ型の範囲より大きい数値を加えることをオーバーフローと呼びます。明確にするため、現在ゼロ値の `uint8` に `257` を加えると、結果は `1` になります。固定型変数は循環的であると考えると参考になることがあります。格納可能な最大の数値を超える数値を加えるとゼロから再び始まり、ゼロの場合も同様に逆方向に働きます(0から引き算するほど、最大値から下に数えていくことになります)。

この種の数値的な落とし穴により、攻撃者はコードを悪用して意図しないロジックフローを引き起こすことができます。例えば、以下のタイムロック契約を考えてみてください。

TimeLock.sol:```solidity
contract TimeLock {

    mapping(address => uint) public balances;
    mapping(address => uint) public lockTime;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
        lockTime[msg.sender] = now + 1 weeks;
    }

    function increaseLockTime(uint _secondsToIncrease) public {
        lockTime[msg.sender] += _secondsToIncrease;
    }

    function withdraw() public {
        require(balances[msg.sender] > 0);
        require(now > lockTime[msg.sender]);
        uint transferValue = balances[msg.sender];
        balances[msg.sender] = 0;
        msg.sender.transfer(transferValue);
    }
}

このコントラクトはタイムボールトのように機能するように設計されており、ユーザーはイーサをコントラクトに預けることができ、少なくとも1週間はそこにロックされます。ユーザーは選択によって待機時間を1週間より長く延長することもできますが、一度預けると、ユーザーは自分のイーサが少なくとも1週間は安全にロックされていることを確信できます。本当にそうでしょうか?...

ユーザーが秘密鍵を渡すことを強制された場合(人質状況を想像してください)、このようなコントラクトは、短期間ではイーサを入手できないようにするのに役立つかもしれません。ユーザーがこのコントラクトに 100 ether をロックし、攻撃者に鍵を渡した場合、攻撃者は lockTime に関係なく、オーバーフローを利用してイーサを受け取ることができます。

攻撃者は、現在鍵を保持しているアドレスの現在の lockTime を確認できます(これは公開変数です)。これを userLockTime と呼びましょう。その後、攻撃者は increaseLockTime 関数を呼び出し、引数として 2^256 - userLockTime という数値を渡します。この数値は現在の userLockTime に加算され、オーバーフローを引き起こして lockTime[msg.sender] を 0 にリセットします。攻撃者はその後、単に withdraw 関数を呼び出して報酬を取得できます。

別の例を見てみましょう。これは Ethernaut Challanges からのものです。

ネタバレ注意: まだEthernautチャレンジをやっていない場合、これはそのレベルの1つの解法を示しています。```solidity pragma solidity ^0.4.18;

contract Token {

mapping(address => uint) balances; uint public totalSupply;

function Token(uint _initialSupply) { balances[msg.sender] = totalSupply = _initialSupply; }

function transfer(address _to, uint _value) public returns (bool) { require(balances[msg.sender] - _value >= 0); balances[msg.sender] -= _value; balances[_to] += _value; return true; }

function balanceOf(address _owner) public constant returns (uint balance) { return balances[_owner]; } }

root@kitploit:~
これは、`transfer()` 関数を採用したシンプルなトークンコントラクトであり、参加者がトークンを移動できるようにするものです。このコントラクトのエラーに気づけますか?

問題は `transfer()` 関数にあります。\[13\] 行目の require ステートメントは、アンダーフローを利用してバイパスできます。残高を持たないユーザーを考えてみましょう。そのユーザーは、ゼロ以外の任意の `_value` を指定して `transfer()` 関数を呼び出し、\[13\] 行目の require ステートメントを通過できます。これは、`balances[msg.sender]` がゼロ(かつ `uint256`)であるため、任意の正の金額(`2^256` を除く)を減算すると、上記で説明したアンダーフローにより正の数になるからです。これは \[14\] 行目にも当てはまり、残高には正の数が加算されます。したがって、この例では、アンダーフローの脆弱性によって無料のトークンを獲得できてしまいます。

<h3 id="ou-prevention">防止技術</h3>

(現時点での)アンダーフロー/オーバーフローの脆弱性を防ぐための一般的な手法は、標準の算術演算子(加算、減算、乗算)を置き換える数学ライブラリを使用または構築することです(除算はオーバーフロー/アンダーフローを引き起こさず、EVM は 0 による除算でリバートするため除外されます)。

[OppenZepplin](https://github.com/OpenZeppelin/zeppelin-solidity) は、Ethereum コミュニティが活用できる安全なライブラリの構築と監査において素晴らしい仕事をしています。特に、[Safe Math Library](https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/math/SafeMath.sol) は、アンダー/オーバーフローの脆弱性を回避するために使用するリファレンスまたはライブラリです。

これらのライブラリが Solidity でどのように使用されるかを示すために、Open Zepplin の `SafeMath` ライブラリを使用して `TimeLock` コントラクトを修正しましょう。オーバーフローのないコントラクトは次のようになります。```solidity
library SafeMath {

  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

contract TimeLock {
    using SafeMath for uint; // use the library for uint type
    mapping(address => uint256) public balances;
    mapping(address => uint256) public lockTime;

    function deposit() public payable {
        balances[msg.sender] = balances[msg.sender].add(msg.value);
        lockTime[msg.sender] = now.add(1 weeks);
    }

    function increaseLockTime(uint256 _secondsToIncrease) public {
        lockTime[msg.sender] = lockTime[msg.sender].add(_secondsToIncrease);
    }

    function withdraw() public {
        require(balances[msg.sender] > 0);
        require(now > lockTime[msg.sender]);
        uint transferValue = balances[msg.sender];
        balances[msg.sender] = 0;
        msg.sender.transfer(transferValue);
    }
}

すべての標準的な算術演算は、SafeMath ライブラリで定義されたものに置き換えられていることに注意してください。TimeLock コントラクトは、アンダーフロー/オーバーフローを引き起こす可能性のある操作をもはや実行しません。

実世界の例: PoWHC とバッチ転送オーバーフロー (CVE-2018–10299)

ある 4chan のグループが、Solidity で書かれたイーサリアム上のポンジ・スキームを構築するのは素晴らしいアイデアだと思い込みました。彼らはそれを Proof of Weak Hands Coin (PoWHC) と名付けました。残念ながら、コントラクトの作者はオーバーフロー/アンダーフローを見たことがなかったようで、その結果、866 ether がコントラクトから引き出されてしまいました。アンダーフローがどのように発生するか(上記の Ethernaut チャレンジとそれほど変わらない)についての優れた概要は、Eric Banisadar の投稿 にあります。

一部の開発者は、いくつかの ERC20 トークンコントラクトに batchTransfer() 関数も実装しました。その実装にはオーバーフローが含まれていました。この投稿 がそれを説明していますが、タイトルは誤解を招くと思います。というのも、これは ERC20 標準とは何の関係もなく、むしろ一部の ERC20 トークンコントラクトに脆弱な batchTransfer() 関数が実装されているからです。

3. 予期しない Ether

通常、ether がコントラクトに送信される場合、フォールバック関数またはコントラクトに記述された別の関数のいずれかを実行する必要があります。これには2つの例外があり、コードを一切実行せずに ether がコントラクト内に存在することができます。送信されるすべての ether についてコードの実行に依存するコントラクトは、ether が強制的にコントラクトに送信される攻撃に対して脆弱になる可能性があります。

これに関する詳細は、スマートコントラクトを保護する方法: 6 と Solidity セキュリティパターン - ether をコントラクトに強制送金する を参照してください。

脆弱性

不変条件チェック は、正しい状態遷移を強制したり操作を検証したりするのに役立つ一般的な防御的プログラミング手法です。この手法では、不変条件(変更されるべきでないメトリクスやパラメータ)のセットを定義し、単一(または複数)の操作の後にこれらの不変条件が変更されていないことをチェックします。これは、チェックされる不変条件が実際に不変条件である場合には、通常は良い設計です。不変条件の一例は、固定発行 ERC20 トークンの totalSupply です。いかなる関数もこの不変条件を変更すべきではないため、transfer() 関数に totalSupply が変更されていないことを確認するチェックを追加して、関数が期待どおりに動作することを確認できます。

特に、使用したくなるかもしれない明らかな 不変条件 が1つありますが、 実際には外部ユーザーによって操作される可能性があります(スマートコントラクトで 定められたルールに関係なく)。それがコントラクトに保存されている現在の ether です。 多くの場合、開発者が Solidity を学び始めたばかりのとき、 コントラクトは payable 関数を介してのみ ether を受け入れたり取得したりできるという誤解があります。 この誤解により、コントラクト内の ether 残高について誤った仮定を持つコントラクトが生まれ、 さまざまな脆弱性につながる可能性があります。この脆弱性の決定的な証拠は、this.balance の (誤った)使用です。 後で説明するように、this.balance の誤った使用は、 この種の深刻な脆弱性につながる可能性があります。

コントラクトに ether を(強制的に)送信する方法は、payable 関数を使用したりコントラクト上でコードを実行したりせずに、2つあります。以下に列挙します。

自己破壊 / 自殺

あらゆるコントラクトは、selfdestruct(address) 関数を実装できます。この関数は、コントラクトアドレスからすべてのバイトコードを削除し、そこに保存されているすべての ether をパラメータで指定されたアドレスに送信します。この指定されたアドレスがコントラクトである場合、フォールバックを含むいかなる関数も呼び出されません。したがって、selfdestruct() 関数は、コントラクトに存在するコードに関係なく、任意のコントラクトに ether を強制的に送信するために使用できます。これには、payable 関数を持たないコントラクトも含まれます。つまり、攻撃者は selfdestruct() 関数を持つコントラクトを作成し、それに ether を送信し、selfdestruct(target) を呼び出すことで、target コントラクトに ether を強制的に送信させることができます。Martin Swende は、self-destruct オペコードのいくつかの癖(Quirk #2)と、クライアントノードが誤った不変条件をチェックしており、それがクライアントの壊滅的な全滅につながる可能性があったことの説明をまとめた優れたブログ記事を公開しています。

事前に送金された Ether

コントラクトが selfdestruct() 関数を使用したり payable 関数を呼び出したりせずに ether を取得できる2つ目の方法は、コントラクトアドレスに ether を事前にロードしておくことです。コントラクトアドレスは決定論的であり、実際には、コントラクトを作成するアドレスと、そのコントラクトを作成するトランザクションの nonce の keccak256(SHA3 と同義とされることもあります)ハッシュから計算されます。具体的には、address = sha3(rlp.encode([account_address,transaction_nonce])) という形式です(これに関する楽しいユースケースについては Keyless Ether を参照してください)。つまり、誰でもコントラクトが作成される前にそのコントラクトアドレスを計算できるため、そのアドレスに ether を送信できます。コントラクトが実際に作成されると、ゼロ以外の ether 残高を持つことになります。

上記の知識を踏まえると、発生し得るいくつかの落とし穴を見ていきましょう。

次の非常にかんたんなコントラクトを考えてみましょう。

EtherGame.sol:```solidity contract EtherGame {

root@kitploit:~
uint public payoutMileStone1 = 3 ether;
uint public mileStone1Reward = 2 ether;
uint public payoutMileStone2 = 5 ether;
uint public mileStone2Reward = 3 ether;
uint public finalMileStone = 10 ether;
uint public finalReward = 5 ether;

mapping(address => uint) redeemableEther;
// users pay 0.5 ether. At specific milestones, credit their accounts
function play() public payable {
    require(msg.value == 0.5 ether); // each play is 0.5 ether
    uint currentBalance = this.balance + msg.value;
    // ensure no players after the game as finished
    require(currentBalance <= finalMileStone);
    // if at a milestone credit the players account
    if (currentBalance == payoutMileStone1) {
        redeemableEther[msg.sender] += mileStone1Reward;
    }
    else if (currentBalance == payoutMileStone2) {
        redeemableEther[msg.sender] += mileStone2Reward;
    }
    else if (currentBalance == finalMileStone ) {
        redeemableEther[msg.sender] += finalReward;
    }
    return;
}

function claimReward() public {
    // ensure the game is complete
    require(this.balance == finalMileStone);
    // ensure there is a reward to give
    require(redeemableEther[msg.sender] > 0);
    uint transferValue = redeemableEther[msg.sender];
    redeemableEther[msg.sender] = 0;
    msg.sender.transfer(transferValue);
}

}

root@kitploit:~
このコントラクトは単純なゲームを表しています(これは自然と[race-conditions](#race-conditions)を引き起こすでしょう)。プレイヤーは`0.5 ether`の量子をコントラクトに送り、3つのマイルストーンのいずれかに最初に到達するプレイヤーになることを期待します。マイルストーンはetherで表示されます。最初にマイルストーンに到達したプレイヤーは、ゲーム終了時にetherの一部を請求できます。ゲームは最終マイルストーン(`10 ether`)に到達すると終了し、ユーザーは報酬を請求できます。

`EtherGame`コントラクトの問題は、\[14\]行目(および関連して\[16\]行目)と\[32\]行目での`this.balance`の不適切な使用に起因します。悪意のある攻撃者は、`selfdestruct()`関数(前述)を介して少量のether、たとえば`0.1 ether`を強制的に送信し、将来のプレイヤーがマイルストーンに到達するのを防ぐことができます。すべての正当なプレイヤーは`0.5 ether`単位でしか送信できないため、`this.balance`はもはや半整数ではなくなり、`0.1 ether`の分も加算されることになります。これにより、\[18\]行目、\[21\]行目、\[24\]行目のすべてのif条件が真になるのを防ぎます。

さらに悪いことに、マイルストーンを逃した復讐心に燃える攻撃者は、`10 ether`(またはコントラクトの残高を`finalMileStone`を超えて押し上げるのに相当する量のether)を強制的に送信し、コントラクト内のすべての報酬を永久にロックすることができます。これは、\[32\]行目のrequire(つまり`this.balance`が`finalMileStone`より大きい)により、`claimReward()`関数が常にrevertするためです。

<h3 id="ether-prevention">予防技術</h3>

この脆弱性は、通常、`this.balance`の誤用から発生します。コントラクトのロジックは、可能な限りコントラクトの残高の正確な値に依存しないようにすべきです。なぜなら、それは人為的に操作される可能性があるからです。`this.balance`に基づいてロジックを適用する場合は、予期しない残高を考慮に入れるようにしてください。

預けられたetherの正確な値が必要な場合は、payable関数でインクリメントされる独自の変数を使用して、預けられたetherを安全に追跡する必要があります。この変数は、`selfdestruct()`呼び出しによって強制的に送信されたetherの影響を受けません。

このことを踏まえると、`EtherGame`コントラクトの修正版は次のようになります:```solidity
contract EtherGame {

    uint public payoutMileStone1 = 3 ether;
    uint public mileStone1Reward = 2 ether;
    uint public payoutMileStone2 = 5 ether;
    uint public mileStone2Reward = 3 ether;
    uint public finalMileStone = 10 ether;
    uint public finalReward = 5 ether;
    uint public depositedWei;

    mapping (address => uint) redeemableEther;

    function play() public payable {
        require(msg.value == 0.5 ether);
        uint currentBalance = depositedWei + msg.value;
        // ensure no players after the game as finished
        require(currentBalance <= finalMileStone);
        if (currentBalance == payoutMileStone1) {
            redeemableEther[msg.sender] += mileStone1Reward;
        }
        else if (currentBalance == payoutMileStone2) {
            redeemableEther[msg.sender] += mileStone2Reward;
        }
        else if (currentBalance == finalMileStone ) {
            redeemableEther[msg.sender] += finalReward;
        }
        depositedWei += msg.value;
        return;
    }

    function claimReward() public {
        // ensure the game is complete
        require(depositedWei == finalMileStone);
        // ensure there is a reward to give
        require(redeemableEther[msg.sender] > 0);
        uint transferValue = redeemableEther[msg.sender];
        redeemableEther[msg.sender] = 0;
        msg.sender.transfer(transferValue);
    }
 }

ここでは、既知の預け入れられたetherを追跡する新しい変数 depositedWei を作成したところです。そして、要件とテストを実行するのはこの変数です。this.balance への参照がもはやないことに注意してください。

実世界の例: 不明

まだ、これが実際に悪用された例は見つけていません。ただし、悪用可能なコントラクトの例は、Underhanded Solidity Contest でいくつか紹介されています。

4. Delegatecall

CALL および DELEGATECALL オペコードは、Ethereum開発者がコードをモジュール化できるようにする上で役立ちます。コントラクトへの標準的な外部メッセージ呼び出しは CALL オペコードによって処理され、コードは外部コントラクト/関数のコンテキストで実行されます。DELEGATECALL オペコードは標準のメッセージ呼び出しと同一ですが、ターゲットアドレスで実行されるコードが呼び出し元コントラクトのコンテキストで実行される点と、msg.sender と msg.value が変更されない点が異なります。この機能により、開発者が将来のコントラクトのために再利用可能なコードを作成できる ライブラリ の実装が可能になります。

これら2つのオペコードの違いは単純で直感的ですが、DELEGATECALL の使用は予期しないコード実行につながる可能性があります。

詳細については、Ethereum Stack Exchange の質問、Solidity ドキュメント、および How to Secure Your Smart Contracts: 6 を参照してください。

脆弱性

DELEGATECALL のコンテキストを保持する性質により、脆弱性のないカスタムライブラリの構築は、考えられているほど簡単ではないことが証明されています。ライブラリ自体のコードは安全で脆弱性がなくても、別のアプリケーションのコンテキストで実行されると、新しい脆弱性が発生する可能性があります。フィボナッチ数を使用した、かなり複雑な例を見てみましょう。

フィボナッチ数列および類似の形式の数列を生成できる次のライブラリを考えてみましょう。 FibonacciLib.sol[^1]```solidity // library contract - calculates fibonacci-like numbers; contract FibonacciLib { // initializing the standard fibonacci sequence; uint public start; uint public calculatedFibNumber;

root@kitploit:~
// modify the zeroth number in the sequence
function setStart(uint _start) public {
    start = _start;
}

function setFibonacci(uint n) public {
    calculatedFibNumber = fibonacci(n);
}

function fibonacci(uint n) internal returns (uint) {
    if (n == 0) return start;
    else if (n == 1) return start + 1;
    else return fibonacci(n - 1) + fibonacci(n - 2);
}

}

root@kitploit:~
このライブラリは、数列における *n* 番目のフィボナッチ数を生成する関数を提供します。ユーザーは数列の開始番号 (`start`) を変更し、この新しい数列における *n* 番目のフィボナッチ類似数を計算できます。

では、このライブラリを利用するコントラクトを考えてみましょう。

`FibonacciBalance.sol`:```solidity
contract FibonacciBalance {

    address public fibonacciLibrary;
    // the current fibonacci number to withdraw
    uint public calculatedFibNumber;
    // the starting fibonacci sequence number
    uint public start = 3;
    uint public withdrawalCounter;
    // the fibonancci function selector
    bytes4 constant fibSig = bytes4(sha3("setFibonacci(uint256)"));

    // constructor - loads the contract with ether
    constructor(address _fibonacciLibrary) public payable {
        fibonacciLibrary = _fibonacciLibrary;
    }

    function withdraw() {
        withdrawalCounter += 1;
        // calculate the fibonacci number for the current withdrawal user
        // this sets calculatedFibNumber
        require(fibonacciLibrary.delegatecall(fibSig, withdrawalCounter));
        msg.sender.transfer(calculatedFibNumber * 1 ether);
    }

    // allow users to call fibonacci library functions
    function() public {
        require(fibonacciLibrary.delegatecall(msg.data));
    }
}

このコントラクトは、参加者がコントラクトからイーサを引き出すことを可能にします。引き出せるイーサの量は、参加者の引き出し順に対応するフィボナッチ数に等しくなります。すなわち、最初の参加者は1イーサ、2番目の参加者も1、3番目は2、4番目は3、5番目は5というように続きます(コントラクトの残高が、引き出そうとしているフィボナッチ数未満になるまで)。

このコントラクトには、説明が必要ないくつかの要素があります。まず、一見興味深い変数 fibSig があります。これは文字列 "setFibonacci(uint256)" のKeccak(SHA-3)ハッシュの最初の4バイトを保持します。これは関数セレクタとして知られており、スマートコントラクトのどの関数を呼び出すかを指定するために calldata に入れられます。これは[21]行目の delegatecall 関数で、setFibonacci(uint256) 関数を実行したいことを指定するために使われます。delegatecall の2番目の引数は、関数に渡すパラメータです。次に、FibonacciLib ライブラリのアドレスがコンストラクタで正しく参照されていることを前提とします(外部コントラクト参照のセクションでは、この種のコントラクト参照の初期化に関連する潜在的な脆弱性について説明します)。

このコントラクトに何かエラーがあるかお気づきですか?これをRemixに入れて、イーサを満載し、withdraw() を呼び出すと、おそらくリバートするでしょう。

状態変数 start がライブラリとメインの呼び出し側コントラクトの両方で使用されていることにお気づきかもしれません。ライブラリコントラクトでは、start はフィボナッチ数列の開始位置を指定するために使用され、0 に設定されています。一方、FibonacciBalance コントラクトでは 3 に設定されています。また、FibonacciBalance コントラクトのフォールバック関数が、すべての呼び出しをライブラリコントラクトに渡すことを許可しており、その結果、ライブラリコントラクトの setStart() 関数も呼び出せることにお気づきかもしれません。コントラクトの状態が保持されることを思い出すと、この関数によってローカルの FibonnacciBalance コントラクト内の start 変数の状態を変更できるように思えるかもしれません。もしそうなら、結果として得られる calculatedFibNumber が start 変数に依存するため(ライブラリコントラクトにあるように)、より多くのイーサを引き出すことが可能になります。実際には、setStart() 関数は FibonacciBalance コントラクトの start 変数を変更しません(変更できません)。このコントラクトの根本的な脆弱性は、単に 変数を変更することよりもはるかに深刻です。

実際の問題を説明する前に、状態変数(storage変数)がコントラクト内で実際にどのように保存されるかを理解するために、少し寄り道をしましょう。状態変数、すなわちstorage変数(個々のトランザクションをまたいで永続する変数)は、コントラクトで導入された順にスロットへ順番に配置されます。(ここにはいくつかの複雑な点があり、より深く理解するために、読者にはストレージ内の状態変数のレイアウトを読むことをお勧めします)。

例として、ライブラリコントラクトを見てみましょう。これには start と calculatedFibNumber という2つの状態変数があります。最初の変数は start で、コントラクトのストレージの slot[0](すなわち最初のスロット)に保存されます。2番目の変数 calculatedFibNumber は、次の利用可能なストレージスロット slot[1] に配置されます。関数 setStart() を見ると、入力を受け取り、start をその入力値に設定します。したがって、この関数は、setStart() 関数に与えた入力を slot[0] に設定することになります。同様に、setFibonacci() 関数は、calculatedFibNumber を fibonacci(n) の結果に設定します。これもまた、ストレージの slot[1] に fibonacci(n) の値を設定しているだけです。

次に、FibonacciBalance コントラクトを見てみましょう。ここではストレージの slot[0] が fibonacciLibrary アドレスに対応し、slot[1] が calculatedFibNumber に対応します。脆弱性はこの誤ったマッピングに存在します。delegatecall はコントラクトのコンテキストを保持します。つまり、delegatecall を介して実行されるコードは、呼び出し側コントラクトの状態(すなわちストレージ)に対して作用するということです。

さて、withdraw() の [21] 行目で、fibonacciLibrary.delegatecall(fibSig,withdrawalCounter) を実行していることに注目してください。これは setFibonacci() 関数を呼び出します。先ほど説明したように、この関数はストレージの slot[1] を変更します。現在のコンテキストでは、これは calculatedFibNumber です。これは期待どおりです(すなわち、実行後、calculatedFibNumber が調整されます)。しかし、FibonacciLib コントラクトの start 変数がストレージの slot[0] に配置されており、現在のコントラクトではそれが fibonacciLibrary アドレスであることを思い出してください。つまり、fibonacci() 関数は予期しない結果を返すことになります。これは、start(slot[0])を参照するためです。現在の呼び出しコンテキストでは、slot[0] は アドレスであり、 として解釈すると非常に大きくなることがよくあります。したがって、 関数は、 が返す 量のイーサを保有していない可能性が高いため、リバートするでしょう。

さらに悪いことに、FibonacciBalance コントラクトは、[26] 行目のフォールバック関数を介して、ユーザーが fibonacciLibrary のすべての関数を呼び出すことを許可しています。先ほど説明したように、これには setStart() 関数も含まれます。この関数によって誰でもストレージの slot[0] を変更または設定できることを説明しました。この場合、ストレージの slot[0] は fibonacciLibrary アドレスです。したがって、攻撃者は悪意のあるコントラクトを作成し(その例は以下に示します)、アドレスを uint に変換して(これはPythonで int('<address>',16) を使えば簡単にできます)、setStart(<attack_contract_address_as_uint>) を呼び出すことができます。これにより、fibonacciLibrary が攻撃コントラクトのアドレスに変更されます。その後、ユーザーが withdraw() またはフォールバック関数を呼び出すたびに、悪意のあるコントラクトが実行され(コントラクトの残高全体を盗むことができます)、それは fibonacciLibrary の実際のアドレスを変更したためです。そのような攻撃コントラクトの例は、次のようになります。```solidity contract Attack { uint storageSlot0; // corresponds to fibonacciLibrary uint storageSlot1; // corresponds to calculatedFibNumber

root@kitploit:~
// fallback - this will run if a specified function is not found
function() public {
    storageSlot1 = 0; // we set calculatedFibNumber to 0, so that if withdraw
    // is called we don't send out any ether.
    <attacker_address>.transfer(this.balance); // we take all the ether
}

}

root@kitploit:~
注意:この攻撃コントラクトは、ストレージ `slot[1]` を変更することで `calculatedFibNumber` を改変しています。原理的には、攻撃者は任意の他のストレージスロットを変更して、このコントラクトに対してあらゆる種類の攻撃を行うことができます。すべての読者には、これらのコントラクトを [Remix](https://remix.ethereum.org) に取り込み、さまざまな攻撃コントラクトや状態変更をこれらの `delegatecall` 関数を通じて試すことをお勧めします。

また、`delegatecall` が状態を保持すると言う場合、コントラクトの変数名について話しているのではなく、その名前が指す実際のストレージスロットについて話していることに注意することも重要です。この例からわかるように、単純なミスが、攻撃者によるコントラクト全体とそのetherの乗っ取りにつながる可能性があります。

<h3 id="dc-prevention">予防手法</h3>

Solidity はライブラリコントラクトを実装するための `library` キーワードを提供しています(詳細は [Solidity Docs](http://solidity.readthedocs.io/en/latest/contracts.html?highlight=library#libraries) を参照)。これにより、ライブラリコントラクトがステートレスで自己破壊不可能であることが保証されます。ライブラリをステートレスに強制することで、このセクションで示したストレージコンテキストの複雑さを軽減できます。ステートレスなライブラリはまた、攻撃者がライブラリのコードに依存するコントラクトに影響を与えるためにライブラリの状態を直接変更する攻撃を防ぎます。
一般的な経験則として、`DELEGATECALL` を使用する際は、ライブラリコントラクトと呼び出し側コントラクトの両方の可能な呼び出しコンテキストに細心の注意を払い、可能な限りステートレスなライブラリを構築してください。

<h3 id="dc-example">実世界の例: Parity Multisig Wallet(2回目のハック)</h3>

2回目の Parity Multisig Wallet ハックは、適切に記述されたライブラリコードが意図しないコンテキストで実行された場合に、そのコンテキストが悪用される例です。このハックには優れた解説が多数あります。例えば、Anthony Akentiev による概要 [Parity MultiSig Hacked. Again](https://medium.com/chain-cloud-company-blog/parity-multisig-hack-again-b46771eaa838)、この [stack exchange の質問](https://ethereum.stackexchange.com/questions/30128/explanation-of-parity-library-suicide/30130)、そして [An In-Depth Look at the Parity Multisig Bug](http://hackingdistributed.com/2017/07/22/deep-dive-parity-bug/) です。

これらの参考文献に加えて、悪用されたコントラクトを調べてみましょう。ライブラリとウォレットのコントラクトは、parity の github の [ここ](https://github.com/paritytech/parity/blob/b640df8fbb964da7538eef268dffc125b081a82f/js/src/contracts/snippets/enhanced-wallet.sol) にあります。

このコントラクトの関連する側面を見てみましょう。ここには重要な2つのコントラクト、ライブラリコントラクトとウォレットコントラクトが含まれています。

ライブラリコントラクトは、```solidity
contract WalletLibrary is WalletEvents {

  ...

  // throw unless the contract is not yet initialized.
  modifier only_uninitialized { if (m_numOwners > 0) throw; _; }

  // constructor - just pass on the owner array to the multiowned and
  // the limit to daylimit
  function initWallet(address[] _owners, uint _required, uint _daylimit) only_uninitialized {
    initDaylimit(_daylimit);
    initMultiowned(_owners, _required);
  }

  // kills the contract sending everything to `_to`.
  function kill(address _to) onlymanyowners(sha3(msg.data)) external {
    suicide(_to);
  }

  ...

}

そしてウォレットコントラクト、```solidity contract Wallet is WalletEvents {

...

// METHODS

// gets called when no other function matches function() payable { // just being sent some cash? if (msg.value > 0) Deposit(msg.sender, msg.value); else if (msg.data.length > 0) _walletLibrary.delegatecall(msg.data); }

...

// FIELDS address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; }

root@kitploit:~
`Wallet` コントラクトは、すべての呼び出しをデリゲートコールを介して `WalletLibrary` コントラクトに渡していることに注目してください。このコードスニペット内の定数 `_walletLibrary` アドレスは、実際にデプロイされた `WalletLibrary` コントラクト(`0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4` に存在)のプレースホルダーとして機能します。

これらのコントラクトの意図された動作は、コードベースと主要な機能が `WalletLibrary` コントラクトにある、シンプルで低コストにデプロイ可能な `Wallet` コントラクトを持つことでした。残念ながら、`WalletLibrary` コントラクトそれ自体がコントラクトであり、自身の状態を保持しています。これがなぜ問題になるのかお分かりでしょうか?

`WalletLibrary` コントラクト自体に呼び出しを送ることが可能です。具体的には、`WalletLibrary` コントラクトは初期化され、所有されるようになる可能性があります。あるユーザーがこれを行い、`WalletLibrary` コントラクトに対して `initWallet()` 関数を呼び出して、ライブラリコントラクトの所有者になったのです。同じユーザーはその後、`kill()` 関数を呼び出しました。ユーザーはライブラリコントラクトの所有者だったため、modifier の検証を通過し、ライブラリコントラクトは自滅(suicide)しました。存在するすべての `Wallet` コントラクトはこのライブラリコントラクトを参照しており、この参照を変更する手段を持たないため、ether を引き出す機能を含むすべての機能が、`WalletLibrary` コントラクトとともに失われました。もっと直接的に言えば、このタイプのすべての Parity マルチシグウォレット内の全 ether は、即座に失われるか、恒久的に回復不可能になります。

<h2 id="visibility"><span id="SP-5">5. デフォルトの可視性</span></h2>

Solidity の関数には、関数をどのように呼び出せるかを定める可視性指定子があります。可視性によって、関数がユーザーによって外部から呼び出せるか、他の派生コントラクトから呼び出せるか、内部からのみ呼び出せるか、外部からのみ呼び出せるかが決まります。可視性指定子は4つあり、詳細は [Solidity ドキュメント](http://solidity.readthedocs.io/en/latest/contracts.html?highlight=library#visibility-and-getters) に記載されています。関数はデフォルトで `public` となり、ユーザーが外部から呼び出すことができます。可視性指定子の誤った使用は、このセクションで説明するように、スマートコントラクトに壊滅的な脆弱性をもたらす可能性があります。

<h3 id="visibility-vuln">脆弱性</h3>

関数のデフォルトの可視性は `public` です。したがって、可視性を指定しない関数は外部のユーザーから呼び出し可能になります。  問題が発生するのは、開発者が private(またはコントラクト内部からのみ呼び出し可能)にすべき関数で、誤って可視性指定子を省略した場合です。

簡単な例をすばやく見てみましょう。```solidity
contract HashForEther {

    function withdrawWinnings() {
        // Winner if the last 8 hex characters of the address are 0.
        require(uint32(msg.sender) == 0);
        _sendWinnings();
     }

     function _sendWinnings() {
         msg.sender.transfer(this.balance);
     }
}

このシンプルなコントラクトは、アドレス推測バウンティゲームとして機能するように設計されています。コントラクトの残高を獲得するには、ユーザーは最後の8桁の16進文字が0であるEthereumアドレスを生成する必要があります。取得できたら、WithdrawWinnings() 関数を呼び出してバウンティを受け取ることができます。

残念ながら、関数の可視性が指定されていません。特に、_sendWinnings() 関数は public であるため、任意のアドレスがこの関数を呼び出してバウンティを盗むことができます。

予防技術

コントラクト内のすべての関数の可視性を、意図的に public にする場合でも常に指定することは良い習慣です。最近のSolidityのバージョンでは、明示的な可視性が設定されていない関数に対してコンパイル中に警告が表示されるようになり、この習慣を促進するのに役立っています。

実世界の例: Parity MultiSig Wallet(最初のハッキング)

最初のParityマルチシグハッキングでは、主に3つのウォレットから約 $31M 相当のEtherが盗まれました。この攻撃がどのように行われたかを的確にまとめたものは、Haseeb Qureshiによるこの投稿にあります。

基本的に、マルチシグウォレット(こちらで見つけることができます)は、コア機能を含むライブラリコントラクトを呼び出すベースの Wallet コントラクトから構築されています(実世界の例: Parity Multisig(2回目のハッキング) で説明されたように)。ライブラリコントラクトには、以下のスニペットからわかるように、ウォレットを初期化するコードが含まれています。```solidity contract WalletLibrary is WalletEvents {

...

// METHODS

...

// constructor is given number of sigs required to do protected "onlymanyowners" transactions // as well as the selection of addresses capable of confirming them. function initMultiowned(address[] _owners, uint _required) { m_numOwners = _owners.length + 1; m_owners[1] = uint(msg.sender); m_ownerIndex[uint(msg.sender)] = 1; for (uint i = 0; i < _owners.length; ++i) { m_owners[2 + i] = uint(_owners[i]); m_ownerIndex[uint(_owners[i])] = 2 + i; } m_required = _required; }

...

// constructor - just pass on the owner array to the multiowned and // the limit to daylimit function initWallet(address[] _owners, uint _required, uint _daylimit) { initDaylimit(_daylimit); initMultiowned(_owners, _required); } }

root@kitploit:~
Notice that neither of the functions have explicitly specified a visibility. Both functions default to `public`. The `initWallet()` function is called in the wallets constructor and sets the owners for the multi-sig wallet as can be seen in the `initMultiowned()` function. Because these functions were accidentally left `public`, an attacker was able to call these functions on deployed contracts, resetting the ownership to the attackers address. Being the owner, the attacker then drained the wallets of all their ether, to the tune of \$31M.

<h2 id="entropy"><span id="SP-6">6. エントロピー錯覚</span></h2>

Ethereumブロックチェーン上のすべてのトランザクションは、決定論的な状態遷移操作です。つまり、すべてのトランザクションはEthereumエコシステムのグローバル状態を変更し、不確実性のない計算可能な方法で変更します。これは最終的に、ブロックチェーンエコシステム内にはエントロピーやランダム性の源泉が存在しないことを意味します。Solidityには `rand()` 関数はありません。分散型エントロピー(ランダム性)を実現することはよく知られた問題であり、これに対処するための多くのアイデアが提案されています(例えば、[RandDAO](https://github.com/randao/randao) や、Vitalikがこの [投稿](https://vitalik.ca/files/randomness.html) で説明しているハッシュチェーンを使用する方法を参照してください)。

<h3 id="entropy-vuln">脆弱性</h3>

Ethereumプラットフォーム上に構築された最初のコントラクトのいくつかは、ギャンブルを中心としたものでした。根本的に、ギャンブルには不確実性(賭ける対象)が必要であり、そのためブロックチェーン(決定論的なシステム)上にギャンブルシステムを構築することはかなり困難です。不確実性はブロックチェーンの外部ソースから得なければならないことは明らかです。これはピア間の賭けでは可能ですが(例えば、[コミット・リビール技術](https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract) を参照)、*ハウス*(ブラックジャックやルーレットのように)として機能するコントラクトを実装したい場合には、はるかに困難です。一般的な落とし穴は、ハッシュ、タイムスタンプ、ブロック番号、ガスリミットなどの将来のブロック変数を使用することです。これらはブロックをマイニングするマイナーによって制御されるため、真にランダムではありません。例えば、次のブロックのハッシュが偶数で終わる場合に黒の数字を返すロジックを持つルーレットスマートコントラクトを考えてみましょう。マイナー(またはマイニングプール)は黒に \\$1M 賭けることができます。次のブロックを解決してハッシュが奇数で終わることを発見した場合、ブロック報酬と手数料が $1M 未満であると仮定すると、ブロックハッシュが偶数になる解決策を見つけるまで、ブロックを公開せずに別のブロックをマイニングするでしょう。過去または現在の変数を使用すると、Martin Swendeが彼の優れた [ブログ記事](http://martin.swende.se/blog/Breaking_the_house.html) で実証しているように、さらに壊滅的になる可能性があります。さらに、ブロック変数のみを使用すると、疑似乱数がブロック内のすべてのトランザクションで同じになるため、攻撃者はブロック内で多数のトランザクションを行うことで勝利を倍増させることができます(最大ベット額がある場合)。

<h3 id="entropy-prevention">予防手法</h3>

エントロピー(ランダム性)の源泉はブロックチェーンの外部でなければなりません。これは、[コミット・リビール](https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract) のようなシステムを使ったピア間、または信頼モデルを参加者のグループに変更すること([RandDAO](https://github.com/randao/randao) のように)によって実現できます。また、ランダム性オラクルとして機能する中央集権的なエンティティを介して行うこともできます。ブロック変数は(一般に、いくつかの例外はありますが)、マイナーによって操作される可能性があるため、エントロピーの源泉として使用すべきではありません。

<h3 id="entropy-example">実世界の例: PRNGコントラクト</h3>

Arseny Reutovは、何らかの疑似乱数生成器(PRNG)を使用している3649の稼働中のスマートコントラクトを分析した後、悪用される可能性のある43のコントラクトを発見し、[ブログ記事](https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620) を書きました。

<h2 id="contract-reference"><span id="SP-7">7. 外部コントラクト参照</span></h2>

Ethereumの *グローバルコンピュータ* の利点の1つは、コードを再利用し、ネットワーク上にすでにデプロイされたコントラクトと対話できることです。その結果、多くのコントラクトが外部コントラクトを参照し、一般的な操作では外部メッセージ呼び出しを使用してこれらのコントラクトと対話します。これらの外部メッセージ呼び出しは、悪意のある行為者の意図を、いくつかの明白でない方法で隠すことができます。これについては後で説明します。

<h3 id="cr-vuln">脆弱性</h3>

Solidityでは、アドレスのコードがキャストされるコントラクト型を表しているかどうかに関係なく、任意のアドレスをコントラクトとしてキャストできます。これは、特にコントラクトの作成者が悪意のあるコードを隠そうとしている場合に、欺瞞的になる可能性があります。例でこれを説明しましょう:

基本的に [Rot13](https://github.com/al1ex/soliditysecurity/blob/HEAD/www.wikipedia.com/rot13) 暗号を実装するコードを考えてみましょう。

`Rot13Encryption.sol`:```solidity
//encryption contract
contract Rot13Encryption {

   event Result(string convertedString);

    //rot13 encrypt a string
    function rot13Encrypt (string text) public {
        uint256 length = bytes(text).length;
        for (var i = 0; i < length; i++) {
            byte char = bytes(text)[i];
            //inline assembly to modify the string
            assembly {
                char := byte(0,char) // get the first byte
                if and(gt(char,0x6D), lt(char,0x7B)) // if the character is in [n,z], i.e. wrapping.
                { char:= sub(0x60, sub(0x7A,char)) } // subtract from the ascii number a by the difference char is from z.
                if iszero(eq(char, 0x20)) // ignore spaces
                {mstore8(add(add(text,0x20), mul(i,1)), add(char,13))} // add 13 to char.
            }
        }
        emit Result(text);
    }

    // rot13 decrypt a string
    function rot13Decrypt (string text) public {
        uint256 length = bytes(text).length;
        for (var i = 0; i < length; i++) {
            byte char = bytes(text)[i];
            assembly {
                char := byte(0,char)
                if and(gt(char,0x60), lt(char,0x6E))
                { char:= add(0x7B, sub(char,0x61)) }
                if iszero(eq(char, 0x20))
                {mstore8(add(add(text,0x20), mul(i,1)), sub(char,13))}
            }
        }
        emit Result(text);
    }
}

このコードは単に文字列(a〜zの文字。検証なし)を受け取り、各文字を13桁右にシフト('z'を超えたら折り返し)することで暗号化します。つまり、'a'は'n'に、'x'は'k'にシフトされます。ここでのアセンブリは重要ではないので、この段階で意味が分からなくても心配する必要はありません。

このコードを暗号化に使用する次のコントラクトを考えてみましょう。```solidity import "Rot13Encryption.sol";

// encrypt your top secret info contract EncryptionContract { // library for encryption Rot13Encryption encryptionLibrary;

root@kitploit:~
// constructor - initialise the library
constructor(Rot13Encryption _encryptionLibrary) {
    encryptionLibrary = _encryptionLibrary;
}

function encryptPrivateData(string privateInfo) {
    // potentially do some operations here
    encryptionLibrary.rot13Encrypt(privateInfo);
 }

}

root@kitploit:~
このコントラクトの問題点は、`encryptionLibrary` アドレスが public でも constant でもないことです。したがって、コントラクトのデプロイヤーは、コンストラクタでこのコントラクトを指すアドレスを指定できた可能性があります:```solidity
//encryption contract
contract Rot26Encryption {

   event Result(string convertedString);

    //rot13 encrypt a string
    function rot13Encrypt (string text) public {
        uint256 length = bytes(text).length;
        for (var i = 0; i < length; i++) {
            byte char = bytes(text)[i];
            //inline assembly to modify the string
            assembly {
                char := byte(0,char) // get the first byte
                if and(gt(char,0x6D), lt(char,0x7B)) // if the character is in [n,z], i.e. wrapping.
                { char:= sub(0x60, sub(0x7A,char)) } // subtract from the ascii number a by the difference char is from z.
                if iszero(eq(char, 0x20)) // ignore spaces
                {mstore8(add(add(text,0x20), mul(i,1)), add(char,26))} // add 13 to char.
            }
        }
        emit Result(text);
    }

    // rot13 decrypt a string
    function rot13Decrypt (string text) public {
        uint256 length = bytes(text).length;
        for (var i = 0; i < length; i++) {
            byte char = bytes(text)[i];
            assembly {
                char := byte(0,char)
                if and(gt(char,0x60), lt(char,0x6E))
                { char:= add(0x7B, sub(char,0x61)) }
                if iszero(eq(char, 0x20))
                {mstore8(add(add(text,0x20), mul(i,1)), sub(char,26))}
            }
        }
        emit Result(text);
    }
}

これは rot26 暗号を実装したものです(各文字を26文字ずらす、わかるでしょ? :p)。繰り返しになりますが、このコントラクトのアセンブリを理解する必要はありません。デプロイヤーは次のコントラクトをリンクすることもできたはずです:```solidity contract Print{ event Print(string text);

root@kitploit:~
function rot13Encrypt(string text) public {
    emit Print(text);
}

}

root@kitploit:~
If the address of either of these contracts were given in the constructor, the `encryptPrivateData()` function would simply produce an event which prints the unencrypted private data. Although in this example a library-like contract was set in the constructor, it is often the case that a privileged user (such as an `owner`) can change library contract addresses. If a linked contract doesn't contain the function being called, the fallback function will execute. For example, with the line `encryptionLibrary.rot13Encrypt()`, if the contract specified by `encryptionLibrary` was:```solidity
 contract Blank {
     event Print(string text);
     function () {
         emit Print("Here");
         //put malicious code here and it will run
     }
 }

すると、テキストが "Here" であるイベントが発行されるでしょう。したがって、ユーザーがコントラクトライブラリを変更できるなら、原則として、ユーザーに知らぬ間に任意のコードを実行させることが可能になります。

注: このような暗号化コントラクトは使用しないでください。スマートコントラクトへの入力パラメータはブロックチェーン上で見えるためです。また、Rot暗号は推奨される暗号化手法ではありません :p

予防技術

上記で示したように、脆弱性のないコントラクトでも、(場合によっては)悪意を持って動作するようにデプロイされる可能性があります。監査人はコントラクトを公開検証でき、その所有者が悪意のある方法でデプロイする可能性があり、その結果、公開監査済みでありながら脆弱性や悪意のある意図を持つコントラクトになる可能性があります。

これらのシナリオを防ぐ技術はいくつかあります。

1つの技術は、new キーワードを使用してコントラクトを作成することです。上記の例では、コンストラクタは次のように記述できます:```solidity constructor() { encryptionLibrary = new Rot13Encryption(); }

root@kitploit:~
このようにして、参照されるコントラクトのインスタンスはデプロイ時に作成され、デプロイヤーはスマートコントラクトを変更しない限り、`Rot13Encryption` コントラクトを他のものに置き換えることはできません。

別の解決策としては、外部コントラクトのアドレスが既知の場合は、それらをハードコードすることです。

一般に、外部コントラクトを呼び出すコードは常に注意深く調べるべきです。開発者として外部コントラクトを定義する際には、コントラクトアドレスを公開すること(以下のハニーポットの例ではそのようになっていません)は良い考えです。これにより、ユーザーはコントラクトによって参照されているコードを簡単に確認できます。逆に、コントラクトがコントラクトアドレスをプライベート変数として持っている場合、それは誰かが悪意のある行動をとっている兆候である可能性があります(実世界の例で示されているとおり)。特権を持つ(または任意の)ユーザーが、外部関数を呼び出すために使用されるコントラクトアドレスを変更できる場合、(分散型システムの文脈では)タイムロックまたは投票メカニズムを実装して、ユーザーがどのコードが変更されているかを確認できるようにするか、参加者に新しいコントラクトアドレスにオプトイン/オプトアウトする機会を与えることが重要になる場合があります。


<h3 id="cr-example">実世界の例: 再入ハニーポット</h3>

最近、メインネット上で多数のハニーポットが公開されています。これらのコントラクトは、コントラクトを悪用しようとするイーサリアムハッカーを出し抜こうとしますが、そのハッカーたちは結局、悪用するつもりだったコントラクトに対して ether を失うことになります。一例として、コンストラクタ内で期待されるコントラクトを悪意のあるものに置き換えるという上記の攻撃を利用しています。コードは[こちら](https://etherscan.io/address/0x95d34980095380851902ccd9a1fb4c813c2cb639#code)で見つかります:```solidity
pragma solidity ^0.4.19;

contract Private_Bank
{
    mapping (address => uint) public balances;
    uint public MinDeposit = 1 ether;
    Log TransferLog;

    function Private_Bank(address _log)
    {
        TransferLog = Log(_log);
    }

    function Deposit()
    public
    payable
    {
        if(msg.value >= MinDeposit)
        {
            balances[msg.sender]+=msg.value;
            TransferLog.AddMessage(msg.sender,msg.value,"Deposit");
        }
    }

    function CashOut(uint _am)
    {
        if(_am<=balances[msg.sender])
        {
            if(msg.sender.call.value(_am)())
            {
                balances[msg.sender]-=_am;
                TransferLog.AddMessage(msg.sender,_am,"CashOut");
            }
        }
    }

    function() public payable{}

}

contract Log
{
    struct Message
    {
        address Sender;
        string  Data;
        uint Val;
        uint  Time;
    }

    Message[] public History;
    Message LastMsg;

    function AddMessage(address _adr,uint _val,string _data)
    public
    {
        LastMsg.Sender = _adr;
        LastMsg.Time = now;
        LastMsg.Val = _val;
        LastMsg.Data = _data;
        History.push(LastMsg);
    }
}

あるRedditユーザーによるこの投稿は、コントラクトに存在すると予想したリエントランシーバグを利用しようとして、このコントラクトで1 etherを失った経緯を説明しています。

8. ショートアドレス/パラメータ攻撃

この攻撃は、Solidityコントラクト自体に対してではなく、それらと相互作用する可能性のあるサードパーティ製アプリケーションに対して実行されます。この攻撃を追加したのは、網羅性のためであり、コントラクト内でパラメータがどのように操作され得るかを認識するためです。

さらに詳しくは、ERC20ショートアドレス攻撃の解説、ICOスマートコントラクトの脆弱性:ショートアドレス攻撃、またはこのRedditの投稿を参照してください。

脆弱性

スマートコントラクトにパラメータを渡す際、パラメータはABI仕様に従ってエンコードされます。エンコードされたパラメータを期待されるパラメータ長より短く送信することが可能です(例えば、標準の40 hex文字(20バイト)ではなく、38 hex文字(19バイト)のアドレスを送信する場合など)。このようなシナリオでは、EVMはエンコードされたパラメータの末尾に0をパディングして、期待される長さを満たします。

これは、サードパーティ製アプリケーションが入力を検証しない場合に問題となります。最も明確な例は、ユーザーが引き出しを要求する際にERC20トークンのアドレスを検証しない取引所です。この例は、上記のPeter Venesses氏の投稿、ERC20ショートアドレス攻撃の解説でより詳細に説明されています。

パラメータの順序に注意しつつ、標準のERC20転送関数インターフェースを考えてみましょう、```solidity function transfer(address to, uint tokens) public returns (bool success);

root@kitploit:~
次に、取引所が大量のトークン(たとえば `REP`)を保有しており、ユーザーが自分の取り分である100トークンを引き出したい場合を考えてみましょう。ユーザーは自分のアドレス `0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead` とトークン数 `100` を送信します。取引所はこれらのパラメータを `transfer()` 関数で指定された順序、すなわち `address` の次に `tokens` という順序でエンコードします。エンコード結果は `a9059cbb000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddeaddead0000000000000` `000000000000000000000000000000000056bc75e2d63100000` となります。最初の4バイト(`a9059cbb`)は `transfer()` の[関数シグネチャ/セレクタ](https://solidity.readthedocs.io/en/latest/abi-spec.html#function-selector)であり、次の32バイトはアドレス、最後の32バイトは `uint256` 型のトークン数を表します。末尾の16進数 `56bc75e2d63100000` が100トークンに相当することに注意してください(`REP` トークンコントラクトで指定されているように、小数点以下18桁です)。

では、1バイト(2桁の16進数)欠けたアドレスを送信した場合に何が起こるかを見てみましょう。具体的には、攻撃者がアドレスとして `0xdeaddeaddeaddeaddeaddeaddeaddeaddeadde`(最後の2桁が欠落)を送信し、同じく引き出したい `100` トークンを送信したとします。取引所がこの入力を検証しない場合、エンコード結果は `a9059cbb000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddeadde00000000000000` `00000000000000000000000000000000056bc75e2d6310000000` となります。その違いは微妙です。送信された短いアドレスを補うために、エンコードの末尾に `00` がパディングされていることに注意してください。これがスマートコントラクトに送信されると、`address` パラメータは `0xdeaddeaddeaddeaddeaddeaddeaddeaddeadde00` として読み取られ、値は `56bc75e2d6310000000` として読み取られます(余分な `0` が2つ追加されていることに注意)。この値は、今や `25600` トークンです(値が `256` 倍されています)。この例では、取引所がこれだけのトークンを保有している場合、ユーザーは変更されたアドレスに対して `25600` トークンを引き出します(取引所はユーザーが `100` しか引き出していないと思っているにもかかわらず)。明らかに、この例では攻撃者は変更されたアドレスを保有していませんが、攻撃者が末尾が `0` で終わる任意のアドレスを生成し(これは簡単にブルートフォースできます)、その生成したアドレスを使用した場合、無防備な取引所から簡単にトークンを盗むことができます。

<h3 id="short-prev">予防技術</h3>

ブロックチェーンに送信する前にすべての入力を検証すれば、この種の攻撃を防げることは言うまでもないでしょう。また、パラメータの順序がここで重要な役割を果たすことにも注意すべきです。パディングは末尾にのみ発生するため、スマートコントラクト内のパラメータを注意深く並べることで、この攻撃の一部の形態を軽減できる可能性があります。

<h3 id="short-example">実際の事例: 不明</h3>

この種の攻撃が実際に公表された例は、私は知りません。

<h2 id="unchecked-calls"><span id="SP-9">9. チェックされていないCALL戻り値</span></h2>

Solidityで外部コールを実行する方法はいくつかあります。外部アカウントへのetherの送信は、一般的に `transfer()` メソッドを介して行われます。しかし、`send()` 関数も使用でき、より多用途な外部コールのために、`CALL` オペコードをSolidityで直接使用することもできます。`call()` 関数と `send()` 関数は、コールが成功したか失敗したかを示すブール値を返します。したがって、これらの関数には単純な注意点があります。それは、これらの関数を実行するトランザクションは、`call()` または `send()` によって開始された外部コールが失敗してもリバートせず、単に `call()` または `send()` が `false` を返すだけであるということです。戻り値がチェックされず、開発者がリバートが発生することを期待してしまう場合に、よくある落とし穴が生じます。

詳細については、[DASP Top 10](http://www.dasp.co/#item-4) と [Scanning Live Ethereum Contracts for the "Unchecked-Send" Bug](http://hackingdistributed.com/2016/06/16/scanning-live-ethereum-contracts-for-bugs/) を参照してください。

<h3 id="unchecked-calls-vuln">脆弱性</h3>

次の例を考えてみましょう。```solidity
contract Lotto {

    bool public payedOut = false;
    address public winner;
    uint public winAmount;

    // ... extra functionality here

    function sendToWinner() public {
        require(!payedOut);
        winner.send(winAmount);
        payedOut = true;
    }

    function withdrawLeftOver() public {
        require(payedOut);
        msg.sender.send(this.balance);
    }
}

このコントラクトは Lotto に似たコントラクトであり、winner が winAmount の ether を受け取ります。通常は少し残金が残り、誰でも引き出すことができます。

バグは [11] 行目に存在し、send() の戻り値を確認せずに使用しています。この単純な例では、トランザクションが失敗する winner(ガス切れになるか、フォールバック関数で意図的に例外を投げるコントラクトの場合)によって、payedOut が true に設定されてしまいます(ether が送金されたかどうかに関係なく)。この場合、誰でも withdrawLeftOver() 関数を使って winner の賞金を引き出すことができます。

予防策

可能な限り、send() ではなく transfer() 関数を使用してください。transfer() は外部トランザクションがリバートした場合に revert するためです。send() が必要な場合は、必ず戻り値を確認するようにしてください。

さらに堅牢な推奨事項は、引き出しパターン を採用することです。この解決策では、各ユーザーが、コントラクトから ether を送信する処理を担う独立した関数(つまり withdraw 関数)を呼び出す責任を負います。これにより、失敗した send トランザクションの影響を個別に処理できます。その考え方は、外部への送信機能をコードベースの他の部分から論理的に分離し、失敗する可能性のあるトランザクションの責任を、withdraw 関数を呼び出すエンドユーザーに負わせるというものです。

実世界の例: Etherpot と King of the Ether

Etherpot は、上記の例のコントラクトとそれほど変わらないスマートコントラクト宝くじでした。etherpot の solidity コードは、lotto.sol にあります。このコントラクトの主な欠点は、ブロックハッシュの誤った使用によるものでした(使用できるのは最後の256個のブロックハッシュのみです。Etherpot がこれを正しく実装できなかった点については、Aakil Fernandes の投稿を参照してください)。しかし、このコントラクトは未チェックのコール値(call value)の問題も抱えていました。lotto.sol の [80] 行目の関数 cash() に注目してください。```solidity ... function cash(uint roundIndex, uint subpotIndex){

root@kitploit:~
    var subpotsCount = getSubpotsCount(roundIndex);

    if(subpotIndex>=subpotsCount)
        return;

    var decisionBlockNumber = getDecisionBlockNumber(roundIndex,subpotIndex);

    if(decisionBlockNumber>block.number)
        return;

    if(rounds[roundIndex].isCashed[subpotIndex])
        return;
    //Subpots can only be cashed once. This is to prevent double payouts

    var winner = calculateWinner(roundIndex,subpotIndex);
    var subpot = getSubpot(roundIndex);

    winner.send(subpot);

    rounds[roundIndex].isCashed[subpotIndex] = true;
    //Mark the round as cashed

} ...

root@kitploit:~
行 \[21\] では `send` 関数の戻り値がチェックされておらず、次の行で勝者に資金が送金されたことを示すブール値が設定されていることに注意してください。このバグにより、勝者が ether を受け取らない一方で、コントラクトの状態が勝者への支払いが済んだことを示す状態になり得ます。

このバグのより深刻なバージョンは、[King of the Ether](https://www.kingoftheether.com/thrones/kingoftheether/index.html) で発生しました。このコントラクトに関する優れた [ポストモーテム](https://www.kingoftheether.com/postmortem.html) が書かれており、チェックされていない失敗した `send()` がどのようにコントラクトを攻撃するために使用され得るかが詳述されています。


<h2 id="race-conditions"><span id="SP-10">10. 競合状態 / フロントランニング</span></h2>

他のコントラクトへの外部呼び出しと、基盤となるブロックチェーンのマルチユーザー的な性質の組み合わせにより、ユーザーが予期しない状態を得るためにコードの実行を *競い合う*、さまざまな Solidity の潜在的な落とし穴が生じます。[Re-Entrancy](#reentrancy) は、そのような競合状態の一例です。このセクションでは、Ethereum ブロックチェーン上で発生し得るさまざまな種類の競合状態について、より一般的に説明します。このテーマに関する優れた記事はいくつかあります。例としては、[Ethereum Wiki - Safety](https://github.com/ethereum/wiki/wiki/Safety#race-conditions)、[DASP - Front-Running](http://www.dasp.co/#item-7)、および [Consensus - Smart Contract Best Practices](https://consensys.github.io/smart-contract-best-practices/known_attacks/#race-conditions) が挙げられます。

<h3 id="race-conditions-vuln">脆弱性</h3>

ほとんどのブロックチェーンと同様に、Ethereum ノードはトランザクションをプールしてブロックにまとめます。トランザクションは、マイナーがコンセンサスメカニズム(現在は Ethereum の [ETHASH](https://github.com/ethereum/wiki/wiki/Ethash) PoW)を解いた後にのみ有効と見なされます。ブロックを解いたマイナーは、プールからどのトランザクションをブロックに含めるかも選択します。これは通常、トランザクションの `gasPrice` によって順序付けられます。ここに潜在的な攻撃ベクトルが存在します。攻撃者は、トランザクションプールを監視して、問題の解決策を含む可能性があるトランザクション、攻撃者の権限を変更または取り消すトランザクション、または攻撃者にとって望ましくない状態にコントラクトを変更するトランザクションを探すことができます。攻撃者はその後、このトランザクションからデータを取得し、より高い `gasPrice` で独自のトランザクションを作成し、元のトランザクションより先に自分のトランザクションをブロックに含めることができます。

簡単な例で、これがどのように機能するかを見てみましょう。コントラクト `FindThisHash.sol` を考えてみます:```solidity
contract FindThisHash {
    bytes32 constant public hash = 0xb5b5b97fafd9855eec9b41f74dfb6c38f5951141f9a3ecd7f44d5479b630ee0a;

    constructor() public payable {} // load with ether

    function solve(string solution) public {
        // If you can find the pre image of the hash, receive 1000 ether
        require(hash == sha3(solution));
        msg.sender.transfer(1000 ether);
    }
}

このコントラクトに1000 etherが含まれていると想像してください。sha3ハッシュ 0xb5b5b97fafd9855eec9b41f74dfb6c38f5951141f9a3ecd7f44d5479b630ee0a のプリイメージを見つけ出したユーザーは、解答を提出して1000 etherを引き出すことができます。あるユーザーが解答が Ethereum! だと気付いたとしましょう。彼らはパラメータとして Ethereum! を渡して solve() を呼び出します。残念ながら、攻撃者はトランザクションプールを監視して、解答を提出する人を待ち伏せするほど賢いです。彼らはこの解答を見つけ、その有効性を確認し、元のトランザクションよりもはるかに高い gasPrice で同等のトランザクションを提出します。ブロックを解決するマイナーは、高い gasPrice のために攻撃者を優先し、元の解答者よりも先に彼らのトランザクションを受け入れる可能性が高いです。攻撃者は1000 etherを取得し、問題を解いたユーザーは何も得られません(コントラクトには ether が残っていないため)。

より現実的な問題は、将来のCasper実装の設計にあります。Casperのプルーフ・オブ・ステーク・コントラクトはスラッシング条件を呼び出します。そこでは、バリデータが二重投票や不正行為をしていることに気付いたユーザーが、その証拠を提出するようインセンティブを与えられます。バリデータは罰せられ、ユーザーは報酬を得ます。このようなシナリオでは、マイナーとユーザーがそのような証拠の提出をすべてフロントランすることが予想されるため、この問題は最終リリース前に解決されなければなりません。

予防技術

この種のフロントラン攻撃を実行できるユーザーには2つのクラスがあります。ユーザー(自分のトランザクションの gasPrice を変更する)とマイナー自身(ブロック内のトランザクションを自分の都合に合わせて並べ替えることができる)です。最初のクラス(ユーザー)に対して脆弱なコントラクトは、2番目のクラス(マイナー)に対して脆弱なものよりもはるかに状況が悪いです。なぜなら、マイナーはブロックを解決したときにしか攻撃を実行できず、特定のブロックを狙う個々のマイナーにとってそれは起こりそうにないからです。ここで、どのクラスの攻撃者を防ぐことができるかに関連して、いくつかの緩和策を列挙します。

採用できる方法の1つは、コントラクト内に gasPrice の上限を設定するロジックを作成することです。これにより、ユーザーが gasPrice を引き上げ、上限を超えて優先的なトランザクション順序を得ることを防ぎます。この予防策は、最初のクラスの攻撃者(任意のユーザー)のみを緩和します。このシナリオのマイナーは、ガス価格に関係なくブロック内のトランザクションを自分の好きなように並べ替えることができるため、依然としてコントラクトを攻撃できます。

より堅牢な方法は、可能な限りコミット・リビールスキームを使用することです。このようなスキームでは、ユーザーは隠された情報(通常はハッシュ)を含むトランザクションを送信します。トランザクションがブロックに含まれた後、ユーザーは送信されたデータを明らかにするトランザクションを送信します(リビールフェーズ)。この方法は、トランザクションの内容を判別できないため、マイナーとユーザーの両方がトランザクションをフロントランするのを防ぎます。しかし、この方法ではトランザクションの値を隠すことはできません(場合によっては、それが隠す必要のある貴重な情報です)。ENSスマートコントラクトは、ユーザーがトランザクションを送信できるようにしました。そのコミットデータには、ユーザーが支払ってもよい ether の金額が含まれていました。ユーザーはその後、任意の値のトランザクションを送信できました。リビールフェーズ中、ユーザーはトランザクションで送信した金額と支払ってもよい金額との差額を返金されました。

Lorenz、Phil、Ari、Florianによるさらなる提案は、Submarine Sendsを使用することです。このアイデアの効率的な実装には CREATE2 オペコードが必要ですが、これは現在まだ採用されておらず、今後のハードフォークで採用される可能性が高いようです。

実世界の例: ERC20 と Bancor

ERC20標準は、Ethereum上でトークンを構築するための標準としてよく知られています。この標準には、approve() 関数に起因する潜在的なフロントラン脆弱性があります。この脆弱性の良い説明はここにあります。

この標準は approve() 関数を次のように指定しています:```solidity function approve(address _spender, uint256 _value) returns (bool success)

root@kitploit:~
この関数により、ユーザーは他のユーザーに自分の代わりにトークンを転送することを許可できます。フロントランニングの脆弱性は、ユーザー Alice が友人 `Bob` に `100 tokens` の使用を *承認* するシナリオで発生します。その後 Alice は `Bob` による `100 tokens` の使用承認を取り消したいと考え、`Bob` の割り当てを `50 tokens` に設定するトランザクションを作成します。チェーンを注意深く監視していた `Bob` はこのトランザクションを確認し、`100 tokens` を使う自分のトランザクションを作成します。彼は自分のトランザクションに `Alice` よりも高い `gasPrice` を設定し、自分のトランザクションを彼女のものより優先させます。一部の `approve()` の実装では、`Bob` が自分の `100 tokens` を転送した後に、`Alice` のトランザクションがコミットされると `Bob` の承認が `50 tokens` にリセットされ、結果的に `Bob` に `150 tokens` へのアクセスを与えることになります。この攻撃の緩和策は、上記のリンク先のドキュメント [こちら](https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit) に記載されています。

もう一つの顕著な実世界の例は [Bancor](https://www.bancor.network/) です。Ivan Bogatty と彼のチームは、初期の Bancor 実装に対する利益を生む攻撃を文書化しました。彼の [ブログ記事](https://hackernoon.com/front-running-bancor-in-150-lines-of-python-with-ethereum-api-d5e2bfd0d798) と [Devon 3 トーク](https://www.youtube.com/watch?v=RL2nE3huNiI) では、これがどのように行われたかが詳しく説明されています。基本的に、トークンの価格はトランザクション値に基づいて決定されるため、ユーザーはトランザクションプールで Bancor のトランザクションを監視し、価格差から利益を得るためにそれらをフロントランできます。この攻撃は Bancor チームによって対処されています。

<h2 id="dos"><span id="SP-11">11. サービス拒否 (DOS)</span></h2>

このカテゴリは非常に広範ですが、基本的にはユーザーがコントラクトを短期間、または場合によっては恒久的に動作不能にできる攻撃で構成されています。これにより、[Second Parity MultiSig ハック](#dc-example) の場合のように、ether がこれらのコントラクトに永久に閉じ込められる可能性があります。

<h3 id="dos-vuln">脆弱性</h3>

コントラクトが動作不能になる方法はさまざまです。ここでは、攻撃者による DOS 攻撃につながる可能性がある、あまり明白ではないブロックチェーン特有の Solidity コーディングパターンに焦点を当てます。

**1. ガスステイペンドなしの外部呼び出し** - 未知のコントラクトへの外部呼び出しを行い、
その呼び出しが失敗するかどうかに関係なくトランザクションの処理を続行したい場合があります。通常、これは
`CALL` オペコードを使用して実現されます。`CALL` は呼び出しが失敗してもトランザクションを
ロールバックしません(詳細と例については [未チェックの CALL 戻り値](#unchecked-calls) を参照してください)。
簡単な例を考えてみましょう。`withdraw()` 関数が呼び出されたときに ether をゆっくりと放出する
コントラクトウォレットがあるとします。`partner` は自分のアドレスを追加し、
ガスを消費して引き出しを呼び出すことができます。これにより、
`partner` と `owner` の両方にコントラクト総残高の
1% が与えられます。```solidity
contract TrickleWallet {

    address public partner; // withdrawal partner - pay the gas, split the withdraw
    address public constant owner = 0xA9E;
    uint timeLastWithdrawn;
    mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances

    function setWithdrawPartner(address _partner) public {
        require(partner == '0x0' || msg.sender == partner);
        partner = _partner;
    }

    // withdraw 1% to recipient and 1% to owner
    function withdraw() public {
        uint amountToSend = address(this).balance/100;
        // perform a call without checking return
        // the recipient can revert, the owner will still get their share
        partner.call.value(amountToSend)();
        owner.transfer(amountToSend);
        // keep track of last withdrawal time
        timeLastWithdrawn = now;
        withdrawPartnerBalances[partner] += amountToSend;
    }

    // allow deposit of funds
    function() payable {}

    // convenience function
    function contractBalance() view returns (uint) {
        return address(this).balance;
    }
}

ライン[17]では、外部呼び出しを実行して、コントラクト残高の1%を ユーザー指定のアカウントに送信していることに注意してください。CALLオペコードが使用される理由は、 外部呼び出しがリバートしても、所有者が引き続き支払いを受け取れるようにするためです。問題は、 トランザクションがそのガスのすべて(実際には、トランザクションガスの大部分のみが送信され、呼び出しの処理を完了するために一部が残されます)を外部呼び出しに送信することです。ユーザーが悪意を持っている場合、すべてのガスを消費するコントラクトを作成し、ガス切れによってすべてのwithdraw()トランザクションを失敗させることができます。

たとえば、すべてのガスを消費する次の悪意のあるコントラクトを考えてみましょう。```solidity contract ConsumeAllGas { function () payable { // an assert consumes all transaction gas, unlike a //revert which returns the remaining gas assert(1==2); } }

root@kitploit:~
もし、引き出し相手がコントラクトの所有者を気に入らなくなった場合、
パートナーアドレスをこのコントラクトに設定し、`TrickleWallet` コントラクト内の
すべての資金を永久にロックすることができます。

このようなDoS攻撃ベクトルを防ぐには、外部呼び出しでガススティペンドを指定して、
そのトランザクションが使用できるガス量を制限してください。この例では、
行 \[17\] を次のように変更することで、この攻撃を解決できます。```solidity
partner.call.gas(50000).value(amountToSend)();

この変更により、外部トランザクションに費やせるガスはわずか50,000に制限されます。owner は、外部トランザクションがどれだけ使用しても、自分のトランザクションを完了させるために、これより大きいガス価格を設定することができます。

2. 外部から操作されるマッピングや配列のループ処理 - これまでの経験では、この種のパターンをさまざまな形で見てきました。典型的には、owner が投資家間でトークンを分配したいと考え、例のコントラクトにあるような distribute() 風の関数を使って行うシナリオで出現します。```solidity contract DistributeTokens { address public owner; // gets set somewhere address[] investors; // array of investors uint[] investorTokens; // the amount of tokens each investor gets

root@kitploit:~
// ... extra functionality, including transfertoken()

function invest() public payable {
    investors.push(msg.sender);
    investorTokens.push(msg.value * 5); // 5 times the wei sent
    }

function distribute() public {
    require(msg.sender == owner); // only owner
    for(uint i = 0; i < investors.length; i++) {
        // here transferToken(to,amount) transfers "amount" of tokens to the address "to"
        transferToken(investors[i],investorTokens[i]);
    }
}

}

root@kitploit:~
このコントラクト内のループは、人為的に膨張させられる可能性のある配列に対して実行されることに注意してください。攻撃者は多数のユーザーアカウントを作成することで、`investor` 配列を大きくすることができます。原理的には、for ループの実行に必要なガスがブロックガスリミットを超えるようにすることが可能であり、その場合、`distribute()` 関数は事実上使用不能になります。

**3. オーナー操作** - もう一つの一般的なパターンは、オーナーがコントラクト内で特定の特権を持ち、コントラクトが次の状態に進むために何らかのタスクを実行しなければならない場合です。一例として、ICO コントラクトでは、オーナーがコントラクトを `finalize()` する必要があり、それによってトークンが転送可能になります。すなわち、``` solidity
bool public isFinalized = false;
address public owner; // gets set somewhere

function finalize() public {
    require(msg.sender == owner);
    isFinalized == true;
}

// ... extra ICO functionality

// overloaded transfer function
function transfer(address _to, uint _value) returns (bool) {
    require(isFinalized);
    super.transfer(_to,_value)
}

...

このような場合、特権を持つユーザーが秘密鍵を紛失したり、活動しなくなったりすると、トークンコントラクト全体が動作不能になります。この場合、owner が finalize() を呼び出せなければ、トークンの転送は一切できなくなります。すなわち、トークンエコシステム全体の運用は単一のアドレスに依存しているのです。

4. 外部呼び出しに基づく状態遷移 - コントラクトは、新しい状態へ進むためにアドレスへの ether の送信や、外部ソースからの入力を待つ必要があるように書かれることがあります。これらのパターンは、外部呼び出しが失敗したり、外部の理由で妨げられたりすると、DOS 攻撃につながる可能性があります。ether の送信の例では、ユーザーは ether を受け取らないコントラクトを作成できます。コントラクトが新しい状態へ進むために ether の引き出しを必要とする場合(再び使用可能になる前にすべての ether を引き出す必要があるタイムロックコントラクトを考えてください)、ether は ether を受け取らないユーザーのコントラクトに送信できないため、そのコントラクトは新しい状態に到達できません。

予防技術

最初の例では、コントラクトは外部ユーザーによって人為的に操作され得るデータ構造をループ処理すべきではありません。代わりに、各投資家が withdraw 関数を呼び出してトークンを個別に請求する、引き出しパターンが推奨されます。

2つ目の例では、コントラクトの状態を変更するために特権ユーザーが必要でした。そのような例では(可能な場合)、owner が機能不全になった場合に備えてフェイルセーフを使用できます。1つの解決策は、owner をマルチシグコントラクトにすることです。もう1つの解決策はタイムロックを使用することです。[13] 行目の require には、unlockTime で指定された期間の経過後に任意のユーザーがファイナライズできるようにする、require(msg.sender == owner || now > unlockTime) のような時間ベースの仕組みを含めることができます。この種の緩和手法は3つ目の例でも使用できます。新しい状態に進むために外部呼び出しが必要な場合は、その失敗の可能性を考慮し、目的の呼び出しが決して来ない場合に備えて、時間ベースの状態遷移を追加することも検討してください。

注記: もちろん、これらの提案には中央集権的な代替手段もあります。必要に応じて、DOSベースの攻撃ベクトルの問題を修正しに来ることができる maintenanceUser を追加する方法です。通常、この種のコントラクトには、そのようなエンティティの権限に対する信頼の問題が含まれますが、それについてはこのセクションでは議論しません。

実世界の例: GovernMental

GovernMental は、かなり大量の ether を蓄積した古いポンジ・スキームでした。実際、ある時点で 1100 ether を蓄積していました。残念ながら、このセクションで述べた DOS の脆弱性の影響を受けやすいものでした。この Reddit の投稿 は、ether を引き出すためにコントラクトが大きなマッピングの削除を必要としたことを説明しています。このマッピングの削除には、当時のブロックガスリミットを超えるガスコストがかかったため、1100 ether を引き出すことは不可能でした。コントラクトアドレスは 0xF45717552f12Ef7cb65e95476F217Ea008167Ae3 であり、トランザクション 0x0d80d67202bd9cb6773df8dd2020e7190a1b0793e8ec4fc105257e8128f0506b から、1100 ether が最終的に 2.5M ガスを使用したトランザクションで取得されたことが分かります(ブロックガスリミットがそのようなトランザクションを許可した後のことです)。

12. ブロックタイムスタンプの操作

ブロックタイムスタンプはこれまで、乱数のエントロピー(詳細は エントロピーの錯覚 セクションを参照)、一定期間の資金のロック、時間に依存するさまざまな状態変更条件文など、さまざまな用途に使用されてきました。マイナーはタイムスタンプをわずかに調整する能力を持っているため、スマートコントラクトでブロックタイムスタンプが誤って使用されると、非常に危険なものになり得ます。

これに関する有用な参考資料: Solidity ドキュメント、この Stack Exchange の質問。

脆弱性

block.timestamp またはその別名 now は、マイナーにそうする動機があれば操作される可能性があります。マイナーによる悪用に対して脆弱な簡単なゲームを構築してみましょう。

roulette.sol:```solidity contract Roulette { uint public pastBlockTime; // Forces one bet per block

root@kitploit:~
constructor() public payable {} // initially fund contract

// fallback function used to make a bet
function () public payable {
    require(msg.value == 10 ether); // must send 10 ether to play
    require(now != pastBlockTime); // only 1 transaction per block
    pastBlockTime = now;
    if(now % 15 == 0) { // winner
        msg.sender.transfer(this.balance);
    }
}

}

root@kitploit:~
このコントラクトは、単純な宝くじのように動作します。各ブロックにつき1つのトランザクションが、コントラクトの残高を獲得するチャンスを得るために `10 ether` を賭けることができます。ここでの前提は、`block.timestamp` が下2桁に関して一様分布しているというものです。もしそうであれば、この宝くじに当選する確率は1/15になります。

ただし、ご存知のとおり、マイナーは必要に応じてタイムスタンプを調整できます。この特定のケースでは、コントラクトに十分な ether が蓄積された場合、ブロックを解決したマイナーは、`block.timestamp` または `now` を15で割った余りが `0` になるようなタイムスタンプを選択するインセンティブがあります。そうすることで、ブロック報酬とともにこのコントラクトにロックされた ether を獲得できる可能性があります。各ブロックで賭けられるのは1人だけなので、[フロントランニング](#race-conditions) 攻撃に対しても脆弱です。

実際には、ブロックタイムスタンプは単調に増加するため、マイナーは任意のブロックタイムスタンプを選択できません(先行ブロックのタイムスタンプより大きくなければなりません)。また、ブロックタイムを遠い未来に設定することも制限されています。そのようなブロックはネットワークによって拒否される可能性が高いためです(ノードはタイムスタンプが未来のブロックを検証しません)。

<h3 id="block-timestamp-prev">防止技術</h3>

ブロックタイムスタンプは、エントロピーや乱数生成に使用すべきではありません。つまり、(ランダムであると想定される)ゲームに勝利したり、重要な状態を変更したりする際の決定要因(直接的であれ、あるいは何らかの導出を介するものであれ)にすべきではありません。

時間に依存するロジックが必要な場合もあります。たとえば、コントラクトのロック解除(タイムロック)、数週間後の ICO の完了、有効期限の適用などです。時間を見積もるには、`block.number`([Solidity ドキュメント](http://solidity.readthedocs.io/en/latest/units-and-global-variables.html#block-and-transaction-properties) を参照)と平均ブロック時間を使用することが推奨される場合があります。すなわち、`10 second` のブロック時間での `1 week` は、おおよそ `60480 blocks` に相当します。したがって、コントラクトの状態を変更するブロック番号を指定する方が、マイナーがブロック番号を簡単に操作できないため、より安全になり得ます。[BAT ICO](https://etherscan.io/address/0x0d8775f648430679a709e98d2b0cb6250d2887ef#code) コントラクトはこの戦略を採用しました。

これは、コントラクトがブロックタイムスタンプのマイナーによる操作を特に気にしない場合には不要かもしれませんが、コントラクトを開発する際には認識しておくべきことです。

<h3 id="block-timestamp-example">実世界の例: GovernMental</h3>

[GovernMental](http://governmental.github.io/GovernMental/) は、かなりの量の ether を蓄積した古いポンジスキームでした。また、タイムスタンプベースの攻撃に対しても脆弱でした。このコントラクトは、ラウンド内で最後に参加したプレイヤー(少なくとも1分間)に報酬を支払っていました。したがって、プレイヤーでもあったマイナーは、タイムスタンプを(未来の時間に)調整して、1分が経過したように見せかけ、そのプレイヤーが1分以上最後に参加したように見せることができました(実際にはそうでなくても)。詳細は、Tanya Bahrynovska による [History of Ethereum Security Vulnerabilities Post](https://applicature.com/blog/history-of-ethereum-security-vulnerabilities-hacks-and-their-fixes) にあります。

<h2 id="constructors"><span id="SP-13">13. コンストラクタの注意点</span></h2>

コンストラクタは、コントラクトを初期化する際に、重要かつ特権的なタスクを実行することが多い特別な関数です。Solidity `v0.4.22` より前では、コンストラクタは、それを含むコントラクトと同じ名前を持つ関数として定義されていました。したがって、開発中にコントラクト名が変更された場合、コンストラクタ名も変更されないと、それは通常の呼び出し可能な関数になります。ご想像のとおり、これはいくつかの興味深いコントラクトハックにつながる可能性があり(実際につながりました)。

さらなる学習のために、[Ethernaught Challenges](https://github.com/OpenZeppelin/ethernaut)(特に Fallout レベル)に挑戦することをお勧めします。

<h3 id="constructors-vuln">脆弱性</h3>

コントラクト名が変更されたり、コンストラクタ名のタイプミスがあったりして、コントラクト名と一致しなくなった場合、コンストラクタは通常の関数のように動作します。これは、特にコンストラクタが特権的な操作を実行している場合、悲惨な結果を招く可能性があります。次のコントラクトを考えてみましょう。```solidity
contract OwnerWallet {
    address public owner;

    //constructor
    function ownerWallet(address _owner) public {
        owner = _owner;
    }

    // fallback. Collect ether.
    function () payable {}

    function withdraw() public {
        require(msg.sender == owner);
        msg.sender.transfer(this.balance);
    }
}

このコントラクトは ether を収集し、所有者のみが withdraw() 関数を呼び出してすべての ether を引き出すことができます。問題は、コンストラクタの名前がコントラクト名と正確に一致していないことに起因します。具体的には、ownerWallet は OwnerWallet と同じではありません。したがって、任意のユーザーが ownerWallet() 関数を呼び出して自分を所有者に設定し、withdraw() を呼び出してコントラクト内のすべての ether を取得できます。

予防手法

この問題は、主にバージョン 0.4.22 の Solidity コンパイラで対処されました。このバージョンでは、関数名をコントラクト名と一致させる必要の代わりに、コンストラクタを指定する constructor キーワードが導入されました。上記のような命名の問題を防ぐために、コンストラクタを指定する際はこのキーワードを使用することが推奨されます。

実世界の例: Rubixi

Rubixi(コントラクトコード)は、この種の脆弱性を示した別のピラミッドスキームでした。当初は DynamicPyramid と呼ばれていましたが、デプロイ前にコントラクト名が Rubixi に変更されました。コンストラクタの名前は変更されなかったため、任意のユーザーが creator になることができました。このバグに関連する興味深い議論は、こちらの Bitcoin Thread にあります。最終的には、ユーザーがピラミッドスキームからの手数料を得るために creator の地位を争うことが可能になりました。この特定のバグの詳細はこちらで確認できます。

14. 未初期化ストレージポインタ

EVM はデータを storage または memory として保存します。コントラクトを開発する際には、これがどのように行われるか、そして関数のローカル変数のデフォルトの型を正確に理解することを強くお勧めします。なぜなら、変数を不適切に初期化することで脆弱なコントラクトを生み出す可能性があるからです。

EVM における storage と memory の詳細については、Solidity Docs: Data Location、Solidity Docs: Layout of State Variables in Storage、Solidity Docs: Layout in Memory を参照してください。

このセクションは、Stefan Beyer による優れた投稿に基づいています。このトピックに関するさらなる読み物は、Sefan の着想元であるこちらの reddit スレッド にあります。

脆弱性

関数内のローカル変数は、その型に応じてデフォルトで storage または memory になります。初期化されていないローカル storage 変数は、コントラクト内の他の予期しないストレージ変数を指す可能性があり、意図的な(すなわち、開発者が後で攻撃するために意図的にそこに置く)または意図的でない脆弱性につながります。

次の比較的単純な名前登録コントラクトを考えてみましょう:```solidity // A Locked Name Registrar contract NameRegistrar {

root@kitploit:~
bool public unlocked = false;  // registrar locked, no name updates

struct NameRecord { // map hashes to addresses
    bytes32 name;
    address mappedAddress;
}

mapping(address => NameRecord) public registeredNameRecord; // records who registered names

Read more

ツールをダウンロード
Attack.sol - 行 [26]
EtherStore
10 ether
9 ether
  • Attack.sol - 行 [27] - 次にフォールバック関数は EtherStore の withdrawFunds() 関数を再度呼び出し、EtherStore コントラクトに "再入" します。

  • EtherStore.sol - 行 [11] - 2 回目の withdrawFunds() 呼び出しでは、行 [18] がまだ実行されていないため、私たちの残高はまだ 1 ether です。したがって、まだ balances[0x0..123] = 1 ether のままです。lastWithdrawTime 変数についても同様です。ここでも、すべての要件を満たします。

  • EtherStore.sol - 行 [17] - さらに 1 ether を引き出します。

  • 手順 4〜8 の繰り返し - Attack.sol の行 [26] に従い、EtherStore.balance >= 1 である限り、手順 4〜8 が繰り返されます。

  • Attack.sol - 行 [26] - EtherStore コントラクトに残っている ether が 1 未満 (あるいはそれ以下) になると、この if 文は失敗します。これにより、EtherStore コントラクトの行 [18] と [19] が (withdrawFunds() 関数の各呼び出しに対して) 実行されるようになります。

  • EtherStore.sol - 行 [18] と [19] - balances と lastWithdrawTime のマッピングが設定され、実行が終了します。

  • start
    fibonacciLibrary
    uint
    withdraw()
    calculatedFibNumber
    uint(fibonacciLibrary)