有名なスマートコントラクト脆弱性・ハックについての復習 / Widely known vulnerability of smart contract

4e1cbdfcd220a481546d25eb1de7a152?s=47 Nao Hanamura
September 26, 2018
620

有名なスマートコントラクト脆弱性・ハックについての復習 / Widely known vulnerability of smart contract

4e1cbdfcd220a481546d25eb1de7a152?s=128

Nao Hanamura

September 26, 2018
Tweet

Transcript

  1. 1 有名なスマートコントラクト脆弱性・ハック についての復習 Widely known vulnerability of smart contract 2018/9/26

    Neutrino Naochika Hanamura
  2. 2 これまでやってきたこと 自己紹介:2017年5月頃からブロックチェーンのエンジニアをしており、特に 仮想通貨監査などに関わってきました 開発・研究 •  ブロックチェーンデータの分析基盤構築 •  ブロックチェーン領域のモニタリングサービス開発 • 

    トレーサブルリング署名研究 コミュニティ活 動 •  DashのWhitePaper翻訳 •  技術ブログ「Neutrinoで働くブロックチェーンエンジニアのブログ」 (www.blockchainengineer.tokyo/) 仮想通貨 監査 •  仮想通貨監査の技術アドバイザリー ✓ 特に取引所の監査手続開発 ✓ 特定通貨の監査手法などのリサーチ 花村直親(Naochika Hanamura) @naomasabit
  3. 3 自己紹介:8月からcatabiraというブロックチェーンの分析サービスを提供す る会社を共同創業しました https://catabira.co/ ໾৬͸Chief Blockchain Officerͱ໊෇͚ΒΕͨ

  4. 4 Ethereumスマートコントラクトのハッキング事 例からみるセキュリティポイント

  5. 5 スマートコントラクトは、価値の移転をルールに従って起こす仕組 みである ޿͍ҙຯͰ͸ɺܖ໿ΛػցͰ࣮૷͢Δ࢓૊Έʢ˞ʣ ڱ͍ҙຯͰ͸ɺσδλϧʹදݱ͞ΕΔࢿ࢈Λ༧ΊఆΊΒΕͨϧʔϧʹैͬͯࣗಈతʹҠసͤ͞Δ࢓૊Έ Ethereumʹ͓͍ͯ͸… • ίʔυΛΞυϨεʹࡌͤͯϒϩ οΫνΣʔϯʹσϓϩΠ͢Δ • σϓϩΠ͞ΕͨίʔυΛϒϩο ΫνΣʔϯͱͯ͠ಈ͔͢

    • ίʔυ͕ಈ͘͝ͱʹಈ͔͢ओମ ͷΨε͕ফඅ͞ΕΔ ੪౻ݡ࣐ࢯͷϒϩοΫνΣʔϯ࿈ଓߨٛୈճΑΓ IUUQTXXXESPQCPYDPNTSKCMSJIPHKJDK#D)TNBSUDPOUSBDUBQEG EM DEXͷྫ
  6. 6 Reentrancy Attack(再入国攻撃)によってDAO事件では当時の価値約65 億円が被害を受けた %"0 $POUSBDU )BDLFS $POUSBDU 1.攻撃者はあらかじめ 少額のETHをdepositしておく

    %"0 $POUSBDU )BDLFS $POUSBDU 2.攻撃者は1で預けた分、withdrawコントラクトを呼び出す が、前提4のFallback関数の動きより、被害者が残高帳を 更新する前に出金が何度も行われる 1.被害者のコントラクトには - deposit関数(預金) - withdraw関数(出金) が定義されており、 銀行的にユーザーの残高を預かったり引き出し たりできる 2.withdraw関数(出金)は、先にETHの出金をして から、残高帳の預金数字を減らす 3.攻撃者は自身にコントラクトを持つアドレスであ る 4.攻撃者は被害者のwithdrawコントラクトを呼び 出し、ETHの出金がされた直後に何度もwithdraw するような処理をFallback関数に既定しておく deposit࣮ߦ withdraw × αճ Reentrancy AttackʢDAOʣ લఏ
  7. 7 Reentrancy Attack(再入国攻撃) - DAO事件の攻撃サンプルとハッキン グされたコードを示す #Ϣʔβʔͷ࢒ߴா mapping (address =>

    uint) private userBalances; #Ҿ͖ग़ؔ͠਺ function withdrawBalance() public { # Ҿ͖ग़͠Մೳֹۚͷऔಘ uint amountToWithdraw = userBalances[msg.sender]; # require͔Β߈ܸऀ͸FallbackΛ࣮ߦͯ͠Ҿ͖ग़͠Λ ϧʔϓ࣮ߦ͢Δ require(msg.sender.call.value(amountToWithdraw) ()); # Ϣʔβʔͷ࢒ߴாߋ৽Λߦ͏ userBalances[msg.sender] = 0; } int private count; function Attacker() public { count = 0; } # Fallbackؔ਺ function () payable public { count += 1; # Ҿ͖ग़͠ΛԿ౓΋ߦ͏ॲཧΛنఆ͓ͯ͠ ͘ if (count < 10000) { DaoContract daoContract = DaoContract(msg.sender); daoContract.withdrawBalance(); } } DAOContract AttackContractExample
  8. Reentrancy Attackを防ぐためには、繰り返しに耐える処理を実装する 必要があった #Ϣʔβʔͷ࢒ߴா mapping (address => uint) private userBalances;

    #Ҿ͖ग़ؔ͠਺ function withdrawBalance() public { # Ҿ͖ग़͠Մೳֹۚͷऔಘ uint amountToWithdraw = userBalances[msg.sender]; #͜͜Λى఺ʹ߈ܸऀ͸FallbackΛ࣮ߦͯ͠Ҿ͖ग़͠ Λϧʔϓ࣮ߦ͢Δ require(msg.sender.call.value(amountToWithdraw)()); # Ϣʔβʔͷ࢒ߴாߋ৽Λߦ͏ userBalances[msg.sender] = 0; } Original DAOContract #Ϣʔβʔͷ࢒ߴா mapping (address => uint) private userBalances; #Ҿ͖ग़ؔ͠਺ function withdrawBalance() public { # Ҿ͖ग़͠Մೳֹۚͷऔಘ uint amountToWithdraw = userBalances[msg.sender]; # Ϣʔβʔͷ࢒ߴாߋ৽ΛɺҾ͖ग़͠લʹߦ͏ ͜ͱͰReentrancy߈ܸʹରԠ͢Δ userBalances[msg.sender] = 0; require(msg.sender.call.value(amountToWithdraw) ()); } Protected DAOContract 8
  9. 9 Etherscanの攻撃トランザクション Beautychain Token(BEC)では、オーバーフローを利用した攻撃があり、 発行量以上の過剰な供給と引き出しが行われた https://etherscan.io/tx/ 0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f ڙڅྔ͕7000000000ͷ τʔΫϯʹରͯ͠ɺ 5789604461865809771

    1785492504343953926 6349923328202820197 2879200395656481996 8τʔΫϯ͕2ͭͷΞυ ϨεʹҾ͖ग़͞Ε͍ͯ Δ
  10. 10 BECトークンの不正な引き出しを起こすために、オーバーフローを利 用して所持残高チェックを擦り抜けた //࢓༷ɿෳ਺ͷΞυϨεʹରͯ͠Ұ౓ʹಉ਺ͷτʔΫϯΛૹ Δ //Ҿ਺ɿ1,ΞυϨεͷ഑ྻ 2,ૹΔτʔΫϯྔ function batchTransfer(address[] _receivers,

    uint256 _value) public whenNotPaused returns (bool) { //① ૹΔτʔΫϯྔʹҾ͖౉͞ΕͨΞυϨεͷ਺Λ͔͚ ͯɺૹΔ૯ྔΛࢉग़͢Δ uint cnt = _receivers.length; // ͜͜ͰΦʔόʔϑϩʔΛى͜͢ uint256 amount = uint256(cnt) * _value; //② ૹΔର৅ΞυϨε͕1Ҏ্20ҎԼ͔ɺૹΓݩʹे෼ͳ τʔΫϯྔ͕͋Δ͔Λ൑ఆ require(cnt > 0 && cnt <= 20); require(_value > 0 && balances[msg.sender] >= amount); //③ ֤ΞυϨεʹରͯ͠ಉ਺ͷτʔΫϯΛૹΔ balances[msg.sender] = balances[msg.sender].sub(amount); for (uint i = 0; i < cnt; i++) { balances[_receivers[i]] = balances[_receivers[i]].add(_value); Transfer(msg.sender, _receivers[i], _value); } return true; } BEC Token Contract オーバーフローを起こした方法 •  引数はアドレス(_receivers)を2つ、送 るトークン量(value)が2^255 •  攻撃者のBECトークン所持量は0 •  amount = 2^255 *2 となり、uint256 型の限界値2^256が巻き戻って0にな る •  引数(value)に設定された2^255BEC トークンが引数のアドレスにそれぞれ 送られる
  11. •  攻撃によるトークン供給量 (攻撃によるトークン供給量) / (単位補正※) =2^256 / (10^18) =115,792,089,237,316,000,000,000,000,000,0 00,000,000,000,000,000,000,000,000,000

    ≒ 1,158阿僧祇(あそうぎ) ※BECトークンの1単位が10^18なので補正す る •  倍率 (攻撃によるトークン供給量 + 元の供給量) / (元の供給量) = (2^256 / (10^18) + 7,000,000,000) / 7,000,000,000 =16,541,727,033,902,300,000,000,000,000,00 0,000,000,007,000,000,000 ≒ 16.5極 結果、元の供給量70億に対して1,158阿僧祇(あそうぎ)量が供給さ れ、16.5極(ごく)倍のインフレーションが起きて価値が崩壊した ୯Ґ ಡΈํ େ͖͞ ԯ ͓͘ 10^8 ஹ ͪΐ͏ 10^12 ژ ͚͍ɺ͖ΐ͏ 10^16 ᆇ ͕͍ 10^20 䉾 ͡ΐɺ͠ 10^24 য় ͡ΐ͏ 10^28 ߔ ͜͏ 10^32 ׾ ͔Μ 10^36 ਖ਼ ͍ͤ 10^40 ࡌ ͍͞ 10^44 ۃ ͘͝ 10^48 ߃Տࠫ ͝͏͕͠Ό 10^52 Ѩૐᷫ ͋ͦ͏͗ 10^56 ಹ༝ଞ ͳΏͨ 10^60 ෆՄࢥٞ ;͔͗͠ 10^64 ແྔେ਺ ΉΓΐ͏͍ͨ͢͏ 10^68 ࢀߟɿ਺ͷ୯Ґ 11
  12. 攻撃を防ぐためにはオーバーフローを考慮した四則演算を行う必要 があった OpenZeppelinは安全にsolidityを書くためのフレームワークを提供している function mul(uint256 _a, uint256 _b) internal pure

    returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (_a == 0) { return 0; } uint256 c = _a * _b; // ݕࢉͯ͠ΦʔόʔϑϩʔରԠΛ͍ͯ͠Δ require(c / _a == _b); return c; } OpenZeppelin SafeMath.sol 12
  13. Parity Multisig Walletでは、オーナー権限の取得が外部から呼び出せる 状態にあったことで、コントラクトが使用不能になった // throw unless the contract is

    not yet initialized. modifier only_uninitialized { if (m_numOwners > 0) throw; _; } //①ϋοΧʔ͸ॳظԽΛͯ͠ΦʔφʔݖݶΛखʹೖΕ ͨ //WalletͷॳظԽؔ਺ initWallet(address[] _owners, uint _required, uint _daylimit) only uninitialized { //೔࣍ͷૹ্ۚݶֹͳͲΛઃఆ͢Δؔ਺ initDaylimit(_daylimit); //ΦʔφʔΛઃఆ͢Δؔ਺ initMultiowned(_owners, _required); } // constructor - stores initial daily limit and records the present day's index. function initDaylimit(uint _limit) only_uninitialized { m_dailyLimit = _limit; m_lastDay = today(); } 13 Parityͷར༻ ϥΠϒϥϦ 0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4 // constructor is given number of sigs required to do protected "onlymanyowners" transactions // as well as the selection of addresses capable of confirming them. function initMultiowned(address[] _owners, uint _required) only_uninitialized { m_numOwners = _owners.length + 1; m_owners[1] = uint(msg.sender); m_ownerIndex[uint(msg.sender)] = 1; for (uint i = 0; i < _owners.length; ++i) { m_owners[2 + i] = uint(_owners[i]); m_ownerIndex[uint(_owners[i])] = 2 + i; } m_required = _required; } // kills the contract sending everything to `_to`. //② ΦʔφʔݖݶΛ࣋ͬͨϋοΧʔ͸killΛݺͼग़ͯ͠ແޮ Խͨ͠ // Φʔφʔ͕ݺͼग़ͤΔίϯτϥΫτͷແޮԽ function kill(address _to) onlymanyowners(sha3(msg.data)) external { suicide(_to); }
  14. initWalletが外部から呼び出せる状態になっていたことで、コントラク トが使用不能になった 14 1 2 Walletが初期化されていない状態で放置されていたc 3 ついでにkill()してコントラクトを無効にするsuicide行為をした 4 ライブラリが削除されたために、Parity

    Multisig Wallet内に収まっていたEtherは凍結と なった ハッカーがinitWallet()で初期化してオーナー権限を奪った Ether凍結の流れ
  15. 15 Ethereumスマートコントラクトのハッキング事例からみるセキュリテ ィポイント ü  ੬ऑੑͷ͋ΔίʔυΛΫϥοΩϯά͢Δ͜ͱͰෆਖ਼ͳࢿ࢈Ҡಈ͕Ͱ͖ͯ͠·͏έʔε ͕͋Δ ü  Ϣʔβʔͷൿີ伴Λ༬͔Βͳ͍ͱͯ͠΋ɺίʔυͷ҆શੑΛ୲อ͢Δඞཁ͕͋Δ ü  σϓϩΠͨ͠ίʔυ͸΄΅มߋͰ͖ͳ͍ͨΊɺࣦഊͰ͖ͳ͍ʢϒϩοΫνΣʔϯͷվ

    ͟ΜෆՄೳੑʣ ü  ಗ໊Ͱ௨՟͕खʹೖΔͨΊΫϥοΩϯάͷΠϯηϯςΟϒ΋ߴ͍ʢϒϩοΫνΣʔϯ ͷಗ໊ੑʣ
  16. 16 We Are Hiring.