スマートコントラクト監査の初心者向けガイドへようこそ! スマートコントラクトの監査を開始するための最良の方法のXNUMXつは、スマートコントラクトのいくつかの一般的なタイプの脆弱性に飛び込んで調べることです。
イーサリアムのSolidityプログラミング言語の基本をすでに理解していると役に立ちます。 NoobSolidityプログラマーによって書かれたコードのいくつかを見ていきます。
再入可能攻撃
実世界のシナリオ:
あなたが50個のチョコレートを持っていると想像してください。 あなたには、一度に2つのチョコレートしか受け取れないようにしたいたずらな妹がいます。 また、虫歯になるのではないかと心配して、10日にXNUMX個以上のチョコレートを彼女に与えたくありません。 これを確実にするために、毎晩あなたはあなたと一緒に残っているチョコレートの数を数えます。 これでうまくいくと思いますか? それとも、妹にハッキングされますか?
残念ながら、それは機能しません! あなたの妹は、あなたが夕方になるまであなたが持っているチョコレートの数に気づいていないことを理解しています。 そのため、翌日、妹が夕方までに6回あなたを訪ね、毎回2つのチョコレートを受け取ります。 これは、リエントラント攻撃と呼ばれるものです。
ここでは、妹が2つのチョコレートを受け取るたびにカウントを更新するのではなく、夕方に持っているチョコレートのカウントを更新しています。 これは、スマートコントラクトでも起こります。 スマートコントラクトは、攻撃者が実際にコントラクトからある程度の暗号を何度も引き出すのに忙しい間、特定のバランスを想定しています。
実際のコード例:
このコードは、と呼ばれるスマートコントラクトに属しています Unbanked。 msg.senderの残高がある限り、誰でも銀行口座を持たない契約からエーテルを引き出すことができます(つまり、 withdraw
関数)は、引き出しを要求された金額以上です。
function withdraw(uint _amount) { require(balances[msg.sender] >= _amount); msg.sender.call.value(_amount)(); balances[msg.sender] -= _amount;
}
必要な量のエーテルをに送信するために使用されるcallキーワードがあることに注意してください。 msg.sender
。 攻撃者は、泥棒と呼ばれるコントラクトを作成することでこれを悪用できます。このコントラクトでは、 fallback()
関数。 NS fallback()
Solidityの関数は、エーテルがスマートコントラクトに送信されたときに実行される特別な関数です。
これは、攻撃者ができることを意味します withdraw関数を再帰的に呼び出します。 したがって、スマートコントラクトが更新される前に、 msg.sender
コードの最後の行で、攻撃者はすでに何度もetherを撤回しています。 callキーワードを使用する前に残高を更新すると、これを回避できます。 チェック-効果-相互作用 パターン。
影響:
史上初のリエントラント攻撃 2016年にDAO(分散型自律組織)で発生し、約50万ドルのハッキングが発生しました。 このハックを逆転させるために、イーサリアムコミュニティはイーサリアムブロックチェーンを分割し、ETC(イーサリアムクラシック)とETH(イーサリアム)を生み出しました。
算術オーバーフローとアンダーフロー
実世界のシナリオ:
思考ゲームをしましょう。 それは糸車で構成されており、勝者は彼が糸車に乗ることができる最大数に基づいて決定されます。 ホイールは256から-256まで全体にマークされています。
ゲームのルールは、すべてのプレーヤーのポインターが各スピンの開始時に0にあることです。 また、プレーヤーは負の数の方向にのみスピンすることができます。 このゲームにどのように勝ちますか?
毎回このゲームに勝つための良い戦略は、ホイールが-256まで回転し、その後一度に256に回転するような力でホイールを回転させることです。 これが可能なのは、256がホイールの-256の直後に来るためです。 これは、算術アンダーフローと呼ばれるものです。 そして、算術オーバーフローはこれとは逆です。
実際のコード例:
An アンダーフローまたはオーバーフロー 算術演算が最小値または最大値に達したときに発生します。
function withdraw(uint _amount) public { require(balances[msg.sender] - _amount > 0); address payable to = payable(msg.sender); to.transfer(_amount); balances[msg.sender] -= _amount;
}
_amount
withdraw関数のパラメーターは符号なし整数です。 バランスマッピングの値(Pythonの辞書、C ++またはJavaのキーと値のペアのようなもの)も符号なし整数です。
mapping(address => uint256) public balances
必要なステートメントは、 msg.sender
ポジティブかどうか。 ただし、金額がの残高よりも多い場合でも、このステートメントは常に当てはまります。 msg.sender
。 これは、両方が balances
および _amount
変数は符号なし整数型であり、それらの算術結果(アンダーフロー後)も符号なし整数になります!
また、覚えているかもしれませんが、符号なし整数は常に正です。 これは、攻撃者がスマートコントラクトから無制限の量のEtherを引き出すことができることを意味します! この脆弱性の詳細な例と実装コードを見つけることができます こちら.
ここで注意すべきもうXNUMXつの重要な点は、たとえばXNUMXつの符号なし整数間の算術演算も符号なし整数であるということです。 これがスマートコントラクトで見落とされると、望ましくないセキュリティ違反が発生する可能性があるため、危険な場合があります。
function votes(uint postId, uint upvote, uint downvotes) { if (upvote - downvote < 0) { deletePost(postId) }
}
上記の例でお気づきかもしれませんが、ifステートメントは次のようにまったく無意味です。 upvote - downvote
常に前向きになります。 そして、投稿は削除されます downvotes
より大きい upvotes
。 このような攻撃を回避するには、Solidityコンパイラのバージョンを次のバージョンよりも大きくすることをお勧めします。 0.8.0.
影響:
と呼ばれるコイン PoWHコイン 2017年に発売されました。これはPonziゲームでしたが、算術オーバーフローのバグが原因でハッキングされ、当時は約866ETHまたは$950,000の損失が発生しました。 あなたはこれについて詳細に読むことができます こちら.
必読: タイニーマンへの攻撃からの教訓、アルゴランドで最大のDEX
サービス拒否攻撃
実世界のシナリオ:
あなたがビットコイン工科大学にいると想像してみてください。 みんなに共通のダイニングテーブルがあることを除けば、すべてが順調に見えます。 そして残念ながら、あなたのクラスの誰よりも先にダイニングテーブルを常に占有することができる別のクラスの人々はほとんどいません。
実際のシナリオでは、彼らはすべての人への不可欠なサービスを拒否しているため、貴重な時間が失われます。 これは、「サービス拒否攻撃」と呼ばれるものです。
実際のコード例:
と呼ばれるゲームで キングオブエーテル、誰でも王になることができます。 しかし、王になるためのルールは、人は現在の王よりも多くのエーテルを預ける必要があるということです。 これは、 claimThrone()
人がエーテルを前の王に直接送り、新しい王になるというエーテルの王契約の機能。
function claimThrone() external payable { require(msg.value > balance, "Need to pay more to become the king"); (bool sent, ) = king.call{value: balance}(""); require(sent, "Failed to send Ether"); balance = msg.value; king = msg.sender; }
ご想像のとおり、このコードはDoS攻撃に対して脆弱ですが、どのようにしたらよいでしょうか。 このためには、イーサリアムにはXNUMXつのタイプのアドレスがあることを理解する必要があります-最初は 外部のアドレス 所有するアカウントまたは単にウォレットのアドレス、そしてXNUMX番目は 契約住所。 これで、これらのアドレスタイプのいずれかからエーテルを送信できます。
この場合、契約アドレスからエーテルが送信されると、契約が王になります。 しかし、この新しい契約には fallback()
契約がエーテルを受け入れたい場合に必要な機能。 次に、新しい人がやって来て、 claimThrone()
関数、それは常に失敗します!
これも部分的に発生することに注意してください claimThrone()
関数は、XNUMX番目の必須ステートメントでエーテルの転送が成功したかどうかを明示的にチェックします。 完全なコードを見つけて、DoS攻撃を行うことができます こちら.
コードに大きなサイズの配列上のループがある場合、コードがDoS攻撃に対して脆弱になる可能性もあります。 これは、 ガス制限 このような場合、超過する可能性があります。 あなたはそれについて読むことができます こちら.
影響:
と呼ばれるゲーム ガバメントメンタル、明らかにポンジースキームでしたが、支払いを処理するために大量のガスが必要だったため、1100エーテルでスタックしました。
安全でないランダム性
実世界のシナリオ:
かつて、彼の猿ペスキーを常に伴っていたヘスキーという名前の男がいました。 ヘスキーは宝くじゲームを実施し、良い利益を上げました。 ある日、アリスはヘスキーがサルのペスキーをじっと見つめていることに気づきました。 それから彼女は彼が一枚の紙に何かを書いているのを見て、それを封筒に封印しました。 不思議なことに、彼女はさらに調査することにしました。
その夜遅く、アリスは、封印された封筒を公に開くことによって宝くじの勝者が決定されるのを見ました。 数日間彼を見た後、アリスはヘスキーがペスキーのジェスチャーを見て、当選した宝くじの番号を決定したことを理解しました(たとえば、サルが頭をかいた場合、ヘスキーは10を書き留めました)! これで、アリスは各宝くじに当選する公式を手に入れ、正しい番号の宝くじを購入する必要がありました。
ヘスキーは、宝くじの当選者を決定する彼の「ランダムな」方法は決して理解できないと思っていましたが、彼は確かに間違っていました。
実際のコード例:
この例では、ブロックの番号とそのブロックのタイムスタンプの組み合わせのハッシュに基づいて乱数が生成されます。 次に、このハッシュが回答変数に割り当てられます。 これで、この(一見)乱数を推測した人には、1イーサが与えられます。 これはハッキングできないと思いますか?
function guess(uint _guess) public { uint answer = uint( keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)) ); if (_guess == answer) { (bool sent, ) = msg.sender.call{value: 1 ether}(""); require(sent, "Failed to send Ether"); } }
いいえ! 攻撃者は、コードをコピーして貼り付けて回答変数に割り当てられた値を生成し、同じ回答変数をに渡すだけで、この乱数を推測できます。 guess()
関数!
guessTheRandomNumber.guess(answer);
あなたは完全なコードを見つけることができます こちら。 この攻撃を回避するには、次のような検証可能なランダム関数を使用することをお勧めします。 チェーンリンクVRF.
影響:
攻撃により約400ETHが失われました スマートビリオンズ宝くじ 契約する。 驚いたことに、契約宝くじ自体でさえポンジースキームでした(痛い!)。
時間操作
実世界のシナリオ:
聡はクッキーを食べるのが大好きです。 彼は母親が作るあらゆる種類のクッキーが大好きです。 しかし、彼の母親は非常に厳格で、クッキーを食べすぎるのは彼にとって良くないと感じています。 そのため、彼の母親は、午後8時にのみCookieを取得するというルールを作成します。
その日の午後7時45分、サトシは母親に駆け寄り、クッキーを求めます。 彼の母親は尋ねます-「今何時ですか?」
「8時です!」 –彼は答えます。
"わかった。 次に、食器棚からクッキーを取り出します。」
このようにして、聡はクッキーを手に入れることができるように、15分で時間をうまく操作することができました! なんてクッキーに飢えたチャップ!
実際のコード例:
ブロックのタイムスタンプは、約 15 seconds 鉱夫によって。 このようにして、マイナーは適切なタイムスタンプを設定し、自分がマイニングする同じブロックに自分のトランザクションを含めることができます。 関数 play()
G-Dotと呼ばれるゲーム契約に属しています。
function play() public { require(now > 1640392200 && neverPlayed == true); neverPlayed = false; msg.sender.transfer(1500 ether);
}
この契約は、プレイ機能を最初に呼び出したプレイヤーに1500エーテルを報酬します。 ただし、ご覧のとおり、play関数を呼び出すことができるのは、トランザクションのnowまたはblock.timestampの呼び出しが含まれている場合のみです。 play()
関数は、より大きい エポックタイム 1640392200.
マイナーは、このタイムスタンプを簡単に操作して、 play()
彼自身が最初のプレーヤーになるように同じブロックで機能します。 このようにして、鉱夫がゲームに勝つことが保証されます!
影響:
block.timestampを使用して、 政府の したがって、時間操作攻撃に対して脆弱でした。
QuillAuditsに連絡する
QuillAuditsは、によって設計された安全なスマートコントラクト監査プラットフォームです。 クイルハッシュ
テクノロジー
これは、スマートコントラクトを厳密に分析および検証して、静的および動的分析ツール、ガスアナライザー、およびシミュレーターを使用した効果的な手動レビューを通じてセキュリティの脆弱性をチェックする監査プラットフォームです。 さらに、監査プロセスには、広範な単体テストと構造分析も含まれます。
スマートコントラクト監査と侵入テストの両方を実施して、可能性を見つけます
プラットフォームの整合性を損なう可能性のあるセキュリティの脆弱性。
スマートコントラクトの監査についてサポートが必要な場合は、お気軽に専門家にご連絡ください ここで!
私たちの仕事を最新にするには、私たちのコミュニティに参加してください:-
Twitter | LinkedIn | Facebook | Telegram
ポスト スマートコントラクト監査の初心者ガイド:パート1 最初に登場した クイルハッシュブログ.
ソース:https://blog.quillhash.com/2022/01/19/beginners-guide-to-smart-contract-auditing-part-1/
- "
- &
- 000
- 2016
- 7
- 私たちについて
- 住所
- すべて
- 既に
- しかし
- 分析
- 監査
- 自律的
- 開始
- BEST
- Bitcoin
- ブロックチェーン
- バグ
- 購入
- コール
- 例
- 小切手
- クラシック
- コード
- コイン
- 組み合わせ
- コマンドと
- コミュニティ
- 含まれています
- 縮小することはできません。
- 契約
- クッキー
- 可能性
- 作成
- クリプト
- 電流プローブ
- DAO
- 中
- 分権化された
- サービス拒否
- 詳細
- デックス
- ダウン
- 簡単に
- 食べる
- ETH
- エーテル
- イーサリアム
- エテリアムブロック鎖
- イーサリアムクラシック
- 例
- 悪用する
- 終わり
- 名
- 無料版
- function
- ゲーム
- Games
- GAS
- 生成する
- GitHubの
- 行く
- 良い
- ガイド
- ハック
- ハック
- ハッシュ
- こちら
- 認定条件
- HTTPS
- 調べる
- IT
- Java
- join
- ジャンプ
- 神様です。
- 言語
- 大
- LINE
- 長い
- 探して
- 宝くじ
- man
- 百万
- 母
- 番号
- 組織
- 紙素材
- パターン
- 支払う
- のワークプ
- ピース
- プラットフォーム
- プレイ
- プレイヤー
- ポンチ
- Ponzi Scheme
- 電力
- プロセス
- プログラマ
- プログラミング
- 公共
- 逆
- レビュー
- 報酬
- ルール
- 聡
- セキュリティ
- セッションに
- スマート
- スマート契約
- スマート契約
- So
- 固い
- 何か
- スピン
- split
- 開始
- ステートメント
- 戦略
- 成功した
- 首尾よく
- テク
- テスト
- 介して
- 時間
- 豊富なツール群
- トランザクション
- 縛られていない
- 大学
- 更新版
- 値
- 脆弱性
- 脆弱性
- 脆弱な
- 財布
- この試験は
- ホイール
- 誰
- win
- 仕事
- 書き込み