【慢霧】EOS智能合約最佳安全開發指南區塊鏈
本文由慢霧(SlowMist)同學主導,麒麟小組以及其他同學參與,旨在為EOS智能合約開發人員提供一些智能合約的安全準則及已知漏洞分析。https://github.com/slowmist/eos-smart-contract-security-best-practices
目錄
安全準則
已知漏洞
1. 數值溢出
1.1 漏洞示例
1.2 防御方法
1.3 真實案例
2. 權限校驗
2.1 漏洞示例
2.2 防御方法
2.3 真實案例
3. apply 校驗
3.1 漏洞示例
3.2 防御方法
3.3 真實案例
參考文獻
致謝
安全準則
EOS 處于早期階段并且有很強的實驗性質。因此,隨著新的 bug 和安全漏洞被發現,新的功能不斷被開發出來,其面臨的安全威脅也是不斷變化的。這篇文章對于開發人員編寫安全的智能合約來說只是個開始。
開發智能合約需要一個全新的工程思維,它不同于我們以往項目的開發。因為它犯錯的代價是巨大的,很難像中心化類型的軟件那樣,打上補丁就可以彌補損失。就像直接給硬件編程或金融服務類軟件開發,相比于 Web 開發和移動開發都有更大的挑戰。因此,僅僅防范已知的漏洞是不夠的,還需要學習新的開發理念:
對可能的錯誤有所準備
任何有意義的智能合約或多或少都存在錯誤,因此你的代碼必須能夠正確的處理出現的 bug 和漏洞。需始終保證以下規則:
1.當智能合約出現錯誤時,停止合約;
2.管理賬戶的資金風險,如限制(轉賬)速率、最大(轉賬)額度;
3.有效的途徑來進行 bug 修復和功能提升。
謹慎發布智能合約
盡量在正式發布智能合約之前發現并修復可能的 bug。
1.對智能合約進行徹底的測試,并在任何新的攻擊手法被發現后及時的測試(包括已經發布的合約)
2.從 alpha 版本在麒麟測試網(CryptoKylin-Testnet)上發布開始便邀請專業安全審計機構進行審計,并提供漏洞賞金計劃(Bug Bounty)
3.階段性發布,每個階段都提供足夠的測試
保持智能合約的簡潔
復雜會增加出錯的風險。
1.確保智能合約邏輯簡潔;
2.確保合約和函數模塊化;
3.使用已經被廣泛使用的合約或工具(比如,不要自己寫一個隨機數生成器);
4.條件允許的話,清晰明了比性能更重要;
5.只在你系統的去中心化部分使用區塊鏈。
保持更新
通過公開資源來確保獲取到最新的安全進展。
1.在任何新的漏洞被發現時檢查你的智能合約;
2.盡可能快的將使用到的庫或者工具更新到最新;
3.使用最新的安全技術。
清楚區塊鏈的特性
盡管你先前所擁有的編程經驗同樣適用于智能合約開發,但這里仍然有些陷阱你需要留意:
require_recipient(account_name name) 可觸發通知,調用name合約中的同名函數,官方文檔
https://developers.eos.io/eosio-cpp/v1.2.0/reference#section-require_recipient
已知漏洞
數值溢出
在進行算術運算時,未進行邊界檢查可能導致數值上下溢,引起智能合約用戶資產受損。
1.漏洞示例
存在缺陷的代碼:batchTransfer 批量轉賬
typedef struct acnts {account_name name0;account_name name1;account_name name2;account_name name3;} account_names;void batchtransfer(symbol_name symbol, account_name from, account_names to, uint64_t balance){ require_auth(from);account fromaccount; require_recipient(from); require_recipient(to.name0); require_recipient(to.name1); require_recipient(to.name2); require_recipient(to.name3); eosio_assert(is_balance_within_range(balance), "invalid balance"); eosio_assert(balance > 0, "must transfer positive balance"); uint64_t amount = balance * 4; //乘法溢出int itr = db_find_i64(_self, symbol, N(table), from); eosio_assert(itr >= 0, "Sub-- wrong name"); db_get_i64(itr, &fromaccount, (account)); eosio_assert(fromaccount.balance >= amount, "overdrawn balance"); sub_balance(symbol, from, amount); add_balance(symbol, to.name0, balance); add_balance(symbol, to.name1, balance); add_balance(symbol, to.name2, balance); add_balance(symbol, to.name3, balance);}
2.防御方法
盡可能使用 asset 結構體進行運算,而不是把 balance 提取出來進行運算。
3.真實案例
【EOS Fomo3D你千萬別玩】狼人殺遭到溢出攻擊, 已經涼涼(https://bihu.com/article/995093)
權限校驗
在進行相關操作時,應嚴格判斷函數入參和實際調用者是否一致,使用require_auth進行校驗。
1.漏洞示例
存在缺陷的代碼:transfer 轉賬
void token::transfer( account_name from,account_name to,asset quantity,string memo ){ eosio_assert( from != to, "cannot transfer to self" ); eosio_assert( is_account( to ), "to account does not exist"); auto sym = quantity.symbol.name();stats statstable( _self, sym ); const auto& st = statstable.get( sym ); require_recipient( from ); require_recipient( to ); eosio_assert( quantity.is_valid(), "invalid quantity" ); eosio_assert( quantity.amount > 0, "must transfer positive quantity" ); eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" ); eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" ); auto payer = has_auth( to ) ? to : from; sub_balance( from, quantity ); add_balance( to, quantity, payer );}
2.防御方法
使用require_auth( from )校驗資產轉出賬戶與調用賬戶是否一致。
3.真實案例
暫無
apply 校驗
在處理合約調用時,應確保每個 action 與 code 均滿足關聯要求。
1.漏洞示例
存在缺陷的代碼:
// extend from EOSIO_ABI#define EOSIO_ABI_EX( TYPE, MEMBERS ) \extern "C" { \ void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \ auto self = receiver; \ if( action == N(onerror)) { \ /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \ eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \} \ if( code == self || code == N(eosio.token) || action == N(onerror) ) { \TYPE thiscontract( self ); \ switch( action ) { \ EOSIO_API( TYPE, MEMBERS ) \} \ /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \} \} \}EOSIO_ABI_EX(eosio::charity, (hi)(transfer))
2.防御方法
使用
if( ((code == self && action != N(transfer) ) || (code == N(eosio.token) && action == N(transfer)) || action == N(onerror)) ) { }
綁定每個關鍵 action 與 code 是否滿足要求,避免異常調用。
3.真實案例
EOSBet 黑客攻擊事件復盤
(https://medium.com/@eosbetcasino/eosbet-黑客攻擊事件復盤-13663d8f3f1)
參考文獻
保管好私鑰就安全了嗎?注意隱藏在EOS DAPP中的安全隱患
https://zhuanlan.zhihu.com/p/40625180
漏洞詳解|惡意 EOS 合約存在吞噬用戶 RAM 的安全風
https://zhuanlan.zhihu.com/p/40469719
How EOSBET attacked by aabbccddeefg
https://www.reddit.com/r/eos/comments/9fpcik/how_eosbet_attacked_by_aabbccddeefg/
BET被黑客攻擊始末,實錘還原作案現場和攻擊手段
https://github.com/ganjingcun/bet-death-causes/blob/master/README.md
累計薅走數百萬,EOS Dapps已成黑客提款機?
https://mp.weixin.qq.com/s/74ggygC3nbDihLkobXOW2w
致謝
麒麟工作組
eosiofans
荊凱(EOS42)
星魂
島娘
趙余(EOSLaoMao)
字符
1.TMT觀察網遵循行業規范,任何轉載的稿件都會明確標注作者和來源;
2.TMT觀察網的原創文章,請轉載時務必注明文章作者和"來源:TMT觀察網",不尊重原創的行為TMT觀察網或將追究責任;
3.作者投稿可能會經TMT觀察網編輯修改或補充。