以太坊上的應用程式管理財務價值,使安全性變得絕對重要。作為一種新興的、實驗性的技術,智慧合約當然也受到了相當多的攻擊。為了防止進一步的攻擊,我列出了幾乎所有已知的攻擊和漏洞的列表。儘管此列表可能包含已知的攻擊,但新的漏洞仍在定期發現,因此,這應該只是您作為工程師研究智慧合約安全性的開始。攻擊在本節中,我們將介紹可用於利用智慧合約漏洞的已知攻擊。前端執行又稱為事務排序依賴性康科迪亞大學(University of Concordia)認為,“先行是一種行動,在此過程中,使用者可以從預先訪問有關即將發生的交易和交易的特權市場資訊中受益”,對市場中未來事件的瞭解會導致剝削。例如如果知道某個特定代幣將要進行非常大的購買,那麼壞的參與者可以提前購買該代幣,並在超大的購買訂單提高價格時出售該代幣以獲取利潤。前端攻擊在金融市場長期以來一直是一個問題,由於區塊鏈的透明性,這個問題在加密貨幣市場再次出現。由於此問題的解決方案因合約而異,因此很難避免。可能的解決方案包括批次交易和使用預提交方案(即允許使用者稍後提交詳細資訊)。限制區塊氣體的DoS在以太坊區塊鏈中,所有區塊都有氣體限制。氣體限制的好處之一是,它可以防止攻擊者建立無限的事務迴圈,但是如果事務的氣體使用量超過此限制,則事務將失敗。 這可能以幾種不同的方式導致DoS攻擊。無限操作在這種情況下,區塊氣限額可能是一個問題,即向一系列地址傳送資金。即使沒有任何惡意,這也很容易出錯。僅僅是因為有太多的使用者需要付費,就可以最大限度地超出氣體的限額,並阻止交易的成功。這種情況也可能導致攻擊。假設一個壞的參與者決定建立大量的地址,每個地址從智慧合約中支付少量資金。如果有效地執行,則可以無限期地阻止事務,甚至可能阻止進一步的事務處理。解決此問題的有效方法是在當前的推付式支付系統上使用預付式支付系統。為此,請將每筆付款分成自己的交易,然後讓收款人呼叫該功能。如果出於某種原因,您真的需要遍歷一個未指定長度的陣列,至少希望它可能佔用多個區塊,並允許它在多個事務中執行,如本例所示:struct Payee { address addr; uint256 value;}Payee[] payees;uint256 nextPayeeIndex;function payOut() { uint256 i = nextPayeeIndex; while (i < payees.length && msg.gas > 200000) { payees[i].addr.send(payees[i].value); i++; } nextPayeeIndex = i;}區塊填充在某些情況下,即使您未遍歷未指定長度的陣列,您的智慧合約也可能受到氣體限制的攻擊。攻擊者可以透過使用足夠高的氣體價格來填充交易之前的幾個區塊。這種攻擊是透過以很高的氣體價格發行幾筆交易來完成的。如果氣體價格足夠高,並且交易消耗了足夠的氣體,它們就可以填滿整個區塊並阻止其他交易被處理。以太坊交易要求傳送者支付費以抑制垃圾交易攻擊,但是在某些情況下,可以有足夠的動機來進行此類攻擊。例如在Dapp Fomo3D上使用了區塊填充攻擊。該應用程式具有倒數計時器,透過最後一次購買金鑰,使用者可以贏得大獎-除非使用者每次購買鑰金鑰,計時器都會延長。攻擊者購買了一把金鑰,然後連續塞滿了接下來的13個區塊,這樣他們才能贏得大獎。為了防止此類攻擊的發生,必須仔細考慮在應用程式中合併基於時間的操作是否安全。撤回DosDoS(拒絕服務)攻擊可能發生在函式中,當您嘗試向使用者傳送資金時,該函式依賴於該資金轉移是否成功。如果資金被髮送到一個由壞的參與者建立的智慧合約中,這可能會有問題,因為他們可以簡單地建立一個回退函式來還原所有付款。例如:// INSECUREcontract Auction { address currentLeader; uint highestBid; function bid() payable { require(msg.value > highestBid); require(currentLeader.send(highestBid)); // Refund the old leader, if it fails then revert currentLeader = msg.sender; highestBid = msg.value; }}如本例所示,如果攻擊者透過具有回退函式的智慧合約出價來還原所有付款,則它們將永遠無法退款,因此,沒有人可以提出更高的出價。在沒有攻擊者在場的情況下,這也可能會帶來問題。 例如您可能希望透過遍歷陣列來向使用者支付費用,當然,您要確保為每個使用者都支付了適當的費用。 這裡的問題是,如果一次付款失敗,該功能將被還原並且而沒有人得到付款。address[] private refundAddresses;mapping (address => uint) public refunds;// badfunction refundAll() public { for(uint x; x < refundAddresses.length; x++) { // arbitrary length iteration based on how many addresses participated require(refundAddresses[x].send(refunds[refundAddresses[x]])) // doubly bad, now a single failure on send will hold up all funds }}解決此問題的有效方法是在當前的推付式支付系統上使用預付式支付系統。 為此,請將每筆付款分成自己的交易,然後讓收款人呼叫該功能。contract auction { address highestBidder; uint highestBid; mapping(address => uint) refunds; function bid() payable external { require(msg.value >= highestBid); if (highestBidder != address(0)) { refunds[highestBidder] += highestBid; // record the refund that this user can claim } highestBidder = msg.sender; highestBid = msg.value; } function withdrawRefund() external { uint refund = refunds[msg.sender]; refunds[msg.sender] = 0; (bool success, ) = msg.sender.call.value(refund)(""); require(success); }}強制將以太坊傳送給智慧合約有時候,使用者不需要將以太坊傳送到智慧合約。不幸的是,在這種情況下,可以繞過智慧合約回退函式並強行傳送以太坊。contract Vulnerable { function () payable { revert(); } function somethingBad() { require(this.balance > 0); // Do something bad }}儘管似乎應該撤消與Vulnerable智慧合約的任何交易,但實際上有兩種方法可以強制傳送Ether。第一種方法是在以“易受攻擊的合同”地址設定為受益人的合同上呼叫“selfdestruct”方法。 這是可行的,因為selfdestruct不會觸發回退函式。另一種方法是預先計算智慧合約的地址,並在部署智慧合約之前將以太坊傳送到該地址。令人驚訝的是,這是可能實現的。Griefing是一種經常在影片遊戲中執行的攻擊,惡意使用者以一種意外的方式玩遊戲,以打擾其他玩家,也就是trolling。此類攻擊還用於阻止事務按預期執行。可以對接受資料並在另一個智慧合約的子呼叫中使用它的智慧合約進行此攻擊。此方法通常用於多簽名錢包以及交易中繼器中。如果子呼叫失敗,則將還原整個事務或繼續執行。讓我們以一個簡單的中繼智慧合約為例。 如下所示,中繼智慧合約允許某人進行交易並簽署交易,而不必執行交易。當使用者無法支付與交易相關的氣體時,通常會使用此函式。contract Relayer { mapping (bytes => bool) executed; function relay(bytes _data) public { // replay protection; do not call the same transaction twice require(executed[_data] == 0, "Duplicate call"); executed[_data] = true; innerContract.call(bytes4(keccak256("execute(bytes)")), _data); }}執行事務的使用者(轉發器)可以透過僅使用足以執行事務的氣體而不是足夠使子呼叫成功的氣體來有效地審查事務。有兩種方法可以防止這種情況發生。第一種解決方案是僅允許受信任的使用者中繼事務。另一種解決方案是要求轉運商提供足夠的氣體,如下所示。// contract called by Relayercontract Executor { function execute(bytes _data, uint _gasLimit) { require(gasleft() >= _gasLimit); ... }}#### 可重入攻擊可重入性是一種攻擊,當契約函式中的錯誤允許函式在本應禁止的情況下多次執行時,可能會發生這種攻擊。如果惡意使用,這可以用來從智慧合約中抽走資金。實際上,可重入性是DAO攻擊中使用的攻擊向量。單函式可重入(Single-function reentrancy)當易受攻擊的函式與攻擊者試圖遞迴呼叫的函式相同時,就會發生單函式重入攻擊。// INSECUREfunction withdraw() external { uint256 amount = balances[msg.sender]; require(msg.sender.call.value(amount)()); balances[msg.sender] = 0;}在這裡,我們可以看到餘額只有在資金轉移後才被修改。這可以讓駭客在餘額設定為0之前多次呼叫該函式,有效地耗盡智慧合約。跨函式重入攻擊跨函式重入攻擊是同一過程的更復雜版本。當易受攻擊的功能與攻擊者可以利用的功能共享狀態時,就會發生跨函式重入攻擊。// INSECUREfunction 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;}在此示例中,駭客可以透過在fallout()函式中將餘額設定為0之前具有回退函式呼叫transfer()來轉移已用資金來利用此智慧合約。防止可重入攻擊在智慧合約中轉移資金時,請使用傳送或轉移而不是呼叫。使用呼叫的問題與其他函式不同,它沒有2300的限制。這意味著可以在外部函式呼叫中使用該呼叫,該函式可用於執行重入攻擊。另一種可靠的預防方法是標記不受信任的功能。function untrustedWithdraw() public { uint256 amount = balances[msg.sender]; require(msg.sender.call.value(amount)()); balances[msg.sender] = 0;}此外,為了獲得最佳安全性,請使用“checks-effects-interactions”模式。這是智慧合約函式的簡單經驗法則。該函式應從checks開始-例如require和assert語句。接下來,應執行智慧合約的效力-例如狀態修改。最後,我們可以與其他智慧合約進行互動-例如外部函式呼叫。這種結構可有效防止重入,因為智慧合約的修改狀態將防止不良行為者執行惡意互動。function withdraw() external { uint256 amount = balances[msg.sender]; balances[msg.sender] = 0; require(msg.sender.call.value(amount)());}由於在執行任何互動操作之前將餘額設定為0,因此如果遞迴呼叫智慧合約,則在第一個事務之後將沒有任何要傳送的內容。脆弱性在本節中,我們將介紹已知的智慧合約漏洞以及如何避免這些漏洞。此處列出的幾乎所有漏洞都可以在智慧合約弱點分類中找到。整數上溢和下溢實際上,整數型別具有最大值。 例如:uint8 => 255uint16 => 65535uint24 => 16777215uint256 =>(2 ^ 256)-1當您超過最大值(溢位)或低於最小值(下溢)時,可能會發生溢位和下溢錯誤。當您超過最大值時,您將返回到零,而當您低於最小值時,它將使您返回到最大值。由於較小的整數型別(如uint8,uint16等)具有較小的最大值,因此更容易引起溢位; 因此應謹慎使用它們。可能的最佳解決溢位和下溢錯誤的方法是在執行數學運算時使用OpenZeppelin SafeMath庫。時間戳依賴性現在或block.timestamp訪問的塊的時間戳可由礦工操作。 使用時間戳執行智慧合約函式時,應考慮三個因素。時間戳操縱如果使用時間戳來嘗試產生隨機性,則礦工可以在區塊驗證後的15秒鐘內釋出時間戳,從而使他們能夠將時間戳設定為一個值,從而增加使用該功能的機率。例如彩票應用可以使用區塊時間戳來選擇組中的隨機投標人。礦工可以進入彩票,然後將時間戳修改為一個值,使他們有更大的機率贏得彩票。因此,不應將時間戳用於建立隨機性。5秒規則以太坊的參考規範“ Yellow Paper”(黃皮書)沒有規定可以改變多少區塊的時間限制,它必須大於其父級的時間戳。話雖這麼說,流行的協議實現會拒絕將來時間戳大於15秒的區塊,因此只要您的時間相關事件可以安全地相差15秒,就可以安全地使用區塊時間戳。不要使用block.number作為時間戳您可以使用block.number和平均區塊時間來估計事件之間的時間差。 但是阻止時間可能會更改並破壞函式,因此最好避免這種用法。透過tx.origin授權tx.origin是Solidity中的全域性變數,它返回傳送事務的地址。重要的是您切勿使用tx.origin進行授權,因為另一個智慧合約可以使用回退函式來呼叫您的智慧合約並獲得授權,因為授權地址儲存在tx.origin中。 考慮以下示例:pragma solidity >=0.5.0 <0.7.0;// THIS CONTRACT CONTAINS A BUG - DO NOT USEcontract TxUserWallet { address owner; constructor() public { owner = msg.sender; } function transferTo(address payable dest, uint amount) public { require(tx.origin == owner); dest.transfer(amount); }}在這裡我們可以看到TxUserWallet智慧合約使用tx.origin授權transferTo()函式。pragma solidity >=0.5.0 <0.7.0;interface TxUserWallet { function transferTo(address payable dest, uint amount) external;}contract TxAttackWallet { address payable owner; constructor() public { owner = msg.sender; } function() external { TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance); }}現在如果有人誘騙您將以太坊傳送至TxAttackWallet智慧合約地址,他們可以透過檢查tx.origin來查詢傳送交易的地址來竊取您的資金。為了防止這種攻擊,請使用msg.sender進行授權。浮動編譯器最好選擇一個編譯器版本並堅持使用它。使用浮動編譯器時,可能會使用過時或有問題的編譯器版本意外地部署智慧合約,這可能會導致錯誤,從而使智慧合約的安全性受到威脅。對於開源專案,該實用程式還會告訴開發人員在部署您的智慧合約時要使用哪個版本。所選的編譯器版本應經過全面測試,並考慮是否存在已知錯誤。對於庫和包,可以使用浮動編譯指示例外。 否則開發人員將需要手動更新編譯指示以在本地編譯。函式預設可見性可以將功能可見性指定為public,private,internal或external。重要的是要考慮哪種可視性最適合您的智慧合約函式。許多智慧合約攻擊是由開發人員忘記或放棄使用可見性修飾符引起的。 然後預設情況下將該函式設定為public,這可能導致意外狀態更改。過時的編譯器版本開發人員通常會在現有軟體中發現錯誤和漏洞並進行修補。 因此儘可能使用最新的編譯器版本很重要。未檢查的呼叫返回值如果未檢查低階呼叫的返回值,則即使函式呼叫丟擲錯誤,也可能繼續執行。這可能導致意外行為並破壞程式邏輯。失敗的呼叫甚至可能是由攻擊者引起的,攻擊者可以進一步利用應用程式進行攻擊。在Solidity中,您可以使用低階呼叫,例如address.call(),address.callcode(),address.delegatecall()和address.send(),也可以使用智慧合約呼叫,例如ExternalContract.doSomething( )。低階呼叫永遠不會引發異常-相反,如果遇到異常,它們將返回false,而智慧合約呼叫將自動引發。如果您使用低階呼叫,請確保檢查返回值以處理可能的失敗呼叫。無保護的以太坊提款如果沒有足夠的訪問控制,不良行為者可能能夠從智慧合約中撤出部分或全部以太坊。這可能是由於錯誤地命名了要用作建構函式的函式,從而使任何人都可以重新初始化智慧合約。為了避免此漏洞,請僅允許授權或按預期的方式觸發提款,並適當命名您的建構函式。無保護的自毀指令在具有自毀方法的智慧合約中,如果缺少訪問控制或訪問控制不足,惡意行為者可以自毀智慧合約。重要的是要考慮自毀功能是否絕對必要。如有必要,請考慮使用多重簽名授權來防止攻擊。在Parity攻擊中使用了此攻擊。一位匿名使用者定位並利用了“庫”智慧合約中的漏洞,從而使自己成為智慧合約的所有者。 然後攻擊者開始自毀智慧合約。這導致資金被凍結在587個唯一的錢包中,總共持有513,774.16個以太坊。狀態變數預設可見性開發人員通常會明確宣告函式可見性,而宣告變數可見性並不常見。狀態變數可以具有三個可見性識別符號之一:public,private或internal。幸運的是,變數的預設可見性是內部的,而不是public的,但是即使您打算將變數宣告為internal的,也必須明確,因此對於誰可以訪問該變數沒有錯誤的假設。未初始化的儲存指標資料作為儲存,記憶體或呼叫資料儲存在EVM中。 理解並正確初始化這兩者很重要。 錯誤地初始化資料儲存指標,或者只是不進行初始化就可能導致智慧合約漏洞。斷言assert從Solidity 0.5.0開始,未初始化的儲存指標不再是問題,因為與未初始化的儲存指標的協定將不再編譯。 話雖如此,瞭解在某些情況下應該使用哪些儲存指標仍然很重要。在Solidity 0.4.10中,建立了以下函式:assert(),require()和revert()。 在這裡,我們將討論assert函式以及如何使用它。正式地說,assert()函式用於宣告不變數;非正式地說,assert()是一個過分自信的保鏢,可以保護您的智慧合約,但會在過程中竊取您的氣體。正常執行的智慧合約永遠不應到達失敗的assert宣告。如果到達了失敗的assert語句,則說明您使用了assert()的方式不正確,或者智慧合約中存在將其置於無效狀態的錯誤。如果在assert()中檢查的條件實際上不是不變的,則建議您將其替換為require()語句。使用過時的函式隨著時間的流逝,Solidity中的函式已被棄用,並經常被更好的函式所取代。不要使用過時的函式,這很重要,因為它可能導致意外的效果和編譯錯誤。下面是一個不推薦使用的函式和替代項的列表。許多替代品都是簡單的別名,如果用作不推薦使用的替代品,則不會破壞當前行為。
委託給不受信任的呼叫者
Delegatecall是訊息呼叫的一種特殊變體。它幾乎與常規訊息呼叫相同,只是目標地址在呼叫協定的上下文中執行,msg.sender和msg.value保持不變。實際上,delegatecall委託其他智慧合約修改呼叫智慧合約的儲存。
由於delegatecall提供了對智慧合約的如此多的控制權,因此只將其用於可信的智慧合約(比如您自己的智慧合約)是非常重要的。如果目標地址來自使用者輸入,請確保它是受信任的協定。
簽名延展性
人們通常會假設在智慧合約中使用加密簽名系統會驗證簽名是否唯一; 但是事實並非如此。在以太坊中的簽名可以在沒有私鑰的情況下進行更改並保持有效。 例如橢圓金鑰密碼術由三個變數v,r和s組成,如果以正確的方式修改了這些值,則可以獲得帶有無效私鑰的有效簽名。
為避免簽名可延展性的問題,切勿在簽名訊息雜湊中使用簽名來檢查智慧合約是否已處理了先前簽名的訊息,因為惡意使用者可以找到並重新建立您的簽名。
建構函式名稱不正確
在Solidity 0.4.22之前,定義建構函式的唯一方法是使用智慧合約名稱建立函式。在某些情況下,這是有問題的。 例如如果智慧合約以不同的名稱重複使用,但建構函式也未更改,則它將變成常規的可呼叫函式。
現在,使用Solidity的現代版本,您可以使用Constructor關鍵字定義建構函式,從而有效棄用此漏洞。 因此,解決此問題的方法只是使用現代的Solidity編譯器版本。
隱藏狀態變數
在Solidity中可以兩次使用相同的變數,但可能會導致意外的副作用。 對於使用多個智慧合約,這尤其困難。 請看以下示例:
contract SuperContract {
uint a = 1;
}
contract SubContract is SuperContract {
uint a = 2;
}
在這裡,我們可以看到SubContract繼承了SuperContract,並且變數a被兩次定義為不同的值。 現在,假設我們使用a在SubContract中執行某些功能。 由於已修改a的值,因此從SuperContract繼承的功能將不再起作用。
為避免此漏洞,重要的是我們檢查整個智慧合約系統是否存在歧義。檢查編譯器警告也很重要,因為只要它們在智慧合約中,它們就可以標記這些歧義。
區塊鏈屬性的隨機性來源較弱
在以太坊中,某些應用程式依賴於隨機數的生成來保持公平。但是在以太坊中,隨機數的生成非常困難,並且有一些陷阱值得考慮。
使用諸如block.timestamp,blockhash和block.difficulty之類的鏈屬性似乎是個好主意,因為它們通常會產生偽隨機值。然而,問題在於礦工修改這些值的能力。 例如在具有數百萬美元大獎的賭博應用中,礦工有足夠的動力去生成許多替代區塊,只選擇會導致礦工中獎的區塊。當然,要像這樣控制區塊鏈會付出巨大的代價,但是如果賭注足夠高,那肯定可以做到。
為了避免在隨機數生成中操縱礦工,有一些解決方案:
1. 承諾方案,例如RANDAO,DAO,其中隨機數由DAO中的所有參與者生成。
2. 透過oracle的外部來源-例如Oraclize。
3. 使用比特幣區塊雜湊,因為網路更加分散,區塊的開採成本更高。
缺少針對簽名重放攻擊的保護
有時在智慧合約中,有必要執行簽名驗證以提高可用性和氣體的成本。但是在實施簽名驗證時需要考慮。
為了防止簽名重放攻擊,智慧合約應僅允許處理新的雜湊。這樣可以防止惡意使用者多次重播另一個使用者的簽名。
為了更加安全地進行簽名驗證,請遵循以下建議:
· 儲存智慧合約處理的每個訊息雜湊,然後在執行功能之前對照現有雜湊檢查訊息雜湊。
· 在雜湊中包括合同的地址,以確保訊息僅在單個合同中使用。
· 切勿生成包含簽名的訊息雜湊。
違反條例
equire()方法用於驗證條件,例如輸入或智慧合約狀態變數,或驗證來自外部智慧合約呼叫的返回值。 為了驗證外部呼叫,可以由呼叫者提供輸入,也可以由被呼叫返回輸入。如果被呼叫方的返回值發生輸入衝突,則可能是以下兩種情況之一出了問題:
· 提供輸入的合同中有一個bug。
· 要求條件太強。
要解決此問題,首先要考慮需求條件是否太強。如有必要,請減弱它以允許任何有效的外部輸入。如果問題不是必需條件,則智慧合約中必須有提供外部輸入的錯誤。確保此智慧合約未提供無效輸入。
寫入任意儲存位置
只有授權地址才能訪問敏感儲存位置。如果整個智慧合約中沒有適當的授權檢查,則惡意使用者可能會覆蓋敏感資料。但是即使存在用於寫入敏感資料的授權檢查,攻擊者仍可能能夠透過不敏感資料覆蓋敏感資料。 這可能使攻擊者可以覆蓋重要的變數,例如智慧合約所有者。
為了防止這種情況的發生,我們不僅要保護具有授權要求的敏感資料儲存,而且還要確保對一個資料結構的寫入不會無意間覆蓋另一資料結構的條目。
繼承順序不正確
在Solidity中,可以從多個來源繼承,如果不能正確理解,則可能會引起歧義。這種歧義被稱為鑽石問題:如果兩個基本智慧合約具有相同的函式,那麼哪個優先? 幸運的是,只要開發人員瞭解解決方案,Solidity就可以很好地處理此問題。
Solidity為鑽石問題提供的解決方案是使用反向C3線性化。這意味著它將使繼承從右到左線性化,因此繼承的順序很重要。建議從更一般的智慧合約開始,再到更具體的智慧合約結束,以避免出現問題。
具有函式型別變數的任意跳轉
Solidity支援函式型別。這意味著可以將型別為function的變數分配給具有匹配簽名的函式。然後可以像其他任何函式一樣從變數中呼叫該函式。使用者不應更改函式變數,但是在某些情況下,這是可能的。
如果智慧合約使用某些彙編指令(例如mstore),則攻擊者可能能夠將函式變數指向任何其他函式。這可能使攻擊者能夠破壞智慧合約的函式,甚至可能耗盡智慧合約資金。
由於內聯彙編是從底層訪問EVM的一種方式,因此它繞過了許多重要的安全功能。 因此,只有在必要且正確理解的情況下,才使用匯程式設計序。
存在未使用的變數
儘管允許,但最好的做法是避免使用未使用的變數。 未使用的變數會導致一些不同的問題:
· 計算量增加(不必要的氣體消耗)
· 錯誤或資料結構錯誤的指示
· 程式碼可讀性降低
強烈建議從程式碼庫中刪除所有未使用的變數。
意外的以太坊餘額
由於始終可以將以太坊傳送到智慧合約中(請參閱“強行將以太幣傳送到智慧合約”)-如果智慧合約具有特定的餘額,則很容易受到攻擊。
假設我們有一個智慧合約,如果智慧合約中儲存了任何以太坊,則該智慧合約將阻止所有函式執行。如果惡意使用者決定透過強行傳送Ether來利用此漏洞,則將引發DoS,使智慧合約無法使用。 因此請勿對智慧合約中的以太坊餘額使用嚴格的平等檢查,這一點很重要。
以太坊智慧合約程式碼始終可以被讀取。即使您的程式碼未在Etherscan上進行驗證,攻擊者仍然可以反編譯甚至檢查與它之間的事務以進行分析。
這裡的一個問題示例是猜謎遊戲,使用者必須猜測所儲存的私有變數才能贏得合同中的以太坊。當然這是極其瑣碎的利用(要點是您不應該嘗試,因為它幾乎可以肯定是蜜罐合約,要複雜得多)。
這裡的另一個常見問題是在Oracle呼叫中使用未加密的鏈下機密(例如API金鑰)。如果可以確定您的API金鑰,惡意行為者可以簡單地自己使用它或利用其他媒介,例如用盡您允許的API呼叫並強迫Oracle返回錯誤頁面,這可能會或可能不會導致問題,具體取決於智慧合約的結構。
檢測智慧合約中錯誤
有些智慧合約不希望其他智慧合約與之互動。防止這種情況的常見方法是檢查主叫帳戶中是否儲存了任何程式碼。但是智慧合約帳戶在構建過程中發起呼叫仍不會顯示它們儲存程式碼,從而有效地繞過了智慧合約檢測。
非封閉區塊鏈依賴
許多智慧合約依賴於在一定時間內發生的呼叫,但以太坊可以在相當長的時間內以相對便宜的價格透過非常高的Gwei交易進行垃圾郵件傳送。
如Fomo3D(倒數遊戲,最後一位投資者贏得了頭獎,但每項投資都增加了倒計時的時間)是由一個使用者贏得的,該使用者在短時間內完全阻塞了區塊鏈,不允許其他人在定時器執行之前進行投資出局,他贏了得了比賽。
如今有許多經紀人賭博合同依靠過去的雜湊來提供RNG。在大多數情況下,這不是可怕的RNG來源,甚至可以解釋256個區塊後發生的雜湊刪除。但是到那時,他們中的許多人根本就沒有下注。這將使某人可以對許多這些功能相似的智慧合約下注,並以一定的結果作為所有人的贏家,在主持人仍未決的情況下檢查主持人的提交,並且如果不利,只需阻塞區塊鏈,直到進行修剪即可,得到他們的賭注。
不遵守標準
在智慧合約開發方面,遵循標準很重要。設定標準是為了防止漏洞,而忽略這些漏洞可能會導致意想不到的後果。
以Binance的原始BNB令牌為例。它以ERC20代幣的形式銷售,但後來指出它實際上不符合ERC-20的原因