読者です 読者をやめる 読者になる 読者になる

counterwallet.ioが改竄された時に僕のビットコインは安全か

ウィルス感染でWebサービスが20日間ダウン

ウィルス感染でWebサービスが20日間ダウン。本当にごめんなさい

サーバーを乗っ取られて信用を落とした顛末が書かれてある記事。他人事ではない。

counterwallet.ioは秘密鍵を保持しないから安全!?

counterwallet.ioは秘密鍵を保持しないから安全と言われています。ただしサーバーがハッキングされてAPIが改竄されていた場合に以下のような危険があります。

送信先が改変されたtxにサインしてしまう危険性

XCP送信例。トランザクション作成

  • 送信元: 12sWrxRY7E7Nhmuyjbz4TtGE9jRewGqEZD
  • 送信先: 1965areciqapsuL2hsia2yKkRLfAsH1smG
  • 送金額: 0.0005XCP
curl http://xxx:xxx/api/ --user xxx:xxx -H 'Content-Type: application/json; charset=UTF-8' -H 'Accept: application/json, text/javascript' --data-binary '{"jsonrpc":"2.0", "id":0, "method":"create_send",   "params":{"source":"12sWrxRY7E7Nhmuyjbz4TtGE9jRewGqEZD", "destination":"1965areciqapsuL2hsia2yKkRLfAsH1smG", "quantity":50000, "asset":"XCP"}}'

レスポンス

未サイントランザクションが返ります。

{"id": 0, "result": "01000000038273bcfe9961f0eb03fa90bdb91af8d1a96a18cdfcf7e362e6260dff7ebf9851000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffff7dd4cdce57f4cd83137e669cd4c64ae749433b889df781fb4fc30b6f219ae61b000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffffccc2c1ddf717c52276461ad031d9471c8f459a5ba3865cb381ecb5a914fd6cfe000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffff0336150000000000001976a91458b6e991b45487df810f4d96d5315da739637f1788ac00000000000000001e6a1c176441d9b0d5e714def4becc729931ec91004dfaf58b4d18c6757f1c73310000000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88ac00000000", "jsonrpc": "2.0"}

送信先の改竄のリスクがある

例えばPHPAPIを実装していた場合なら、送信先を こそっと 変更することは簡単です。レスポンスを見ただけでは正しいかは分かりません。

counterwallet.ioは未サイントランザクションのチェックがある

さすがにマズイのでcounterwallet.ioは、サイン前に未サイントランザクションのチェックを行っています。以下に必要そうなところだけ抜き出しました。

CWBitcore.checkTransactionDest = function(txHex, source, dest) { 
  var tx = CWBitcore.parseRawTransaction(txHex);    
  for (var i=0; i<tx.outs.length; i++) {
      var addresses = CWBitcore.extractAddressFromTxOut(tx.outs[i]).split(',');
      var containsSource = _.intersection(addresses, source).length > 0;
      var containsDest = _.intersection(addresses, dest).length > 0;
      if ((containsSource == false && containsDest == false) && 
           tx.outs[i].getScript().classify() != bitcore.Script.TX_RETURN &&
           tx.outs[i].getScript().classify() != bitcore.Script.TX_UNKNOWN) {
        return false;
      }
  return true;
}

CWBitcore.extractAddressFromTxOut = function(output) {
  switch (output.script.classify()) {
    case bitcore.Script.types.PUBKEY_OUT:
      return output.script.toAddress(NETWORK).toString();

    case bitcore.Script.types.PUBKEYHASH_OUT:
      return output.script.toAddress(NETWORK).toString();

    case bitcore.Script.types.SCRIPTHASH_OUT:
      return output.script.toAddress(NETWORK).toString();

    case bitcore.Script.types.MULTISIG_OUT:
      var addresses = CWBitcore.extractMultiSigAddressesFromScript(output.script);
      return addresses.join(",");

    case bitcore.Script.types.DATA_OUT:
      return "";

    default:
      throw new Error("Unknown type [" + output.script.classify() + "]");
  }
}

未サイントランザクションCheck簡易版

説明を簡単にするために簡易版のスクリプトを用意しました。

bower install

bower install bitcore-lib
bower install bitcore-mnemonic

html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <script src="bower_components/bitcore-lib/bitcore-lib.min.js"></script>
  <script src="bower_components/bitcore-mnemonic/bitcore-mnemonic.min.js"></script>
</head>
<body>
  <script type="text/javascript">
    var bitcore = require('bitcore-lib');
    var Mnemonic = require('bitcore-mnemonic');

    var raw = new bitcore.deps.Buffer('01000000038273bcfe9961f0eb03fa90bdb91af8d1a96a18cdfcf7e362e6260dff7ebf9851000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffff7dd4cdce57f4cd83137e669cd4c64ae749433b889df781fb4fc30b6f219ae61b000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffffccc2c1ddf717c52276461ad031d9471c8f459a5ba3865cb381ecb5a914fd6cfe000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffff0336150000000000001976a91458b6e991b45487df810f4d96d5315da739637f1788ac00000000000000001e6a1c176441d9b0d5e714def4becc729931ec91004dfaf58b4d18c6757f1c73310000000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88ac00000000', 'hex');
    var tx = new bitcore.Transaction(raw);

    for (var i = 0; i < tx.outputs.length; i++) {
      var type = tx.outputs[i].script.classify();
      switch (type) {
        case bitcore.Script.types.PUBKEY_OUT:
        case bitcore.Script.types.PUBKEYHASH_OUT:
        case bitcore.Script.types.SCRIPTHASH_OUT:
          console.log(tx.outputs[i].script.toAddress(bitcore.Networks.livenet).toString());
          break;
          
        case bitcore.Script.types.MULTISIG_OUT:
        case bitcore.Script.types.DATA_OUT:
        default:
          console.log('Unknown type [' + type + ']');
      }
    }
  </script>
</body>
</html>

コンソールログ

index.html:28 1965areciqapsuL2hsia2yKkRLfAsH1smG
index.html:34 Unknown type [Data push]
index.html:28 12sWrxRY7E7Nhmuyjbz4TtGE9jRewGqEZD

Blockchain.info

3つのスクリプトに含まれる2つのアドレスが、コンソールに出力されたアドレスと一致しています。この一致を比較することで意図した送金を確認できます。

スクリーンショット 2016-02-23 17.02.52.png

Data pushスクリプトの中身は見なくていいの?

BitcoinとCounterpartyの送信トランザクションの比較 に書きましたが、Data pushスクリプトには送金元、送金先の情報は含めないので、悪意あるユーザーに送金されることはありえません。

ただし、愉快犯的なクラッカーにより不正な値のトランザクションが送信される可能性があります。(例えば、勝手にアセットにロックをかけたり、配当もできる..?)

チェックスクリプト自体を改変された場合はどうなるのか

Webアプリの場合はチェックスクリプト自体を改変・無効に変更することができますね.. コード改竄監視は必要になりそうです。気になるのでcounterwallet.ioコミュニティに質問投げてみます。

クライアントがモバイルアプリの場合は、チェックスクリプトの改竄リスクは低いでしょう。

まとめ

サーバのセキュリティを強化しないといけない.. と思いました。どのプロダクトが良いのか調べてみます。

参考

Underscore.js

Problem with Buffer when trying to add a string to a script

Bitcoins the hard way: Using the raw Bitcoin protocol

BitcoinとCounterpartyの送信トランザクションの比較

TipMe

TipMe with IndieSquare