暗号資産購入
マーケット
スポット
先物
金融
特別企画
さらに
reward-center新規登録ゾーン
レポート分析詳細
業界調査

スマートコントラクトにおける一般的な脆弱性と攻撃の概要

CoinEx logo
投稿日: 2024-02-14

スマートコントラクトとは何か?

イーサリアムには、外部所有アカウント(EOA)とスマートコントラクトアカウント(SCA)という2つの一般的なタイプのアカウントがあります。

EOAは、資金を保管し、アプリケーションと対話するために一般的に使用される電子金融口座に非常によく似ています。例えば、ユーザーはPayPalを通じて法定通貨を入金し、様々なウェブサイト、店舗、アプリと支払いのためにやり取りします。DeFiマイナーは通常、EOAに暗号資産を保管し、DeFi dAppsと対話し、利益を得るためにdAppsに資金を預けます。しかし、EOAには電子金融口座にはない特徴があります:ユーザーは秘密鍵の所有権を通じてEOAに対する制御を検証する必要があります—あなたの鍵でなければ、あなたのコインではありません。

SCAもまた、本質的に実行可能なバイトコードのセグメント(スマートコントラクトとしても知られる)に関連付けられたアカウントの一種です。スマートコントラクトは様々なビジネスロジックを記述し、dAppsのバックエンドとして機能します。しかし、従来のチューリング完全な開発言語と比べてより多くの制約があるにもかかわらず、準チューリング完全なスマートコントラクトは依然として数多くの攻撃に脆弱であり、ブロックチェーン業界に数え切れないほどの打撃を与えてきました。

一般的なスマートコントラクト攻撃

1.リエントランシー攻撃

最も一般的で悪名高い攻撃は、イーサリアムクラシックの創設につながったイーサリアムのフォークの原因となったリエントランシー攻撃です。2016年、ハッカーはDAOコントラクトにリエントランシー攻撃を実行し、当時1億5000万ドル以上の価値があった3,600,000 ETHを盗みました。イーサリアムの初期段階で発生したこの攻撃は、エコシステムに壊滅的な打撃を与え、投資家の信頼を粉々に砕き、最終的にフォークにつながりました。

具体的なロジック

リエントランシー攻撃の原理をより良く理解するために、以下の例を挙げます。銀行Bは以前に銀行Aにお金を貸していました。ある日、銀行Bは銀行Aに送金を開始し、全額を銀行Bに返還するよう要求します。通常のプロセスは次の通りです:

ステップ1:銀行Bが資金の引き出しを要求

ステップ2:銀行Aが銀行Bに資金を送金

ステップ3:銀行Aが銀行Bへの送金成功を確認

ステップ4:銀行Aが銀行Bの口座残高を更新します。

しかし、銀行Bがステップ2の後に抜け道を作り、ステップ3での確認なしに銀行Aからすべての資金を要求し続けると、銀行Bにある銀行Aの口座残高は変更されません。この再帰的な呼び出しにより、銀行Aの資産がすべて空になってしまいます。

スマートコントラクトにおける一般的な脆弱性と攻撃の概要


関連するスマートコントラクト

銀行Aのコントラクトには2つの関数が含まれています:

  • deposit():銀行Aに資金を預け入れ、ユーザーの残高を更新する預金関数
  • withdraw():ユーザーが銀行Aからすべての資金を引き出すことを可能にする引き出し関数
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 2
  • 銀行Bの攻撃コントラクトは主に、receive()コールバック関数をトリガーするループを含んでおり、これが銀行コントラクトのwithdraw()関数を呼び出して、1回の預金、1回の引き出し、そしてreceive()コールバック関数の呼び出しの連続を通じて銀行Aの資産を枯渇させ、最終的にAにおけるBの残高を更新します。これには2つの関数が含まれています:receive():ETHを受け取った時にトリガーされるコールバック関数で、銀行コントラクトのwithdraw()関数を再帰的に呼び出して引き出しを行います。
  • attack():まず銀行コントラクトのdeposit()関数を呼び出して残高を更新し、その後withdraw()関数を呼び出して最初の引き出しを開始し、receive()コールバック関数をトリガーしてwithdraw()を再帰的に呼び出し、銀行コントラクトの資産を枯渇させます。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 3


