DeFiで落ちてるお金を拾いたいブログ

DeFiについてのメモ書きやポエムを投下する予定です

Beanstalkのexploitに感動したので半年ぶりにブログ書く

はじめに

こんにちは、exploitで6万ドル持ってかれて財布がすっからかんになったビビドット (@vividot) | Twitterです。泣きながらハーモニー馬を売って補填します。

そもそもexploitというのは、DeFiの実装の不具合や運用のミスを突いて他のユーザの資金を搾取する攻撃・操作の総称です。ちなみに今回のexploitの被害額(攻撃者の得た利益の意)は7600万ドルで歴代11番目となっています。 最近のexploitは「よくわからないDeFiがオラクルを操作されてやられた」だの「5 out of 9のマルチシグなのに1つのエンティティが5つの署名を求めることが可能だった」だの、しょうもないexploitばっかりだったのですが久しぶりに感動したので筆をとりました。

正直exploitを回避する術は「堅いDeFi以外を使わない」に限るので、この記事の意図は感動を伝えたい9割・注意喚起1割ぐらいですが、高いAPRを求める方の参考になれば幸いです。

Beanstalkってなんやねん

BeanstalkというのはBeanと呼ばれる無担保型のステーブルコインを実現するプロトコルで、既にペグが外れてしまったBasis CashやESD等の後継といっても過言ではありません。 また、無担保型ステーブルコインの仲間として、ペグを維持する仕組みは異なりますがUSTやFRAXなどが挙げられます。

Beanstalkの仕組みとして、Beanの価格が1ドルを上回っている間は1時間ごとにその時のBeanの価格に応じて新規でBeanがmintされ、そのうち半分がBeanのホルダー及び流動性提供者に対して分配されます。 そのBeanの価格が直近1週間で$1.04まで上昇したこともあり、短期的なAPRとしては1000%を優に超えていました。

引用:https://www.coingecko.com/en/coins/bean

そのため、高いAPRを求めて時価総額も1週間でほぼ2倍に膨れ上がっているという状況でした。

引用:https://www.coingecko.com/en/coins/bean

しかしながら、このタイミングでexploitが行われ、提供されていた流動性(計24,830ETH)が根こそぎ引っこ抜かれ終わりを迎えます。 個人的には、歴代無担保型ステーブルコインと同様にペグが外れて盛大に爆死するのか、その結論が出る前にこのような形で終わってしまって残念です。

エクスプロイトの流れ

今回のexploitでは、オンチェーン投票とフラッシュローンが悪用されました。それぞれについて簡単に解説を入れます。

オンチェーン投票

DeFiの文脈ではガバナンストークン等を用いて投票が行われることが多々あります。 例えば、DEXでは特定のプールに対してインセンティブを与えるか与えないかだったり、Lending Protocolでは新しいトークンを担保として認めるか認めないかだったりを投票で決めます。 だって、ガバナンストークンを持ってるDEXがよく分からないトークンにAPR100万%ぐらいでガバナンストークン与えはじめたら嫌ですよね?そのために投票が利用されています。

投票を実現する方法は、オフチェーンで行われた投票の結果に従って管理者が実行したり、投票から実行までが全てオンチェーンであったりと様々です。 Beanstalkは後者であり、Beanstalkの実装では、提案者が提案内容を記述したスマートコントラクト(以下、提案SC)をデプロイし、プロトコルに対して提案、他のユーザがそれに対して投票を行います。 また、BeanstalkではプロトコルにDepositしたBean及びLPの量に応じて投票権が与えられていました。

その後、投票の結果、提案が認められればBeanstalkのコントラクトを介して提案SCに記述されたロジックを誰でも実行できる…という流れです。

フラッシュローン

フラッシュローンとは担保なしで瞬間的にお金を借りる方法のことで、ここでいう"瞬間"はEthereumの1トランザクションの実行の開始から終了までの間です。 つまり、フラッシュローンでは1トランザクションの間に貸した資金が戻ってくるので担保なしで貸したとしても貸し倒れが存在しません。そのため、様々なプロトコル(e.g. aave, uniswap, dydx, dodo, etc.)が手数料のため・利便性のためにこの機能を提供しています。

図にする必要があったのか分かりませんが、図にするとこんな感じ。なにかしらの操作、というのは例えばDeFiのLending Protocolでの借り換えやDEX間のアビトラ等に利用されています。 前述の通り、何の操作をするかは借り手の勝手ですが、例えば借りた1億ドル全額でETHを買ったとしても、トランザクションの終わりでは1億ドル耳を揃えて(プロトコルによっては手数料を添えて)返さなければトランザクションが失敗します。

エクスプロイト手法

ここまで読んでピンときた方は天才です!そうです。オンチェーン投票で悪意のある提案を行って、フラッシュローンで借りてきた資金を使ってそれにめちゃくちゃ投票して有効な提案にすればプロトコルに対して悪意のある攻撃を行うことができます。 ただ、開発者もそれが簡単に通るような設計にはしていません。具体的には、提案してから1日経過しないと提案の実行を行うことはできず、今回この条件をクリアするために巧妙な手口が用いられました。 以下、Beanstalkに対して行われたexploitについて時系列に沿って書いていきます。

