【入門編】 Mythrilによるシンボリック実行を用いた複雑な脆弱性解析 – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

こんにちは!ブロックチェーンの世界へようこそ。スマートコントラクトの開発、日々ワクワクしながら進めていますよね。「コードは法律(Code is Law)」なんて言われますが、自分が書いたプログラムに思わぬ抜け穴があって、ある日突然ウォレットから全財産が消えてしまったら……想像するだけでも冷や汗モノです。

今回は、そんなスマートコントラクトに潜む「複雑な論理的欠陥」を、最先端の解析ツール「Mythril(ミスリル)」を使って見つけ出す方法を、身近な防犯の仕組みに例えながら優しく紐解いていきたいと思います。一歩ずつ、安心して学んでいきましょう!

—

1. 家の鍵で例える「スマートコントラクトの論理的欠陥」

セキュリティの勉強を始めると、小難しい専門用語がたくさん出てきて挫折しそうになりますよね。まずは、私たちの身近にある「家と鍵」の例えで考えてみましょう。

皆さんの家には玄関の鍵がありますよね。普通は、ピッキング対策がされた頑丈な鍵をかけ、家族だけが合鍵を持っています。これが通常のセキュリティです。

しかし、スマートコントラクトにおける「論理的欠陥(ロジックのバグ)」というのは、こんな状態を指します。

  • 「玄関の鍵は最新式でピッキング不可能だけど、なぜか『裏庭の窓を3回ノックして犬の鳴き真似をすると、自動で玄関のドアが開く』という設計ミスが隠れていた」

泥棒(攻撃者)は、玄関の頑丈な鍵を無理やり壊そうとはしません。プロの泥棒は、開発者がうっかり作り込んでしまった「複雑な条件の組み合わせ(裏庭のノック+鳴き真似)」を見つけ出し、いとも簡単に家の中に入り込んでしまうのです。

スマートコントラクトも全く同じです。人間が頭の中で考えた条件分岐(「もしAがこうで、かつBが時間切れになっていなければ…」など)が複雑になればなるほど、プログラマーの意図しない「思わぬ抜け道」が生まれやすくなります。

—

2. Mythril(ミスリル)ってどんなツール?

「じゃあ、そんな人間には見つけにくい抜け道をどうやって探せばいいの?」と思いますよね。そこで登場するのが、今回主役のMythril(ミスリル)です。

ファンタジー小説に出てきそうなかっこいい名前ですが、これはEthereum(イーサリアム)などのスマートコントラクト向けの「シンボリック実行(記号実行)」という技術を使ったセキュリティ解析ツールです。

シンボリック実行を「自動で全パターンを試すロボット」に例えてみる

人間がコードを目視でチェックする場合、何百行もある複雑な条件分岐をすべて頭の中で追うのは至難の業です。途中で疲れて見落としてしまいますよね。

Mythrilは、いわば「絶対に疲れない、超高速のパズル自動解答ロボット」です。
ロボットは、コードの変数を具体的な数字(1や100など)ではなく、「何が入るかわからない変数(シンボル)」として扱い、あり得ない組み合わせも含めてあらゆる可能性のルートを同時にシミュレーションします。

そして、

  • 「おっと、この条件とあの条件をクリアすると、残高がゼロなのに誰でもお金を引き出せるルートが見つかりましたよ!」

と、人間が気づきにくい泥棒の侵入経路をあぶり出してくれるのです。

—

3. 実践!脆弱なコントラクトをMythrilで解析してみよう

百聞は一見に如かず。実際に、わざと複雑な論理的欠陥を仕込んだ簡単なスマートコントラクトのコードを見てみましょう。

以下のSolidityコードを Vault.sol という名前で保存したとします。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleVault {
    mapping(address => uint256) public balances;
    uint256 public lockTime;

    // お金を預ける関数
    function deposit() public payable {
        require(msg.value > 0, "0以上の金額を送金してください");
        balances[msg.sender] += msg.value;
        // ロック時間を現在のタイムスタンプから1週間後に設定
        lockTime = block.timestamp + 1 weeks;
    }

    // おを引き出す関数(ここに問題が潜んでいます!)
    function withdraw(uint256 _amount) public {
        // 条件1: 預けた金額が引き出し希望額以上であること
        require(balances[msg.sender] >= _amount, "残高が不足しています");
        
        // 条件2: ロック時間が過ぎているか、あるいは特殊な条件を満たしていること
        // 実はこの複雑な条件分岐に論理的な穴があります
        if (block.timestamp > lockTime || _amount == 1337 wei) {
            (bool sent, ) = msg.sender.call{value: _amount}("");
            require(sent, "送金に失敗しました");
            balances[msg.sender] -= _amount;
        }
    }
}

