paint-brush
スマート コントラクトの整数オーバーフロー/アンダーフロー脆弱性を解決する@dansierrasam79
2,180 測定値
2,180 測定値

スマート コントラクトの整数オーバーフロー/アンダーフロー脆弱性を解決する

Daniel Chakraborty9m2023/02/11
Read on Terminal Reader

長すぎる; 読むには

データ型は、コーディングするプログラミング言語に応じて、プログラマーが時間をかけて指定するか指定しないかのどちらかです。データ型は主要な算術演算にとって重要ですが、計算の範囲は限られています。実世界での整数オーバーフローの最も一般的な例は、移動したマイルの値が 000000 にリセットされる車両で発生します。
featured image - スマート コントラクトの整数オーバーフロー/アンダーフロー脆弱性を解決する
Daniel Chakraborty HackerNoon profile picture


私たちのほぼ全員が、Google スプレッドシートまたは Microsoft Excel を使用して、何らかの計算用のデータを入力したことがあります。従業員の名前、電話番号、役職、給与を入力するとします。


スプレッドシートまたは Excel でレコードまたはケースが表示される最も単純な形式は次のとおりです。

Excel または Google スプレッドシートのレコードまたはケース

おわかりのように、従業員の名前と役職はテキストで構成され、電話番号と給与は一連の数字で構成されています。


したがって、セマンティックの観点から、私たちは人間として、これらのフィールドが現実の世界で何を意味するかを理解し、それらを区別することができます。


明らかに、違いを見分けるのにコンピューター サイエンスの学位は必要ありませんが、コンパイラーまたはインタープリターはこのデータをどのように処理するのでしょうか?

データ型

これがデータ型の出番であり、プログラマーがコーディングするプログラミング言語に応じて、時間をかけて指定するかどうかを決定します。


つまり、従業員名と役職の下のデータ ポイントは文字列と呼ばれます。もちろん、小数点がないため、給与は明らかに整数です。簡単に言えば、これらはコーディング時にそのように宣言する必要があるデータ型であり、そのデータ型に関連付けられた適切な操作のみが実行されます。


これは、Solidity で整数データ型を宣言する方法です。

とはいえ、上のスプレッドシートの電話番号フィールドには、一意の文字列として使用されるデータ ポイントが含まれていますが、その説明は別の機会にします。ここでは、基本的な算術演算をすべて実行したプリミティブ データ型に焦点を当てます。


はい、重要な算術演算には重要ですが、計算の範囲が限られている整数データ型について話しているのです。

整数のオーバーフロー/アンダーフローが発生するのはなぜですか?

おそらく、実世界での整数オーバーフローの最も一般的な例は、車両で発生します。走行距離計とも呼ばれるこれらのデバイスは、通常、車両が何マイル走行したかを追跡します。


では、移動したマイルの値が 6 桁のオドメーターで 999999 の符号なし整数値に達するとどうなるでしょうか?


理想的には、さらにマイルが加算されると、この値は 1000000 に達するはずですよね?しかし、7 桁目の規定があるため、これは発生しません。


代わりに、以下に示すように、移動したマイルの値が 000000 にリセットされます。

走行距離計の整数オーバーフロー


定義上、7 桁目が使用できないため、正確な値が表されないため、「オーバーフロー」が発生します。


あなたは写真を手に入れますよね?


逆に、それほど一般的ではなくても、その逆も発生する可能性があります。言い換えれば、記録された値が範囲内で利用可能な最小値よりも小さい場合、これは「アンダーフロー」として知られています。


ご存知のように、コンピュータは整数をメモリに 2 進数に相当するものとして格納します。ここで、簡単にするために、8 ビット レジスタを使用しているとしましょう。


したがって、符号なし整数 511 を格納する場合、これは次のように分割されます。


= 2⁸*1 + 2⁷*1 + 2⁶*1 + 2⁵*1 + 2⁴*1 + 2³*1 + 2²*1 + 2¹*1 + 2⁰*1

= 256 + 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1

= 111111111


各ビットが 1 の場合、おわかりのように、それ以上の値を格納することはできません。


一方、数値 0 を 8 ビット レジスタに格納する場合は、次のようになります。


