# Solidity Hacks ## [Re-Entrancy](https://solidity-by-example.org/hacks/re-entrancy/) - 作法:Attack合約會定義fallback function以返回呼叫Target合約的withdraw function實現重入 - 過程:A.attack &rarr; E.deposit &rarr; E.withdraw &rarr; A.fallback &rarr; E.withdraw &rarr; A.fallback &rarr; E.withdraw &rarr; A.fallback ... 直到ethers領完為止 (balance < 1) - 修正:將state changes提前 (i.e., balances)、或是使用ReEntrancyGuard (i.e., mutex lock);等到internal work結束之後、直到最後才呼叫external function (from [Consensys](https://consensys.github.io/smart-contract-best-practices/attacks/reentrancy/)) &rarr; [checks-effects-interactions pattern](https://docs.soliditylang.org/en/develop/security-considerations.html#use-the-checks-effects-interactions-pattern) - 補充:msg.sender.call()導致Attack合約能利用剩餘gas執行、balances[msg.sender] = 0的位置 ![](https://i.imgur.com/Syk1556.png) <!-- <div style="page-break-after: always"></div> --> ## [Arithmetic Overflow and Underflow](https://solidity-by-example.org/hacks/overflow/) - 作法:利用unsigned integer的overflow or underflow特性進入漏洞 - 過程:A.attack &rarr; T.deposit &rarr; T.increaseLockTime [設定 2<sup>256</sup> - 1 + 1 - (last deposit time + 1 week)] &rarr; lockTime = 2<sup>256</sup> - 1 + 1 - (last deposit time + 1 week) + (last deposit time + 1 week) = 0 &rarr; T.withdraw - 修正:在 compiler version < 0.8 時使用SafeMath;在version >= 0.8已預設自動檢查 - 補充:上述T.increaseLockTime嘗試改設 0 - (last deposit time + 1 week) 也能達成相同效果 ![](https://i.imgur.com/YQrjUNP.png) ## [Force Feeding](https://solidity-by-example.org/hacks/self-destruct/) - 作法:利用selfdestruct強制發送ethers給target (定義fallback + revert也無效) 以進入漏洞 - 過程:傳送7 ethers給A &rarr; A.attack &rarr; E.balance = 7 ethers &rarr; no winner & 無法提領當中ethers - 修正:別使用 address(this).balance 判斷,改用state variable記錄 (e.g., balance += msg.value) - 補充:雖然E.deposit限制單次只能收 1 ether,但是selfdestruct可以繞過此限制 - 其他: - [Pre-calculated Deployments](https://consensys.github.io/smart-contract-best-practices/attacks/force-feeding/#pre-calculated-deployments) (i.e., 合約地址可推算出來,在那之前能夠事先傳送ethers過去) - [Block Rewards and Coinbase](https://consensys.github.io/smart-contract-best-practices/attacks/force-feeding/#block-rewards-and-coinbase) (i.e., 將礦工獎勵target address設定為該合約,也能強制發送) ![](https://i.imgur.com/lpLDNVJ.png) ## [Accessing Private Data (1)](https://solidity-by-example.org/hacks/accessing-private-data/) - 作法:藉由EVM內部「Layout of State Variables in Storage」的特性 fetch對應的private data - 過程:利用web3.js &rarr; getStorageAt(contract address, slot #) &rarr; 顯示data內容 - 修正:所有合約內的 private data 在區塊鏈上都是可見的,因此別放置機密資訊在合約中 - 補充:每個合約都會被分配 2<sup>256</sup> slots (32 bytes each) 的儲存空間,根據state變數宣告的排列並依序儲存下來。若有幾個size較小的變數總和在32 bytes以內,則這些會被一起packed儲存在同個slot中 (影響gas optimization)。 - 測試結果: - Contract deployed on Goerli: 0x2c40b3074f7e0dbed9142ab7a735d8e0badfad1e - passwd: 0x7465737400000000000000000000000000000000000000000000000000000000 - Slot 0: 0x ... 7b(123) - Slot 1: 0x ... 1f(31) + 01(true) + bE7d961B54a10959353a81866c5E5cF37266159E(msg.sender) &rarr; 順序不同[??] - Slot 2: 0x7465737400000000000000000000000000000000000000000000000000000000 (passwd) ![](https://i.imgur.com/N1PEtIi.png) ## [Accessing Private Data (2)](https://solidity-by-example.org/hacks/accessing-private-data/) - 作法:藉由EVM內部「Mappings and Dynamic Arrays」的特性 fetch對應的private data - 過程:利用web3.js &rarr; getStorageAt() + getArrayLocation() / getMapLocation() &rarr; 顯示data內容 - 修正:所有合約內的 private data 在區塊鏈上都是可見的,因此別放置機密資訊在合約中 - 補充:Dynamic array &rarr; currSlot儲存array length、而array element則儲存在slot# = hash(currSlot) + index * elemSize。以下圖為例,每個element為一個struct User,所以elemSize = 1 (uint256) + 1 (bytes32) = 2。 - 補充:Mapping &rarr; currSlot為空值、而array element則儲存在slot# = hash(key, slot) - 測試結果: - Contract deployed on Goerli: 0x2c40b3074f7e0dbed9142ab7a735d8e0badfad1e - Slot 6: 2 (array length) - AddUser[0]: 0, 0x7465737400000000000000000000000000000000000000000000000000000001 - AddUser[1]: 1, 0x7465737400000000000000000000000000000000000000000000000000000002 - getArrayLocation(6, 0, 2) = 111414077815863400510004064629973595961579173665589224203503662149373724986687 - getArrayLocation(6, 1, 2) = 111414077815863400510004064629973595961579173665589224203503662149373724986689 - Slot 7: (empty) - AddUser[1]: 1, 0x7465737400000000000000000000000000000000000000000000000000000002 - getMapLocation(7, 1) = 81222191986226809103279119994707868322855741819905904417953092666699096963112 ![](https://i.imgur.com/SpwjXDK.png) ![](https://i.imgur.com/be5EWOQ.png) ## [Delegatecall](https://solidity-by-example.org/hacks/delegatecall/) - 作法:使用delegatecall時必須牢記兩件事 1. delegatecall保留context(storages、caller等) &rarr; 執行後改變的是呼叫delegatecall的合約狀態 2. 呼叫delegatecall的合約 & 被呼叫的合約 之 storage layout須相同 - 過程:A.attack &rarr; H.fallback &rarr; H.delegatecall &rarr; L.pwn &rarr; H.owner = msg.sender (i.e., A.address) - 修正:使用不會改變state variable的library - 補充:delegatecall得小心使用,用法錯誤或理解不正確可能導致毀滅性的結果 ![](https://i.imgur.com/yUk30SF.png) ## [Source of Randomness](https://solidity-by-example.org/hacks/randomness/) - 作法:利用Attack合約進行相同的計算產出answer,接著呼叫G.guess()將answer傳送過去即破解 - 過程:A.attack &rarr; compute answer &rarr; G.guess(answer) &rarr; G send ether to A - 修正:實作與隨機數有關的應用時,不可使用blockhash與block.timestamp ![](https://i.imgur.com/BZdhOTf.png) ## [Denial of Service](https://solidity-by-example.org/hacks/denial-of-service/) - 作法:利用DOS原理(i.e., revert),使得合約變得無法再使用 (unusable) - 過程:User1 sends 1 ether to K.claimThrone &rarr; User2 sends 1 ether to K.claimThrone &rarr; User1 receives 1 ether (refund) &rarr; A.attack [+ 3 ethers] &rarr; K.claimThrone &rarr; 其他users無法再執行K.claimThrone - 修正:改用withdraw的方式,讓users自行領取refunds ([favor pull over push](https://consensys.github.io/smart-contract-best-practices/development-recommendations/general/external-calls/#favor-pull-over-push-for-external-calls)) - 補充:下一位user執行K.claimThrone並要refund給A時,因為該合約沒有fallback function,故發生revert而無法順利refund,導致current King無法退位使得合約becomes unusable。 - 補充:Attack合約內的comments描述「即便沒有require(sent, ...),此攻擊仍然有效」,但自行測試結果表示攻擊無效[??],僅導致變數值(balance)與合約餘額(K.balance)變得不同 - 其他: - [Gas Limit DoS on a Contract via Unbounded Operations](https://consensys.github.io/smart-contract-best-practices/attacks/denial-of-service/#gas-limit-dos-on-a-contract-via-unbounded-operations) - e.g., 利用迴圈支付給許多帳戶而超過block gas limit導致tx failure - [Gas Limit DoS on the Network via Block Stuffing](https://consensys.github.io/smart-contract-best-practices/attacks/denial-of-service/#gas-limit-dos-on-the-network-via-block-stuffing) - e.g., attacker可以透過以足夠高的 gas price 放置計算密集型交易,防止其他交易被包含在區塊鏈當中的幾個區塊,例如Fomo3D攻擊事件 ![](https://i.imgur.com/HsyQfk9.png) ## [Phishing with tx.origin](https://solidity-by-example.org/hacks/phishing-with-tx-origin/) - 作法:利用tx.origin的特性 + 網路釣魚 以實現攻擊 - 過程:Attacker誘使W合約owner呼叫A.attack &rarr; W.transfer &rarr; tx.origin = owner &rarr; 成功將ethers轉給Attacker - 修正:改用msg.sender而不使用tx.origin - 補充:若合約A呼叫合約B、合約B呼叫合約C,在C中msg.sender是B,tx.origin是A ![](https://i.imgur.com/0Wc7Loz.png) ## [Hiding Malicious Code with External Contract](https://solidity-by-example.org/hacks/hiding-malicious-code-with-external-contract/) - 作法:任何地址都可以被鑄造成特定的合約,即使該地址的合約不是被鑄造的那個 - 過程:佈署Foo合約時,_bar填寫的是Mal合約(layout與Bar相同)地址,Mal為其他檔案且被隱瞞 &rarr; 呼叫F.callBar() &rarr; 結果執行的是M.log [user以為是執行Bar.log] &rarr; M.log裡可能包含惡意程式碼 - 修正:於F.constrcutor產生新合約[bar = new Bar()]、並且將bar改為public以方便瀏覽合約 ![](https://i.imgur.com/hTZUVih.png) ## [Front Running](https://solidity-by-example.org/hacks/front-running/) - 作法:Attacker可以監視tx pool並發送交易,將其包含在user交易之前的塊中。這種機制可以被用來重新排序交易,以達到對attacker有利。 - 過程:user找到正確solution後發送交易呼叫F.solve &rarr; A在tx pool中發現user交易中的答案 &rarr; A發送交易(+找到的答案 & 設定高額gas price) &rarr; A的交易被排在user前面 &rarr; A獲得獎金10 ether - 修正:採用 [commit-reveal scheme](https://medium.com/swlh/exploring-commit-reveal-schemes-on-ethereum-c4ff5a777db8) 寫法、或者是使用 [submarine send](https://libsubmarine.org/) - Commit-Reveal Schemes為一種加密演算法,用於允許某人承諾一個值,同時將其隱藏不讓其他人知道,並能夠在以後揭示它。該方案有兩個階段:選擇和指定值的commit phase & 顯示和檢查值的reveal phase - 隱藏committed value的方法即使用hash;要reveal時,將solution之hash與committed value比對以確認commit前後答案是否一致、再與正確答案(bytes32 public hash)比對即可 ![](https://i.imgur.com/oA07Ah1.png) ![](https://i.imgur.com/YmmcnP8.png) ## [Block Timestamp Manipulation](https://solidity-by-example.org/hacks/block-timestamp-manipulation/) - 作法:block.timestamp 可以被礦工操縱 - 過程:Attacker運行一個powerful miner節點以控制block timestamp &rarr; 該節點設定一個可以被15整除的timestamp並出塊 &rarr; attacker贏得這場遊戲並取得ethers - 修正:不要使用 block.timestamp 作為隨機數 - 補充:礦工實際上可以在區塊被驗證後的15秒內發佈一個timestamp (from [Consensys](https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/timestamp-dependence/)) ![](https://i.imgur.com/zCtqljx.png) ## [Signature Replay](https://solidity-by-example.org/hacks/signature-replay/) - 作法:相同簽名可多次被使用來執行函數。若簽名者的意圖是一次批准交易,這可能是有害的。 - 修正:加入nonce和合約address簽署訊息 & 記錄txHash是否已執行 (或導入[EIP-712實作方法](https://mirror.xyz/xyyme.eth/cJX3zqiiUg2dxB1nmbXbDcQ1DSdajHP5iNgBc6wEZz4)) ![](https://i.imgur.com/FNQsh75.png) ## [Bypass Contract Size Check](https://solidity-by-example.org/hacks/contract-size/) - 作法:佈署一份新合約,使之在佈署時constructor內呼叫該對象合約即能繞過size check - 補充:使用extcodesize判斷caller是否為合約未能 100% cover (在constructor內呼叫時,其extcodesize = 0) ![](https://i.imgur.com/1CobKH4.png) ## Oracle Manipulation https://consensys.github.io/smart-contract-best-practices/attacks/oracle-manipulation/ ## Griefing https://consensys.github.io/smart-contract-best-practices/attacks/griefing/ ## Honeypot https://solidity-by-example.org/hacks/honeypot/ <div style="page-break-after: always"></div>