Paoの技術力を磨くブログ

機械学習やブロックチェーン等の技術を身に付けていくブログです。

【Ethereum】ERC20トークンで発生した脆弱性「batchOverflow」について

つい最近話題になった「batchOverflow」についてホットなうちにまとめる。

参考
イーサリアム基盤ERC20トークン“重大バグ”発見|ポロニエックスほか複数の大手取引所取引停止 | 仮想通貨まとめ
まぁ重大バグではないが。。

要約

  • ERC20トークン自体の脆弱性ではない
  • 一部のERC20トークンが独自実装している「batchTransfer」というメソッドに問題がある
  • 影響範囲としては少数のERC20トークンのみ

原因となったメソッド「batchTransfer」

早速原因となったメソッドを見ていきます。

function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
    uint cnt = _receivers.length; 
    uint256 amount = uint256(cnt) * _value; 
    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;
}

とてもシンプルなメソッドで、複数のアドレスに同時に支払いを行うもので、払い戻し等に使うことが予想される。
具体的に言えば、1つ目の引数に指定したアドレスのリスト_receiversに対して、2つ目の引数である_valueを送金する関数。

最初のところから。

uint cnt = _receivers.length;    //送金先のアドレスがいくつあるかを取得
uint256 amount = uint256(cnt) * _value;    //送金量の合計を計算

はい、いきなり原因が存在する。2行目です。
悪意のあるユーザが、この関数を呼び出すときに、_valueを巨大な値にしたとすると、
cnt*_valueは非常に大きな値になり、amountが桁あふれ(オーバーフロー)を起こしてしまう。

桁あふれ(オーバーフロー)については、こちら など調べたらいろいろ出てくるはず。
要するに3桁しか管理できないところに1000を入れても、桁が溢れて0となってしまう。

悪意あるものによってオーバーフローされると、amountはほぼ0になってしまう。

その前提で次の2行。

require(cnt > 0 && cnt <= 20);   //cntが0より大きく20以下であること
require(_value > 0 && balances[msg.sender] >= amount);    //_valueが0より大きく、送金元にamount以上が存在すること

1行目で送金先のアドレスの数を制限している。
2行目では、送金が0より大きく、送金元に十分資金があるように制限している。

ここで本来は、_valueが大きいとamountも大きくなり、送金元の保持する資金を超えてしまったらエラーになるはず。
しかし、amount = uint256(cnt) * _valueによるオーバーフローにより、_valueが大きくてもamountが小さくなってしまう。
結果、送金元に十分資金があることのチェックが出来ず、悪意あるものの攻撃に対して、条件チェックで防げなくなってしまった。

あとは残り。

balances[msg.sender] = balances[msg.sender].sub(amount);    //送金元の資金からamountを引く
for (uint i = 0; i < cnt; i++) {    //送金先アドレスの数だけループ
    balances[_receivers[i]] = balances[_receivers[i]].add(_value);    //送金先のアドレスの資金に_valueを足す
    Transfer(msg.sender, _receivers[i], _value);    //実際に送金先アドレスに、_valueを送る
}

前の通り、悪意あるものが攻撃した前提で、_valueは大きく、amountは0に近い状況を考える。
1行目で送金元資金から、amountを引いていますが、amountはオーバーフローにより小さいため、送金元資金はほとんど減らない。
(ここはあまり関係ないかも?)
そして、for文に入り、送金先アドレスに、大きい値である_valueを送金。。といった感じ。


プログラムを見ると、かなりシンプルな脆弱性であり、わかりやすいです。
そしてオーバーフローによる攻撃自体は、元々Solidityとして注意すべきものとして挙げられているので、 恐らくERCトークンに標準搭載しているメソッドであれば、誰か気づいたと思う。

また、今回の攻撃自体もスマートコントラクトのセキュリティをしっかり勉強しておけば防げると思う。
なので、セキュリティ対策はしっかり勉強していきたいですね!