= 2⁸*0 + 2⁷*0 + 2⁶*0 + 2⁵*0 + 2⁴*0 + 2³*0 + 2²*0 + 2¹*0 + 2⁰*0

= 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0

= 000000000


各ビットが 0 の場合、これより低い値を保存できないことがわかります。


つまり、このような 8 ビット レジスタに使用できる整数の範囲は 0 ~ 511 です。それで、整数512または-1をそのようなレジスタに格納することは可能ですか?


もちろん違います。その結果、オドメーターの例で移動したマイルのリセット値に似た値をバイナリ値として保存します。


明らかに、そのような数に快適に対応するには、さらに数ビットのレジスタが必要です。そうしないと、オーバーフローの状況が再び発生する危険があります。


符号付き整数の場合、負の整数も格納します。そのため、上記のように、許容範囲より小さい数値またはゼロより小さい数値を格納しようとすると、アンダーフローが発生します。


繰り返しになりますが、計算を行うポイントは決定論的な結果を取得することであるため、これはせいぜい面倒ですが、最悪の場合、数百万の損失を引き起こす可能性があります。特に、これらの整数オーバーフローまたはアンダーフロー エラーがスマート コントラクトで発生した場合。

整数のオーバーフロー/アンダーフローの脆弱性がこれほど被害をもたらすのはなぜですか?

整数のオーバーフローとアンダーフローは何十年も前から存在していましたが、スマート コントラクトのバグとして存在することで問題が発生しました。攻撃者がこのようなエラーを利用すると、大量のトークンのスマート コントラクトを流出させる可能性があります。


おそらく、この種のバグが最初に発生したのはブロック 74638 で、3 つのアドレスに対して数十億のビットコインが作成されました。ソフトフォークによってこのエラーを解決するには数時間かかり、ブロックが破棄され、トランザクションが無効になります。


たとえば、2,100 万ビットコインを超える取引は拒否されました。これは、前述の 3 つのアカウントに多額の送金を行った場合と同様に、オーバーフロー トランザクションでも同じでした。


ただし、イーサリアムのスマート コントラクトでも整数のオーバーフローとアンダーフローが発生しており、BeautyChain もその顕著な例です。


この場合、スマート コントラクトには次の 1 行のコードに誤りがありました。


整数オーバーフローを引き起こしたその単一のコード


その結果、攻撃者は理論的に無制限の BEC トークンを受け取ることができ、理論的には (2²⁵⁶)-1 の値になる可能性があります。


脆弱な batchTransfer 関数


ここで、整数のアンダーフロー/オーバーフローが発生するスマート コントラクトの別の例を見てみましょう。

スマート コントラクトにおける整数のオーバーフロー/アンダーフローの脆弱性を打破する

一見すると、この例では相互作用する 2 つのコントラクトがあり、整数オーバーフローの場合に何が起こるかを示しています。


以下に示すように、TimeLock 契約では資金の入金と出金が可能ですが、違いがあります。後者は、一定期間後にのみ実行できます。この場合、1 週間以内にのみ資金を引き出すことができます。


タイムロック契約


ただし、Attack コントラクトで攻撃関数を呼び出すと、タイム ロックが無効になるため、攻撃者はすぐに残高を引き出すことができます。

つまり、type(uint).max+1-timeLock.locktime(address(this)) ステートメントで整数オーバーフローが発生するため、タイム ロックが解除されます。


整数オーバーフローを使用すると、デポジットのタイム ロックがすぐに解消されます


たとえば、上記のコードを使用して両方のスマート コントラクトをデプロイしたら、次に示すように、TimeLock コントラクトで入金関数と引き出し関数を呼び出して、タイムロックが保持されているかどうかをテストできます。


2 ETH入金後の残高


ご覧のとおり、2 Ether を選択すると、上記の 2 Ether のスマート コントラクト残高が得られます。


2 ETHの入金


具体的には、残高機能のフィールドにアドレスを追加し、残高ボタンをクリックすると、2 Ether の残高を保持する特定のアドレスを確認できます。


2 ETH を保持するアドレスは?


