Multichainで起きた新しい?ハッキング手法
Multichain とは?
Multichain は、異なる多様なブロックチェーンが相互に通信するためのニーズをサービス化するため、2020年7月20日に Anyswap として誕生しました。Multichain により、ほとんど全てのブロックチェーンが相互運用できるようになります。Ethereum のようなチェーン(例:Binance Smart Chain)、Ethereumにファイナリティを求める異なる Layer 2 チェーン(例:Polygon)、パラチェーンのネットワーク(例:Polkadot システムの Moonbeam)、Bitcoin 型のチェーン(例:Litecoin)、COSMOS チェーン(例:Terra)など制限はありません。
Multichain ハッキング事件
6 つのトークン(WETH, PERI, OMT, WBNB, MATIC, AVAX)に影響を及ぼす重大な脆弱性が存在することをセキュリティ企業 Dedaub 社から報告されました。ハッキングに脆弱な 6 つのトークンについて、ユーザがスマートコントラクトの承認を削除することを開始するようブログ投稿で発表しました。
しかし、この情報が悪意のあるハッカーに伝わり、プロトコルの脆弱な口座から資金が流出しました。幸いなことに、ハッカーの 1 人はホワイトハット・ハッカーであり、彼は盗んだ資金の約80%を返却し、一部をバグバウンティとして手元に残しました。
実はこのように、暗号通貨が盗まれた後にハッカーがその通貨を返却するケースはいくつかあるようです。
発見された脆弱性
Multichain コントラクトの今回のケースにおいて、攻撃ベクターは 2 つにわかれます。1 つは WETH(Wrapped ETH)流動性保管コントラクト(AnyswapV5ERC20 のインスタンス)で、もう 1 つはトークンを他のチェーンに転送するルーターコントラクト(AnyswapV4Router)で発生しました。
イーサリアムだけでも、たった 3 つのアカウントから、1 回の直接取引で4億3100万ドルの WETH が盗まれることになります。また、同様のコントラクトが、異なるトークンや Polygon、BSC、Avalanche、Fantom など複数のブロックチェーンで展開されています。
主な被害者となるであろうアカウント、AnySwap Fantom Bridge は、Fantom ブロックチェーンに移動したトークンをエスクローしています。つまり、攻撃者は任意の金額を Fantom に移動させ、ブリッジの現在の $367Mと共に、Ethereum 上でそれを盗み返すことができるのです。移動したトークンは、Fantom でも、その後移動した他の場所でも、まだ生きている(そして価値がある)でしょう。このため、この攻撃の潜在的な影響は理論的には無限となります。イーサリアムの被害者から盗まれた $431M の金額に加えて、他のチェーンでいくらであっても、「投資」した金額を 2 倍にすることが可能です。
攻撃ベクター
呼び出し側は、任意のトークンに対する permit( ) 復帰に依存してはいけません。token.permit(…) の呼び出しは、以下のようなトークンに対しては決して復帰しません。
permit( ) を実装していない
(返り値のない)フォールバック関数を実装している
-> フォールバック関数に関しては以下に詳細を書いてます
最も注目すべきは、WETH(ERC-20 による ETH の表現)がそのようなトークンであることです。
このようなパターンをファントム関数と呼ぶことにします。例えば、「WETH はファントム permit( ) を持っている」あるいは「permit( ) は WETH コントラクトのファントム関数である」と言います。ファントム関数を持つコントラクトは、その関数を実際に定義しているわけではなく、返り値がなしでその関数へのあらゆる呼び出しを受け入れるのです。Ethereum の場合、ファントム permit( ) を持つ他の高値トークンは BNB と HEX があります。他のチェーン(WBNB、WAVAX など)のネイティブに相当するトークンも、ファントム permit( ) を持っている可能性があります。
さらに詳しく
Solidity のスマートコントラクトには、フォールバック関数を含めることができます。これは、コントラクト上で任意の関数 func( ) が呼び出されたが、func( ) が定義されていない場合に呼び出されるコードです。しかし、Solidity の古いバージョンでは、フォールバック関数はコントラクトが ETH を受け取ったときに呼び出されるコードでもあるため、フォールバック関数を含めることが一般的でした。(Solidity の新しいバージョンでは、代わりに明示的な受信関数が使用されます。) 実際、フォールバック関数は以前は名前がなく、単に function( ) でした。例えば、WETH コントラクトには、以下のように定義されたフォールバック関数があります。
function() public payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
Deposit(msg.sender, msg.value);
}
この関数は ETH を受け取るとき(そしてちょうどそれを預けて、それでラップされた ETH を発行するとき)にも呼ばれますが、重要なのは未定義の関数が WETH コントラクト上で呼び出されるときにも呼ばれることです。
問題は、未定義の関数が重要なセキュリティチェックを行うために信頼されている場合はどうなるかということです。
AnySwap/MultiChain コードの場合、最も単純で脆弱なコントラクトは以下のようなコードを含んでいます。
function deposit() external returns (uint) {
uint _amount = IERC20(underlying).balanceOf(msg.sender);
IERC20(underlying).safeTransferFrom(msg.sender, address(this), _amount);
return _deposit(_amount, msg.sender);
}
...
function depositWithPermit(address target, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s, address to) external returns (uint) {
IERC20(underlying).permit(target, address(this), value, deadline, v, r, s);
IERC20(underlying).safeTransferFrom(target, address(this), value);
return _deposit(value, to);
}
通常の入金 deposit( ) は、外部呼び出し元(msg.sender)から、からこのコントラクトに送金することを意味します(このコントラクトは、支出者として承認されている必要があります)。
この入金動作は常に安全ですが、クライアントは、自分が呼び出しを開始したとき、つまり自分が msg.sender であるときにだけ、この動作が起こると確信しているので、コントラクトがお金を転送することを承認しています。
しかし、資金を預ける 2 番目の経路である depositWithPermit( ) は、permit( ) 呼び出しが成功する限り、他の誰か(target)が所有する資金を預けることができます。
それをサポートする ERC-20 トークンでは、permit( ) は標準的な approve( ) 呼び出しの代替となります。許可者は、permit リクエストに署名することで、受益者がお金を使うことを承認しています。
この場合の問題は、先に述べたように、WETH トークンがファントム permit( ) を持っているので、depositWithPermit( ) の呼び出しは失敗しません。また、誰かの協力も必要ありません。
残念なことに、このコントラクトには、入金経路 deposit( ) を利用したすべてのクライアントの承認がすでに存在しています。そのようなクライアントのすべての WETH は、depositWithPermit( ) に続いて withdraw を呼び出すだけで、盗まれる可能性があります。