1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コントラクトの脆弱性を報告した話

Last updated at Posted at 2024-03-19

背景

お触り系タスクでありがちな,「一日一回無料でNFTをMintできる」類のタスクを自動化するために,該当するコントラクトの関数を見ていた時に発見(具体的なコントラクト名は伏せる).

コントラクト

まず,該当するコントラクトの関数部分を見てみる.

mintDailyNFT

function mintDailyNFT(
    address to,
    uint256 quantity,
    uint256 nonce,
    bytes memory signature
) public nonReentrant{
    _mintWithSignature(to, NFT_TOKEN_ID, quantity, nonce, signature, true);
    emit MintDailyOma(to, quantity);
}

これが一日一回NFTをMintできる関数である.動作としては,reentrancyを回避しつつ_mintWithSignatureを実行し,フロントエンドにイベントを送信している.ここでの疑問は,引数にあるsignatureとは何か?だろう.これは_mintWithSignatureを見ることで解決できる.

_mintWithSignature

function _mintWithSignature(
    address to,
    uint256 tokenId,
    uint256 quantity,
    uint256 nonce,
    bytes memory signature,
    bool isDaily
) internal {
    // nonceが既に使われていれば例外を投げる
    require(!usedNonces[nonce], "nonce already used.");
    
    // 引数からmessageを作成
    bytes32 hash = keccak256(
        abi.encodePacked(to, tokenId, quantity, nonce, address(this), isDaily)
    );
    bytes32 message = _prefixed(hash);
    
    // signatureを使用してmessageからsigner addressを復元
    address signer = message.recover(signature);

    // signer addressがDEFAULT_ADMIN_ROLEを持っていなければ例外を投げる
    // つまり,signerがadmin(contract creator)でなければ無効なtxである
    require(
        hasRole(DEFAULT_ADMIN_ROLE, signer),
        "bad signer role signature."
    );

    // nonceを使用済みにする
    usedNonces[nonce] = true;

    // NFTをMint
    _nftMint(to, tokenId, quantity);
}

ここで,signatureはadminによる署名であることが分かる.つまり,フロントエンドからMintしないと有効なsignatureは作れない ということである.更に,nonceがあるため署名の再利用はできない.よくできているなと感心しつつ,フロントエンドを介さないとMintできない事実に落胆した.

MintのInput Data

気を取り直して,フロントエンドからMintした際のInput Dataを見てみる.

test.png

他は予想通りだが,nonceに謎の値が使用されていることが分かる.ここで話は変わるが,MetaMask等のWalletアプリでは,txの送信前にtxの内容を確認できる機能が付いている.実際に,mintDailyNFTをフロントエンドから実行しようとした際のtxの内容を確認してみる.

test2.png

ここで,青線がnonce,赤枠がsignatureである.次に,このtxを送信せずにバツを押し,再度mintDailyNFTをフロントエンドから実行しようとした際のtxの内容を確認すると,以下の様になっていた.

test3.png

なんと,nonceの値が全く異なる.要するに,フロントエンド側はtxごとにランダムなnonce値を使ってsignatureを作成しているということである.これの何が脆弱性なのかというと,無限にnonceが異なるsignatureを作り放題=無限にMintし放題 ということである.

実際にやってみた

複数のsignatureを作成し,三連続でmintDailyNFTを実行してみた.

test4.png

実行できてしまった.(MintしたNFTはburn済)

脆弱な点まとめ

  1. nonceをtxごとに乱数で作成していた
  2. Mintが一回目かどうかをフロントエンド側で判断していた(これは仕方ない?)

以上,この記事がよりセキュアなコントラクト作成に活用できれば幸いです.

3/19 追記

脆弱性が修正されていました.おそらく日付毎に固定のnonceが設定されている様子.

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?