ただし、前述のように、タイム ロックが設定されているため、これらの資金をまだ引き出すことはできません。取り消し機能を押した後にコンソールを見ると、赤い「x」記号で示されるエラーが見つかります。以下に示すように、このエラーの理由はコントラクトによって「ロック時間が期限切れになっていません」と示されています。


ロック時間未期限エラー


次に、以下に示すように、デプロイされた攻撃コントラクトを見てみましょう。



現在、攻撃機能を呼び出すには、1 Ether 以上の値を預ける必要があります。したがって、この例では、以下に示すように 2 つの Ether を選択しました。


最初に 2 ETH を入金してください!


この後、「攻撃」を押します。預け入れた 2 イーサはすぐに引き出され、以下の 2 イーサの残高によって証明されるように攻撃コントラクトに追加されます。


2 ETH を Attack スマート コントラクトに転送


明らかに、デポジットを行うとすぐに長時間のロックが有効になるため、これは発生しないはずです.もちろん、type(uint).max+1-timeLock.locktime(address(this)) ステートメントは、increaseLockTime 関数を使用してロック時間を短縮します。これがまさに、イーサ残高をすぐに引き出すことができる理由です。


整数のオーバーフローとアンダーフローの脆弱性を修正する方法はありますか?

整数のオーバーフロー/アンダーフローの脆弱性を回避する 2 つの方法

整数のオーバーフロー/アンダーフローの脆弱性が壊滅的なものになる可能性があることを認識して、このバグに対するいくつかの修正が展開されました。これらの修正と、そのようなエラーを回避する方法の両方を見てみましょう。


方法 1: OpenZeppelin の SafeMath ライブラリを使用する

組織としての Open Zeppelin は、サイバーセキュリティ技術とサービスに関して多くを提供しており、 SafeMath ライブラリはスマート コントラクト開発リポジトリの一部となっています。このリポジトリには、スマート コントラクト コードにインポートできるコントラクトが含まれており、SafeMath ライブラリはその 1 つです。


SafeMath.sol 内の関数の 1 つが整数オーバーフローをチェックする方法を見てみましょう。


tryAdd SafeMath 関数


ここで、a+b の計算が行われると、c<a であるかどうかのチェックが行われます。もちろん、これは整数オーバーフローの場合にのみ当てはまります。


Solidity のコンパイラ バージョンが 0.8.0 以降に到達すると、整数のオーバーフローとアンダーフローのチェックが組み込まれるようになりました。そのため、このライブラリを使用して、言語とこのライブラリの両方を使用するときに、この脆弱性をチェックすることができます。もちろん、スマート コントラクトで 0.8.+ 未満のコンパイラ バージョンが必要な場合は、このライブラリを使用してオーバーフローやアンダーフローを回避する必要があります。


方法 2: コンパイラの 0.8.0 バージョンを使用する

前述のように、スマート コントラクトに 0.8.0 以降のコンパイラ バージョンを使用している場合、このバージョンにはそのような脆弱性に対する組み込みのチェッカーがあります。


実際、上記のスマート コントラクトで動作するかどうかを確認するために、コンパイラのバージョンを「^0.8.0」に変更して再デプロイすると、次の「revert」エラーが発生します。


整数オーバーフローを防ぐエラーを元に戻す


もちろん、タイムロック値のオーバーフローをチェックしているため、2 Ether の入金は行われません。その結果、そもそも資金が入金されていないため、引き出しはできません。


Attack コントラクトへの 2 ETH の転送が阻止されました


間違いなく、ここでは Attack.attack() 関数呼び出しが機能していないので、問題ありません。

オーバーフロー/アンダーフロー脆弱性のまとめ

この長いブログ投稿から収集すべきことがあるとすれば、BEC 攻撃のように、この脆弱性を無視するとコストがかかる可能性があるということです。おわかりのように、チェックしないままにしておくと、悪意のないエラーが簡単に発生します。または、ハッカーがこの脆弱性を悪用するのと同じくらい簡単です。


そういえば、BEC 攻撃がどのように発生したかについての私たちの理解を利用して、この脆弱性を認識することは、提供されている修正のおかげで、スマート コントラクトを作成する際の攻撃を防ぐのに大いに役立ちます。他のいくつかのスマート コントラクトの脆弱性があなたをつまずかせるのを待っているとしても.