解決策

リエントランシーロックの実装

リエントランシーロックは、リエントランシーを防ぐために使用されるモディファイアで、呼び出しが再度呼び出される前に実行を完了する必要があることを保証します。例えば、Bank Bによる攻撃はBankコントラクトのwithdraw()関数を複数回呼び出す必要があるため、リエントランシーロックの実装により失敗します。

スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 4

使用方法

スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 5

2. tx.originの誤用

スマートコントラクトにおけるtx.originの主な機能は、トランザクションを開始した元のアカウントを取得することです。ここでは、スマートコントラクトにおける2つの一般的な変数について説明します:msg.sendertx.originです。msg.senderはスマートコントラクトを直接呼び出しているアカウントを取得しますが、ブロックチェーンの世界では、異なるスマートコントラクト(例えばDeFi Lego)のネストや相互呼び出しのため、tx.originを使用してトランザクションを開始した元のアカウントを取得する必要があります。dApp開発者がコード内でtx.originのセキュリティのみを確認し、攻撃者が中間契約をデプロイしてtx.originをバイパスし攻撃を仕掛けるセキュリティ検証を怠った場合、脆弱性が生じます。

具体的なロジック

ここでは、一般的な攻撃シナリオを深く理解するための例を挙げます。ビルはスマートウォレットを持っており、そのウォレットはビルが送金の発信者であることを確認します。あるとき、ビルはフィッシングサイトでNFTをミントしました。それによりウェブサイトはビルの身元を取得し、彼の身元を使用してスマートウォレットから送金を開始し、資産損失を引き起こしました。通常の状況では、ユーザーがこの罠に引っかかる可能性は低いですが、ウォレットを使用してdAppと対話する際、ユーザーは対話プロンプトをチェックすることをしばしば忘れます。例えば、両方がMint()関数を含む場合、不注意なユーザーはフィッシングの罠に簡単に陥る可能性があります。フィッシングウェブサイト内のビジネスロジックには罠がはびこっているため、通常の対話中にエラーがないか対話プロンプトを確認することが重要です。

スマートウォレット契約

スマートウォレット契約には1つの機能が含まれています:

  • transfer():ウォレット所有者(この場合はビル)のみが開始できる引き出し機能。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 6

フィッシング攻撃契約

フィッシング攻撃契約では、Mint()関数がユーザーをハッカーのアドレスに資金を送金するよう誘導します。これには1つの関数が含まれます:

  • Mint(): 呼び出されると、フィッシング機能は内部でWallet契約の transfer() を実行します。オリジナルの発信者はユーザー自身(この例ではBill)であるため、 verification require(tx.origin == owner, "Not owner") は問題になりません。しかし、送金先のアドレスはすでにハッカーのアドレスに改ざんされており、資金の盗難につながります。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 7

解決策

1. tx.originの代わりにmsg.senderを使用する

契約呼び出しの数に関係なく(契約A → 契約B →...→ ターゲット契約)、msg.sender、つまり直接の呼び出し元のみを検証し、悪意のある中間契約によって引き起こされる攻撃を避けます。

スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 8

2. tx.origin == msg.senderを検証する

この方法は悪意のある契約を遠ざけることができますが、開発者は自身のビジネスの現実を考慮する必要があります。なぜなら、これは事実上すべての他の外部契約呼び出しを孤立させるからです。

スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 9

3. 乱数生成器(RNG)攻撃

これは、2018年から2019年頃のギャンブルや賭けに関するdAppトレンドに遡ります。一般的に、開発者はスマートコントラクトで特定のシードを使用して、抽選時に当選者を選ぶためのランダムな数字を生成します。一般的なシードには、block.number、block.timestamp、blockhash、keccak256などがあります。しかし、マイナーはこれらのシードを完全に制御できるため、悪意のあるマイナーが変数を操作して利益を得る場合があります。

