読み取り時間: 6 分
最大の仮想通貨ハッキングと、それによって失われた目を見張るような数字を詳しく見てみると、それらはコーディングの欠陥に深く根ざしていたはずです。
このようなセキュリティの脆弱性の一般的な発生の XNUMX つは、リエントラント攻撃です。 ただし、リエントラントの処理を誤ったために引き起こされる破壊的な影響は、攻撃自体を開始するほど単純ではないかもしれません。
よく知られ、よく知られている問題であるにもかかわらず、スマート コントラクトでの再入可能性のバグの出現は常に避けられません。
過去数年間に、再入可能性の脆弱性がハッカーによってどのくらいの頻度で悪用されましたか? それはどのように機能しますか? スマート コントラクトがリエントラント バグで資金を失うのを抑える方法は? このブログでこれらの質問に対する答えを見つけてください。
ですから、そのうちに、メモリ内で最大の再入可能攻撃についてブラッシュ アップしましょう。
最も悪名高いリアルタイム再入可能ハックの一部
プロジェクトに最も壊滅的な影響を与えた再入可能攻撃は、最終的にこれら XNUMX つのうちの XNUMX つ、または両方を実行することになりました。
- スマート コントラクトから Ether を完全に排出する
- スマート コントラクト コードに侵入するハッカー
現在、リエントラント攻撃のいくつかのケースとその影響を観察できます。
2016月XNUMX日: DAO 攻撃 – 3.54 万ドルまたは 150 億 XNUMX 万ドルのイーサ
2020月XNUMX日: Uniswap/Lendf.Me のハッキング – 25 万ドル
2021可能性があります。 BurgerSwap のハッキング – 7.2 万ドル
2021月XNUMX日: CREAM FINANCE のハッキング – 18.8 万ドル
Mar 2022: オラ・ファイナンス – 3.6万ドル
2022年XNUMX月XNUMX日: OMNI プロトコル – 1.43 万ドル
リエントラント攻撃が時代遅れになったことがないことは明らかです。 以下の節で、それについての深い洞察を得ましょう。
リエントラント攻撃の概要
「リエントラント」という名前の由来のように、「何度も再突入する」という意味があります。 リエントラント攻撃には、被害者コントラクトと攻撃者コントラクトの XNUMX つのコントラクトが含まれます。
攻撃者コントラクトは、被害者コントラクトの再入可能性の脆弱性を悪用します。 それを実現するために、withdraw 関数を使用します。
攻撃者のコントラクトは、被害者のコントラクトの残高が更新される前に、繰り返し呼び出しを行うことで、被害者のコントラクトから資金を引き出すための引き出し関数を呼び出します。 被害者コントラクトは残高を確認し、資金を送信して残高を更新します。
しかし、資金を送金してコントラクトの残高を更新する時間枠内で、攻撃者コントラクトは資金を引き出すための継続的な呼び出しを行います。 その結果、攻撃者コントラクトがすべての資金を使い果たすまで、被害者コントラクトの残高は更新されません。
再入可能性の悪用の深刻さとコストは、実行する緊急の必要性を警告します スマートコントラクト監査 そのようなエラーを見落とす可能性を排除します。
リエントラント攻撃の説明図
以下の簡略化された図から、リエントラント攻撃の概念を理解してみましょう。
ここに XNUMX つのコントラクトがあります: 脆弱なコントラクトとハッカーのコントラクト
ハッカー コントラクトは、脆弱なコントラクトから撤退するように呼び出します。 電話を受けると、脆弱な契約はハッカー契約の資金をチェックし、資金をハッカーに転送します。
ハッカーは資金を受け取り、フォールバック機能を実装します。これは、脆弱なコントラクトで残高が更新される前であっても、脆弱なコントラクトを再度呼び出します。 このように同じ操作を繰り返すことで、ハッカーは脆弱なコントラクトから資金を完全に引き出します。
攻撃者が利用するフォールバック機能の特徴
- それらは外部から呼び出すことができます。 つまり、それらが書かれたコントラクト内から呼び出すことはできません
- 名前のない関数
- フォールバック関数には任意のロジックが含まれていません
- ETH がそれに含まれるスマート コントラクトに送信され、 receive() 関数が宣言されていない場合、フォールバックがトリガーされます。
技術的な観点からの再入可能攻撃の分析
サンプル コントラクトを取り上げて、リエントラント攻撃がどのように発生するかを理解しましょう。
悪意のある契約
contract Attack {
DepositFunds public depositFunds;
constructor(address _depositFundsAddress) {
depositFunds = DepositFunds(_depositFundsAddress);
}
// Fallback is called when DepositFunds sends Ether to this contract.
fallback() external payable {
if (address(depositFunds).balance >= 1 ether) {
depositFunds.withdraw();
}
}
function attack() external payable {
require(msg.value >= 1 ether);
depositFunds.deposit{value: 1 ether}();
depositFunds.withdraw();
}
}
これは、攻撃者が 2ETH を入金する攻撃者コントラクトです。 攻撃者は、脆弱なコントラクトでwithdraw関数を呼び出します。 脆弱なコントラクトから資金を受け取ると、フォールバック機能がトリガーされます。
次に、フォールバックは引き出し関数を実行し、脆弱なコントラクトから資金を排出します。 このサイクルは、脆弱な契約から資金が完全に使い果たされるまで続きます。
脆弱な契約
contract DepositFunds {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint bal = balances[msg.sender];
require(bal > 0);
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to send Ether");
balances[msg.sender] = 0;
}
}
脆弱なコントラクトは 30ETH です。 ここで、withdraw() 関数は要求された金額を攻撃者に送信します。 残高が更新されないため、トークンは繰り返し攻撃者に転送されます。
再入可能攻撃の種類
- 単一関数の再入可能
function withdraw() external {
uint256 amount = balances[msg.sender];
require(msg.sender.call.value(amount)());
balances[msg.sender] = 0;
}
msg.sender.call.value(amount)() が資金を転送した後、攻撃者のコントラクト フォールバック関数は、balances[msg.sender] = 0 が更新される前に、withdraw() を再度呼び出します。
- 機能間の再入可能性
function transfer(address to, uint amount) external {
if (balances[msg.sender] >= amount) {
balances[to] += amount;
balances[msg.sender] -= amount;
}
}
function withdraw() external {
uint256 amount = balances[msg.sender];
require(msg.sender.call.value(amount)());
balances[msg.sender] = 0;
}
機能間の再入可能性は、識別するのがはるかに複雑です。 ここでの違いは、フォールバック関数が transfer を呼び出すことです。これは、単一関数の再入可能性とは異なり、withdraw を呼び出します。
リエントラント攻撃に対する防御
チェック-効果-相互作用パターン: Checks-effects-interactions パターンは、関数の構造化に役立ちます。
プログラムは、最初に条件をチェックするようにコーディングする必要があります。 チェックに合格したら、コントラクトの状態への影響を解決する必要があります。その後、外部関数を呼び出すことができます。
function withdraw() external {
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0;
require(msg.sender.call.value(amount)());
}
ここで書き直されたコードは、checks-effects-interactions パターンに従います。 ここでは、外部呼び出しを行う前に残高をゼロにします。
修飾子の使用
関数に適用される修飾子 noReentrant は、再入可能な呼び出しがないことを保証します。
contract ReEntrancyGuard {
bool internal locked;
modifier noReentrant() {
require(!locked, "No re-entrancy");
locked = true;
_;
locked = false;
}
}
最後に
最も効果的なステップは、QuillAudits のような主要なセキュリティ会社からスマート コントラクトの監査を受けることです。監査者は、コードの構造を注意深く監視し、フォールバック機能がどのように実行されるかをチェックします。 調査したパターンに基づいて、コードの再構築が推奨されます。 脆弱な行動.
資金の安全性は、損失の犠牲になる直前に確保されます。
よくあるご質問
リエントラント攻撃とは?
脆弱なコントラクトの関数が信頼されていないコントラクトを呼び出すと、再入攻撃が発生します。 信頼されていないコントラクトは、資金が完全に使い果たされるまで脆弱なコントラクトを再帰的に呼び出す攻撃者のコントラクトになります。
リエントラントとは何ですか?
再入力とは、コードの実行を中断し、プロセスを最初からやり直すことを意味し、再入力とも呼ばれます。
リエントラントガードとは?
再入可能ガードは、関数が繰り返し呼び出されるのを防ぐ修飾子を使用します。 上記のブログを読んで、再入可能ガードの例を見つけてください。
スマート コントラクトに対する攻撃にはどのようなものがありますか?
スマート コントラクトは、再入可能性、タイムスタンプ依存性、算術オーバーフロー、DoS 攻撃など、多数の脆弱性にさらされています。 したがって、契約のロジックを崩壊させるバグがないことを確認するための監査は必須です。
69 ビュー