Etheriumスマートコントラクトの脆弱性。 コード例

この投稿は、イーサリアムスマートコントラクトセキュリティのトピックに関する一連の記事から始まります。 開発者の数は雪崩のように成長しており、「レーキ」から救う人はいないため、このトピックは非常に重要だと思います。 さようなら-翻訳...



1.未確認送信エラーのライブイーサリアム契約のスキャン



オリジナル- 「未チェック送信...」のライブイーサリアム契約のスキャン







著者: ジカイアレックスウェンおよびアンドリューミラー



Ethereumのスマートコントラクトのプログラミングは、エラーが発生しやすいことが知られています[1] 。 最近、私たちはいくつかを見ました

King of the EtherThe DAO-1.0などのハイエンドスマートコントラクトには、プログラミングエラーに起因する脆弱性が含まれていました。



2015年3月以降、スマートコントラクトプログラマーは、コントラクトが互いにメッセージを送信するときに発生する可能性のある特定のプログラミングの危険性について警告されてきました[6]



いくつかのプログラミングガイドは、よくある間違いを避ける方法を推奨しています(ホワイトペーパーEthereum [3]およびUMDからの独立したガイド[2] )。 これらの危険はそれらを回避するのに十分に理解できますが、そのような間違いの結果はひどいです。



これらの危険から生じるエラーはどれくらい一般的ですか? 脆弱性はあるものの、活発なイーサリアムブロックチェーンコントラクトはまだありますか? この記事では、私たちが開発した新しい分析ツールを使用して、イーサリアムライブブロックチェーン上のコントラクトを分析することにより、この質問に答えます。







未チェック送信エラーとは何ですか?



イーサコントラクトを別のアドレスに送信するには、 sendキーワードを使用するのが最も簡単な方法です。 これは、各オブジェクトに対して定義されたメソッドとして機能します。 たとえば、次のコードスニペットは、ボードゲームを実装するスマートコントラクトにあります。











/*** Listing 1 ***/ if (gameHasEnded && !( prizePaidOut ) ) { winner.send(1000); //    prizePaidOut = True; }
      
      





ここでの問題は、 送信メソッドが失敗する可能性があることです。 うまくいかない場合、勝者はお金を受け取りませんが、変数prizePaidOutはTrueに設定されます。



winner.send()関数が失敗する場合がある2つの異なるケースがあります。 後でそれらの違いを分析します。 最初のケースは、 勝者のアドレスが契約(ユーザーアカウントではない)であり、この契約のコードが例外をスローすることです(たとえば、「ガス」を使いすぎる場合)。 もしそうなら、おそらくこの場合、それは「勝者の間違い」です。 2番目のケースはそれほど明白ではありません。 Ethereum仮想マシンには、「 callstack 」(コールスタック深度)と呼ばれる限られたリソースがあり、このリソースは、以前にトランザクションで実行された別のコントラクトコードで使用できます。 sendコマンドが実行されるまでにコールスタックがすでに使い果たされている場合、 勝者の決定方法に関係なく、コマンドは失敗します。 勝者の賞品は、彼自身の過失なく破棄されます!









このエラーをどのように回避できますか?



Ethereumのドキュメントには、この潜在的な危険についての簡単な警告[3]が含まれています。「 送信の使用には多少の危険があります。 「ガス」は終了します。したがって、安全なブロードキャストを確保するために、 送信の戻り値を常に確認するか、さらに良いことに、受信者がお金を引き出すテンプレートを使用します。

2つの文。 1つは、 sendの戻り値をチェックして、正常に完了したかどうかを確認することです。 そうでない場合は、例外をスローして状態をロールバックします。











  /*** Listing 2 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (winner.send(1000)) prizePaidOut = True; else throw; }
      
      





これは現在の例に対する適切な修正ですが、常に正しい決定とは限りません。 ゲームが終了したときに勝者と敗者が運命を取り戻すように例を変更するとします。 「正式な」ソリューションの明らかな用途は次のとおりです。











 /*** Listing 3 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (winner.send(1000) && loser.send(10)) prizePaidOut = True; else throw; }
      
      





ただし、これは追加の脆弱性を導入するため、間違いです。 このコードは勝者をコールスタック攻撃から保護しますが、 勝者敗者を互いに脆弱にします。 この場合、コールスタック攻撃を防ぎたいが、何らかの理由でsendコマンドが失敗した場合は実行を継続します。



したがって、ベストプラクティス(Solidityにも同様に適用されますが、Ethereum and Serpent Programmer's Guideで推奨)でさえ、 コールスタックリソースをチェックすることです。 マクロcallStackIsEmpty()を定義できます。これは、 callstack 空の場合にのみエラーを返します。











 /*** Listing 4 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (callStackIsEmpty()) throw; winner.send(1000) loser.send(10) prizePaidOut = True; }
      
      





さらに良いことには、イーサリアムのドキュメントからの推奨事項-「受信者がお金を受け取るテンプレートを使用する」は少しわかりにくいですが、説明があります。 提案は、 送信失敗の影響が分離され、一度に1人の受信者のみに影響するようにコードを再編成することです。 以下は、このアプローチの例です。 ただし、このヒントはアンチパターンでもあります。 彼は、受信者自身コールスタックをチェックする責任を負い、同じトラップに陥ることを可能にします。











 /*** Listing 5 ***/ if (gameHasEnded && !( prizePaidOut ) ) { accounts[winner] += 1000 accounts[loser] += 10 prizePaidOut = True; } ... function withdraw(amount) { if (accounts[msg.sender] >= amount) { msg.sender.send(amount); accounts[msg.sender] -= amount; } }
      
      





多くの高度に開発されたスマートコントラクトは脆弱です。 キングオブザエアオブザスローンの宝くじは、この間違いの最も有名なケースです[4] 。 このエラーは、200のエーテル(今日の価格で2000米ドル以上の価値がある)が宝くじの正当な勝者を獲得できなくなるまで気づかれませんでした。 King of the Etherの対応するコードは、リスト2のコードに似ています。幸い、この場合、契約開発者は、契約の無関係な関数を「手動オーバーライド」として使用して、スタックした資金を解放できました。 それほど慎重でない管理者でも、同じ機能を使用してブロードキャストを盗むことができます!







未送信エラーのライブイーサリアム契約のスキャンを継続しました パート2



All Articles