一般的なサイコロ契約

サイコロ契約には1つの機能があります:

  • Bet(): ユーザーが賭け数字を入力し、ETHを支払う賭け機能。複数のシードを使用してランダムな数字が生成され、賭け数字がランダムな数字と一致した場合、ユーザーは賞金プール全体を獲得します。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 10

マイナーの攻撃契約

マイナーは、勝利するランダム数字を事前に計算し、同じブロックで実行するだけで勝利できます。これには1つの機能が含まれます:

  • attack(): マイナーが勝利するランダム数字を事前に計算する賭け攻撃機能。同じブロックで実行されるため、同じブロック内のblockhash(block.number - 1)とblock.timestampは同じです。その後、マイナーはサイコロ契約のBet()を呼び出して攻撃を完了します。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 11

解決策

オラクルプロジェクトが提供するオフチェーンのランダム数字を使用する

Chainlinkのようなオラクルプロジェクトが提供するサービスを通じて、オンチェーンの乱数がオンチェーンのコントラクトに注入され、ランダム性とセキュリティが確保されます。しかし、オラクルプロジェクトには中央集権化のリスクも伴うため、より成熟したオラクルサービスが必要となります。

4. リプレイ攻撃

リプレイ攻撃は、以前に使用された署名を使用して取引を再開始し、資金を盗むものです。近年最も有名なリプレイ攻撃の一つは、Optimism上のマーケットメーカーWintermute から2000万$OPトークンが盗まれた事件で、これはクロスチェーンリプレイ攻撃でした。Wintermuteのマルチシグウォレットアカウントが一時的にイーサリアムメインネットにのみデプロイされていたため、ハッカーはイーサリアム上でWintermute がマルチシグアドレスをデプロイする際の取引署名を使用して、Optimismチェーン上で同じ取引を再実行し、Optimism上のマルチシグウォレットアカウントの制御権を獲得しました。マルチシグウォレットアカウントは本質的にスマートコントラクトアカウントであり、これはSCAとEOAの重要な違いも示しています。EOAの場合、通常のユーザーは1つの秘密鍵だけでイーサリアムとEVM互換チェーン上のすべてのアドレス(アドレス文字列は全く同じ)を制御できますが、SCAはデプロイ後、1つのチェーン上でのみ有効です。

具体的なロジック

ここでは、典型的なリプレイ攻撃(同一チェーンリプレイ攻撃)の例を提供します。Billはスマートウォレットを持っており、各取引の実行前に電子署名の入力が必要です。ハッカーのLucyがBillの電子署名を盗んだ場合、彼女はBillのスマートウォレットから資金を引き出すために無制限に取引を開始できます。

脆弱性のあるコントラクトは3つの機能で構成されています:

  • checkSig(): ECDSA検証機能で、検証結果が最初に設定された署名者であることを確認します。
  • getMsgHash(): ハッシュを生成する機能で、toとamountを組み合わせてハッシュを形成します。
  • transfer(): 転送機能で、ユーザーが流動性プールから資金を引き出すことを可能にします。署名に制限がないため、同じ署名を再利用でき、ハッカーが継続的に資金を盗むことができます。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 12

解決策

リプレイ攻撃を防ぐために、署名の組み合わせにnonceを含めます。このパラメータの原理は以下の通りです:

  • nonce: ブロックチェーンネットワーク上のEOAの取引回数を表す変数です。順序性と一意性を持ちます。取引が1回増えるごとに、nonce値は1ずつ増加します。ブロックチェーンネットワークは、取引のnonceがアカウントの現在のnonceと一致しているかどうかをチェックします。したがって、ハッカーが使用済みの署名を使用しようとしても、署名の組み合わせに含まれるnonce値がEOAの現在のnonce値よりも小さいため、失敗します。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 13

5. サービス拒否(DoS)攻撃

サービス拒否(DoS)攻撃は、従来のWeb2の世界では目新しいものではありません。これは、大量のジャンクや破壊的な情報を送信するなど、サーバーへの干渉を指し、可用性を妨げたり完全に破壊したりします。同様に、スマートコントラクトもこのような攻撃に悩まされており、本質的にはスマートコントラクトを機能不全にすることを目的としています。