コードのどこが危ないの?

上記の withdraw 関数を見てください。通常のルールでは block.timestamp > lockTime (1週間経過する)にならないとお金を引き出せません。

しかし、開発者がこっそり仕込んだ(あるいはミスで入ってしまった) _amount == 1337 wei という条件はどうでしょう?
「もし引き出し額がジャスト 1337 wei なら、ロック時間に関係なくいつでも引き出せる」という裏口(バックドア)になってしまっています。攻撃者はこの複雑な条件の組み合わせを見つけ出し、あなたの大切な資金を不正に持ち去る可能性があります。

Mythrilを使ったスキャンの実行

このコードに対して、Mythrilを使って脆弱性診断を実行してみましょう。
ターミナル(コマンドプロンプト)を開き、以下のようにコマンドを打ち込みます。

# Mythrilを使ってSolidityファイルを解析するコマンド
myth analyze Vault.sol

しばらく待つと、Mythrilのロボットが裏側で何万通りものシミュレーションを終え、次のようなレポートを出力してくれます。

==== Integer Arithmetic Bugs / Logic Flaw ====
SWC ID: 101
Severity: High
Contract: SimpleVault
Function: withdraw(uint256)
PC Address: 0x3ab2
The control flow of the smart contract is dependent on a user-supplied value 
in a way that might allow unauthorized access to funds.
------------------------------------------------.

「おっ、ここ怪しいですよ!」と、コードのどの部分が論理的欠陥につながっているのかをピンポイントで教えてくれるのです。

—

4. 安全なコントラクトにするための対策

脆弱性が見つかったら、もちろんコードを修正しますよね。
先ほどの例であれば、複雑で意図しない抜け道を生むような「おまけの条件(_amount == 1337 wei など)」は一切排除し、セキュリティの鉄則であるシンプルな設計に書き換えます。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SecureVault {
    mapping(address => uint256) public balances;
    uint256 public lockTime;

    function deposit() public payable {
        require(msg.value > 0, "0以上の金額を送金してください");
        balances[msg.sender] += msg.value;
        lockTime = block.timestamp + 1 weeks;
    }

    // 修正版:複雑な例外条件をなくし、時間を厳格に守る
    function withdraw(uint256 _amount) public {
        require(balances[msg.sender] >= _amount, "残高が不足しています");
        
        // 泥棒が入り込む隙を与えないよう、条件をシンプルに一本化する
        require(block.timestamp > lockTime, "ロック期間がまだ終了していません");
        
        (bool sent, ) = msg.sender.call{value: _amount}("");
        require(sent, "送金に失敗しました");
        balances[msg.sender] -= _amount;
    }
}

このように、余計な裏口を作らないこと、そして複雑になりすぎたロジックは定期的にMythrilのようなシンボリック実行ツールでスキャンすることが、Web3開発における最大の防御策になります。

—

まとめ

今回は、Mythrilを用いたシンボリック実行による複雑な脆弱性解析について、身近な防犯の例えを交えて解説しました。

  • スマートコントラクトの「論理的欠陥」は、家の鍵の隙をつく泥棒の侵入経路のようなもの。
  • 人間の目では追い切れない複雑な条件分岐は、Mythrilという「自動パズル解答ロボット」にチェックしてもらう。
  • 開発の早い段階からツールを組み込んで、安全なコードベースを維持する。

セキュリティの世界は奥が深く、最初は難しく感じるかもしれませんが、一つひとつ仕組みを理解していけば確実にスキルアップできます。ぜひ、ご自身の開発プロジェクトにもMythrilを取り入れて、堅牢なスマートコントラクト作りを目指してみてくださいね。一歩ずつ、一緒に頑張っていきましょう!

コメント

タイトルとURLをコピーしました