exploitの1日前、攻撃者はBIP-18の提案SCをデプロイしました。 BIP-18というのは、Beanstalkで18個目の提案という意味であり、このコードはPublishされているので誰でも確認することができます。 実際に確認すると、ウクライナの寄付アドレスに対して25万Bean・提案者に対して1万Bean与える…といった内容になっています。

その後、Beanstalkに対して2つの提案(BIP-18BIP-19)が行われました。 また、提案の際には提案SCのアドレスを指定する必要があり、ここで開発者等を欺くような巧妙な操作が行われました。

それは、BIP-18の提案では「BIP-18の提案SCではないアドレス」を指定し、BIP-19の提案では「BIP-18の提案SCのアドレス」を指定したことです。 ちなみに、図ではコントラクトをオレンジ、通常のアドレスを緑に色分けしています。

この一連の提案を見た開発者は「BIP-18はアドレスの指定を間違えちゃったんだな!その後にアドレスにETH送ってるし!BIP-19が本来の提案!」と思ってBIP-18はスルーしたに違いありません。 実際、DiscordではBIP-19の内容(BIP-18の提案SCの内容)に対して言及して問題ない旨のコメントをしていました。

しかしながら、Ethereumには「コントラクトアドレスを事前に導出できる」という仕様があります。 ここでは詳細を省きますが、気になる人はこちらをご参照ください。 身近なところでは、コントラクトウォレットのArgentがこの仕組みを用いてユーザのアドレスを発行していたりします。

とにかく、この仕様を用いて攻撃者はexploitの直前にBIP-18で指定されたアドレスに対して悪意あるコントラクトをデプロイしました(下図5)。 その後、攻撃SCをデプロイし、exploitを実行。下図7,8,9を少し丁寧に書くと、

  1. フラッシュローンで借りてきたステーブルコインを用いてCurveのLPを作成
  2. BeanstalkにLPをdepositして閾値の2/3を超える投票権を取得
  3. BIP-18に対して投票・実行
  4. BIP-18で指定されたアドレスのスマートコントラクトが実行
  5. BeanstalkのコントラクトからDepositされているBean及びLPトークンを攻撃者に送付
  6. 1億Beanを攻撃者に新規mint
  7. フラッシュローンを返して残ったトークンをETHにswap

となります。

その結果、何が起きたかは冒頭の通りです。いや~、賢い。

何が悪かったのか/何が巧妙だったのか

実装の問題

BIP-18の実行にはemergencyCommitと呼ばれる緊急時用の関数が用いられました。emergencyCommitはどんな場合でも呼べる訳ではなく、投票期間として1日以上経過していること・投票が投票権の3分の2を満たしていることが条件です。 つまり、攻撃者は前述の巧妙な手口によって期間の条件を、フラッシュローンによって投票権の条件を満たしたわけです。

ここで、例えば、VoteしたトランザクションでemergencyCommitできないようにすればexploitを防ぐことができたので、その一点においては開発者の責任とも言えます。

コミュニティの問題

資金が集まってホットなDeFiにも関わらず誰も提案をチェックしていない、というのも問題でした。 前述の通り、exploitの実行までには1日の猶予期間があり、その間に気づくことができればexploitを未然に防ぐ事ができたかもしれません。 僕自身もコントラクトアドレスが事前に導出可能であることを知っていたので、開発者のDiscordのコメントを鵜呑みにせずに実際のTXを見に行けば気づけた可能性もあります。 つまり、自分を含むコミュニティのユーザも責任の一端を担っていると言えます。Don't Trust. Verify.

結局何が巧妙だったのか

「コントラクトアドレスを事前に導出可能であることをexploitに利用する頭の柔軟さ」も称賛に値しますが、今回のexploitの巧妙な点は「提案が安全なものであると誤認させ、誰にも気づかれずに1日やり過ごした」所です。

1回目の提案はコピペミスなどの誤操作を装い、2回目の提案はバカバカしい内容で注意を向けさせる…。 提案SCはEtherscan上でCodeが公開されているのですが、これが公開されていなかったり、提案者に対して100億Bean与えるといった致命的な内容であったりすれば警戒度は上がったことでしょう。 あくまでもウクライナの寄付アドレスに25万Bean、提案者に1万Bean与えるという内容だったからこそ開発者も軽視したのだと思います。

そして実際には見過ごされた1回目の提案のアドレスを使って攻撃を行う。惚れ惚れしますね。

今までのexploitは「スマートコントラクトに脆弱性見つけたぜ!資金引っこ抜きィ!資金洗浄!」って感じだったのですが、今回は人間の脆弱性を突いているようで面白いです。

あとがき

自分はいつも資金を突っ込む魔界みのあるDeFiを(実際の効果はさておき)セルフauditしてるのですが、こんだけ時価総額あったら誰かチェックしてるだろ!!!と他力本願寺したのが仇になりました。反省。 去年からずっと色々触ってふわふわしているので、もうちょっと手のかからない堅いDeFiに分散させて、自分のスキルアップを目指したい所存です。1億円ちょっとじゃ一生は暮らせねえんだ(自戒)。

ところでこのブログを書いて思ったんですが攻撃者=開発者の可能性、あると思います。
追記:開発者が自分たちじゃないよ、と個人情報をセルフ開示してました 大変だなあ

謝辞

本記事の執筆及び公開にあたり、内容を確認し適切なアドバイスをくださった代替性かおすさん環さんずがーんさんFFFさんひよっこさん垂水ケイさんさいころ君さん信玄さんに感謝します。