具体的なロジック

例を見てみましょう。プロジェクトAがプロトコルトークンの公募を行っており、すべてのユーザーが流動性プール(スマートコントラクト)に資金を拠出して先着順で割り当てを購入でき、余剰資金は参加者に返還されるとします。ハッカーのAliceは攻撃コントラクトを利用して公募に参加します。流動性プールがAliceの攻撃コントラクトに資金を返還しようとすると、DoS攻撃が発生し、返還処理が実行されなくなります。その結果、大量の資金がスマートコントラクト内にロックされてしまいます。

公募契約の実例

公募契約には2つの機能が含まれています:

  • deposit(): 預金機能で、預金者のアドレスと拠出額を記録します。
  • refund(): 返金機能で、プロジェクトチームが投資家に資金を返還するために使用します。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 14

DoS攻撃契約

DoS攻撃契約には1つの機能が含まれています:

  • attack(): 攻撃機能ですが、それ自体に問題はありません。主な問題は、Hacker契約に組み込まれたreceive()支払いコールバック関数にあります。この関数には例外の判定が含まれており、Hacker契約に資金を送金する外部契約は全て、revert()を通じて例外を発生させ、操作の完了を妨げます。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 15

解決策

1.外部契約を呼び出す際に重要な機能が停止しないようにする

上記のPublicSale契約のrefund()関数からrequire(success, "Refund Fail!");を削除し、単一のアドレスへの返金が失敗しても返金操作が続行できるようにします。

2. デカップリング

上記のPublicSale契約のrefund()関数において、返金を配布するのではなく、ユーザーが自ら返金を請求できるようにし、外部契約との不要な相互作用を最小限に抑えます。

6. permit攻撃

許可攻撃では、アカウントAが事前に指定された当事者に対して署名を提供し、その後アカウントBがその署名を取得して、認可されたトークン転送を実行し、一定量のトークンを盗むことができます。ここでは、スマートコントラクトにおけるトークン認可の2つの一般的な関数、approve()とpermit()について主に議論します。

一般的なERC20契約では、アカウントAはapprove()を呼び出してアカウントBに一定量のトークンを認可し、後者が前者からそれらのトークンを転送できるようにします。さらに、permit()はEIP-2612でERC20契約に導入され、Uniswapは2022年11月に新しいトークン認可標準であるPermit2をリリースしました。

具体的なロジック

ここで例を挙げます。ある日、ビルがブロックチェーンニュースのウェブサイトを閲覧していたとき、突然Metamaskの署名ポップアップが表示されました。多くのブロックチェーンウェブサイトやアプリケーションがユーザーログインの確認に署名を使用しているため、ビルはそれほど気にせずに直接署名を完了しました。5分後、彼のMetamaskの資産が流出しました。ビルはその後、ブロックチェーンエクスプローラーで、未知のアドレスがpermit()トランザクションを開始し、それに続いてtransferFrom()トランザクションが彼のウォレットを空にしたことを発見しました。

2つの関数は以下の通りです:

  • approve():標準的な認可関数で、アカウントAがアカウントBに一定量の資金を認可します。
  • permit():署名認可関数で、アカウントBが署名検証を提出および完了してアカウントAから認可された金額を取得します。パラメータには、認可を与える所有者、認可を受ける使用者、認可された金額、署名の期限、および所有者の署名データv、r、sが含まれます。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 16
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 17

解決策

1. オンチェーン取引における全ての署名に注意を払う

一部のウォレットがapprove()認証署名情報のデコードと表示に対策を講じているにもかかわらず、permit()署名フィッシングに対してはほとんど警告を行っていないため、攻撃のリスクが高まっています。したがって、未知の署名が全てpermit()関数を対象としているかどうかを厳密に検査することを強くお勧めします。

2. 日常的な取引用ウォレットと資産保管用ウォレットを分離する

これは暗号資産ユーザー、特にエアドロップハンターにとって非常に重要です。彼らは毎日数え切れないほどのdAppやウェブサイトと取引を行い、罠に陥りやすいからです。日常的な取引用ウォレットには少額の資金のみを保管することで、損失を管理可能な範囲に抑えることができます。

7. ハニーポット攻撃

ブロックチェーン業界において、ハニーポット攻撃とは、プロジェクトチームによってデプロイされた悪意のあるトークン契約の一種を指します。この契約はプロジェクトチームにのみ売却の権限を与え、一般ユーザーは購入のみが可能で売却ができないため、損失を被ることになります。

具体的なロジック

以下に例を示します。Telegramの発表で、プロジェクトAはトークンがメインネットにデプロイされ、取引可能になったことをユーザーに通知します。トークンは購入のみが可能で売却ができないため、当初は価格が急上昇し、取り残されることを恐れるユーザーが買い続けます。しばらくしてユーザーが売却できないことに気付いた頃、プロジェクトチームはこの機会を捉えてトークンをダンプし、価格を暴落させます。

主要機能:

  • _beforeTokenTransfer(): トークン転送時に呼び出される内部関数で、所有者によって呼び出された場合のみ成功します。他のアカウントからの呼び出しは失敗します。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 18

解決策

セキュリティスキャンツールを使用する

a. Ethereumトークン用のToken Sniffer

b. 他のチェーン上のトークン用のAve Check

c. Dextoolsのような組み込みの検出ツールを持つマーケットウェブサイト

スコアの低いトークンの取引を避けてください。

8. フロントランニング攻撃

フロントランニングは元々、伝統的な金融市場で発生しました。情報の非対称性により、金融仲介業者が特定の業界情報に基づいて迅速な行動を取り、利益を得ることができました。ブロックチェーン業界では、フロントランニングは主にオンチェーンのフロントランニングから生じており、これはマイナーを操作して自分の取引を優先的にチェーンにパッキングさせ、利益を得ることを含みます。

ブロックチェーン分野では、マイナーはブロックにパッキングする取引を操作することで利益を得ることができます。例えば、特定の取引を除外したり、取引の順序を変更したりします。このような利益はMiner Extractable Value(MEV)で測定できます。ユーザーの取引がイーサリアムメインネットに追加される前に、大多数の取引はメモリプールに集約されます。マイナーはこのメモリプールでガス価格の高い取引を探し、それらを優先的にパッキングして利益を最大化します。一般的に、ガス価格の高い取引はマイナーによってより簡単にパッキングされます。同時に、一部のMEVボットもメモリプールで収益性のある取引を探しています。

具体的なロジック

以下は一例です。ビルは価格変動の大きい新しい人気のトークンを発見します。Uniswapでのトークン取引の成功を確実にするために、ビルは例外的に広いスリッページ範囲を設定します。残念ながら、アリスのMEVボットがメモリプールでこの取引を検出し、即座にガス料金を引き上げ、ビルの前に買い取引を開始し、同じブロック内でビルの取引の後に売り取引を挿入します。ブロックの確認後、これによりビルは大きなスリッページ損失を被り、一方でアリスは安く買って高く売る裁定取引から利益を得ます。

関数は以下の通りです:

  • solve(): 誰でも回答を提出できる推測関数で、提出された回答が目標の回答と一致した場合、提出者は10イーサを受け取ることができます。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 19
  1. プロセス:ビルが正解を見つけます。
  2. アリスはメモリープールを監視し、誰かが正解を提出するのを待ちます。
  3. ビルはsolve()を呼び出して回答を提出し、ガス価格を100 Gweiに設定します。
  4. アリスはビルが送信したトランザクションを見て回答を発見します。彼女はビルよりも高いガス価格200 Gweiを設定し、solve()を呼び出します。
  5. アリスのトランザクションがビルのトランザクションよりも先にマイナーによってパックされます。
  6. アリスが10イーサの報酬を獲得します。

解決策

3つの主要な関数は以下の通りです:

  • commitSolution(): 結果を提出する関数で、ユーザーの提出した回答solutionHash、提出時間commitTime、および状態revealedをCommit構造体に配置します。
  • getMySolution(): 結果を取得する関数で、ユーザーが自分の提出した回答と関連情報(ユーザーの提出した回答solutionHash、提出時間commitTime、および状態revealed)を閲覧できるようにします。
  • revealSolution(): パズルの推測に対する報酬を請求する関数で、ユーザーが回答と設定したパスワードを提供した後に報酬を請求できるようにします。
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 20
スマートコントラクトにおける一般的な脆弱性と攻撃の概要 - image 21

プロセス:

  1. ビルが正解を見つけます。
  2. ビルはcommitSolution()を呼び出して正解を提出します。
  3. 次のブロックで、ビルはrevealSolution()を呼び出し、答えと設定したパスワードを提供して報酬を請求します。

commitSolution()では、ビルは暗号化された文字列を提出し、平文データは自身のみが保持します。この段階で、提出ブロック時間commitTimeも記録されます。次に、revealSolution()では、同一ブロック内でのフロントランニングを防ぐためにブロック時間がチェックされます。revealSolution()の呼び出しには平文の答えの提出が必要なため、この手順は他者がcommitSolution()をバイパスしてrevealSolution()を直接呼び出すことを防ぐことを目的としています。検証が成功し、答えが正しいと確認されると、報酬が配布されます。

結論

スマートコントラクトはブロックチェーン技術において重要な役割を果たし、多くの利点を提供します。まず、分散化された自動実行を可能にし、第三者を介さずに取引の安全性と信頼性を確保します。次に、スマートコントラクトは仲介のステップとコストを削減し、取引の効率性を向上させます。

このような多くの利点があるにもかかわらず、スマートコントラクトはユーザーに財務的損失をもたらす攻撃のリスクにも直面しています。そのため、オンチェーンユーザーにとっては、いくつかの習慣が不可欠です。まず、ユーザーは常に慎重にdAppを選択し、コントラクトコードと関連ルールを徹底的にレビューする必要があります。さらに、ハッカー攻撃のリスクを軽減するために、セキュアなウォレットとコントラクト対話ツールを定期的に更新し使用すべきです。また、資金を複数のアドレスに分散して保管し、コントラクト攻撃による潜在的な損失を最小限に抑えることをお勧めします。

業界関係者にとって、スマートコントラクトの安全性と安定性を確保することも同様に重要です。最優先事項は、スマートコントラクトの監査を強化し、潜在的な脆弱性やセキュリティリスクを特定して修正することです。次に、業界関係者は、コントラクト攻撃に関連するブロックチェーンの最新の動向を把握し、それに応じたセキュリティ対策を講じるべきです。最後に、スマートコントラクトの正しい使用方法に関するユーザー教育とセキュリティ意識の向上も重要です。

結論として、ユーザーと業界関係者の協調的な努力により、スマートコントラクトによるセキュリティリスクは大幅に軽減できます。ユーザーは常に慎重にコントラクトを選択し、個人資産を保護する必要があります。一方、業界関係者はコントラクトの監査を強化し、技術の進歩に遅れをとらず、ユーザー教育とセキュリティ意識の向上に努めるべきです。これらの取り組みにより、スマートコントラクトの安全で信頼性の高い発展を共に推進していくことができます。



参考文献:


Solidity by Example:https://solidity-by-example.org/

Blockchain Know-how of SlowMist:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4ODQ3NTM2OA==&action=getalbum&album_id=1378673890158936067&scene=173&from_msgid=2247498135&from_itemidx=1&count=3&nolastread=1#wechat_redirect

Chainlink - Top 10 DeFi Security Best Practices:https://blog.chain.link/defi-security-best-practices/#post-title

WTF - Solidity 104 Contract Security:https://www.wtf.academy/solidity-104/

Vulnerabilities in DeFi Smart Contracts in 4 Categories with 38 Scenarios:https://www.weiyangx.com/381670.html

OpenZeppelin:https://github.com/OpenZeppelin/